Receive callbacks for touch events without disrupting normal flow - android

I want my callback to be fired on every touch event for a particular view. I've found similar question: View.onTouchEvent only registers ACTION_DOWN event by there is no direct answer.
If true is returned from onTouch() then further events belonging to the same touch (eg. ACTION_MOVE) are reported, but flow is disrupted and normal event processing does not happen (eg. View is not entering in pressed state).
If false or super.onTouchEvent is returned then only ACTION_DOWN is reported but not other actions and normal processing happens correctly (eg. View is entering in pressed state). Unfortunately other callbacks like onInterceptTouchEvent() aren't called as well.
I want to be both notified on all touch events (ACTION_DOWN, ACTION_MOVE and so on) and not disrupt normal processing (eg. View should enter correct state when touched). How to achieve this behavior?

You can try to extend your particular View and overwrite dispatchTouchEvent like this
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
handleTouch(ev);
return super.dispatchTouchEvent(ev);
}
And implement handleTouch to do whatever you want to do without disrupting the normal flow.

Related

ListView parent view cannot intercept touch events

I have a simple layout:
<CustomFrameLayout>
<ListView />
</CustomFrameLayout>
In my CustomFrameLayout, I am overriding:
onInterceptTouchEvent(MotionEvent event);
and returning false. When scrolling the ListView, the onInterceptTouchEvent receives the ACTION_DOWN and the first ACTION_MOVE event. Then, the ListView seems to take over and onInterceptTouchEvent fails to receive the following ACTION_MOVE events.
This goes against what the documentation states:
For as long as you return false from this function, each following event (up to and including the final up) will be delivered first here and then to the target's onTouchEvent().
http://developer.android.com/reference/android/view/ViewGroup.html#onInterceptTouchEvent(android.view.MotionEvent)
I have looked into requesting touch events to not be intercepted at the ListView level.
requestDisallowInterceptTouchEvent(boolean disallowIntercept)
This does not seem to have any effect. Should I be calling this on the ListView cell view's as well? Is there something else I am missing? I am testing on Android 4.4
Thanks.
you should return true in onInterceptTouchEvent(MotionEvent event);
true indicates that you are willing to receive further touch actions where as false indicates you are not interested further actions...
I have decided to override this method instead. Here, I can guarantee that I can intercept all touch events before they are dispatched to and potentially consumed by the child views.
dispatchTouchEvents(MotionEvent event);

How to stop receiving onTouchEvents for a gesture after returning true the first time

I know that for an Android view, if I return true to onTouchEvent, then it means that you want to continue receiving events for the current gesture. If you return false it should not process more events for the current gesture
However I'd like to try to detect whether a gesture is a click or a hold/drag event, so I return true to the initial ACTION_DOWN event, until some number of milliseconds has elapsed, at which point I timeout and recognize the gesture as a hold/drag instead of a click.
Once the timeout has elapsed, I'd like to return false to further onTouchEvents, so that I can stop receiving events and let the parent view manage the rest of the drag.
However I seem to notice that once you return true for the first event, then it receives all further events with the associated gesture, even after I continually return false to later events. It seems the return value only matters for the very first event, after which it is ignored.
Is there any way to stop receiving onTouchEvent, and therefore pass it to the parent, once you have accepted the first event in a gesture?
Remember that the boolean return value of onTouchEvent only indicates whether the event has been handled or not - you should still receive events when the user is in the middle of a gesture.
Generally, you can tailor the behavior however you want in your handler:
private boolean hasHandledGesture = false;
public boolean onTouchEvent( MotionEvent event ){
if( !hasHandledGesture ){
// Do your actual handling here for the first event
return true;
} else {
return super.onTouchEvent( event );
}
}
However, I think in your case you'd be better off creating a handler that registers via setOnLongClickListener. Not only will it make your life easier, but you'll also let the OS decide how long it takes to make a long press. This is the best choice if you want to make the length of the press match the rest of the system, helping the overall app's native look and feel.
If you want to catch some more complex events, you might want to look at using a GestureDetector, but it probably is more complexity than you need if you're just detecting a long press.

why does return value of onTouchEvent either crashes application or freezes mapView?

I am working on a google map application where user can touch the map and mark start and end points over the map. Then the path connecting the two points is draw on the map.
To handle touch events I have implemented onTouchEvent method inside MapOverlay class.
I have observed some weird behavior with the map. When user gets a path connecting two points the map freezes i.e. i can't pan or zoom over the map.
I found out that if the return value is true inside the onTouchEvent which is inside the MapOverlay method the map freezes in above said circumstance. If i return false then map won't freeze. However if I want to mark new points on the map again the application crashes... How is return value of onTouchEvent related to this behavior?
The way onTouchEvent() even works is if you return true, it means the touch event was handled and Android won't continue down the view hierarchy to handle the touch event. If you return false, it will continue.
In this case, when you always return true, you're always saying the touch event was handled. Thus, any gestures you do will never be handled because you're saying it's already handled. Thus, it looks like the map freezes. Instead, put a condition like this:
#Override
public boolean onTouchEvent(MotionEvent event){
boolean wasHandled = false;
if(/* condition where your touch event was handled */)
wasHandled = true;
else
wasHandled = super.onTouchEvent(event);
return wasHandled;
}
The reason it crashes when you return false is either a problem in your program or it goes into a loop because the event's never handled.
If it returns true, the handler tells the system, that it handled the onTouchEvent and the system wont call another onTouchListener. If you return false, you tell the system, that you did not handle the touch event and it will call the onTouchListener of the underlying view. You map does not freeze, but it does not get any touch events, because your handler handles them.

How to get onTouchEvent, long click and context menu working together?

In our application we have a custom view (that extends ImageView) and in it we handle the touch events to record data. I wanted to add context menu functionality to this view and followed the guidelines in the official Android documents.
The onTouchEvent code works fine by itself. The context menu code also works fine. However, if I add them both, the context menu code stops working. I found out that with both pieces of code added, onCreateContextMenu is never called, therefore context menu is never displayed.
According to my interpretation of the Android documentation, returning false from onTouchEvent indicates that the event is not consumed, so it should be used for further processing. For some reason, it is not happening here. I would appreciate if anybody can tell me what I am missing. BTW, the target is Nexus One running 2.3.4 ROM.
Here's the code for the onTouchEvent in the custom view:
public boolean onTouchEvent(MotionEvent event)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
// Add event coordinates to an arraylist
break;
}
return false;
}
Thank you in advance for any help.
Elaborating on hackbod answer, you should probably have as last method statement return super.onTouchEvent(event);.
I guess that if you don't process the event, and if you don't invoke the default View behavior, than no one will do anything, and nothing will happen.
The point of return value might be for example to invoke some ancestor' default behavior, and let the derived class know if the ancestor processed the event or not.
After doing some search on Android Developers, referring to the topic override an existing callback method for the View here it says :
This allows you to define the default behavior for each event inside your custom View and determine whether the event should be passed on to some other child View.
Hence the main idea behind the return value is to let Android know whether the event should be passed down to child Views or not.
HTH
Edit:
Regarding the "directions" you mention in your comment, generally speaking (i.e. not only on Android) the UI event handling process goes on something like this:
At some point your derived custom control receives the event. In your event hook implementation, it's up to you whether to involve your ancestor's behavior or not. That's all you got regarding the class inheritance direction.
Then, there's the other direction, the one related to the UI controls hierarchy. Your custom control might be contained in one larger control container, and your control might as well contain other inner controls (textboxes, buttons, ...). Regarding this direction, if you declare not to process the event (returning false) then the UI event handling mechanism will pass the bucket to the containing (?) control (think the one on the background of yours).
You could call, from your long click listener,
openContextMenu(View view)
http://developer.android.com/reference/android/app/Activity.html#openContextMenu(android.view.View)
Do not register for context menu in OnCreate(), do it in onTouch() before
return true;
registerForContextMenu(View v);
openContextMenu(View v);
return true;
Returning false tells the parent that you didn't consume the event. The default implementation of View implements touch handling for that view; if you want that to execute, you must call super.onTouchEvent(event);
I encounter similar problem recently. When I enable long clickable in RecyeclerView's child, the ACTION_DOWN event can't not be received in RecyclerView's onTouchEvent.
If I changed to RecyclerView's dispatchTouchEvent, I would works. The ACTION_DOWN event can be received.

Does onInterceptTouchEvent() really works as the sdk said?

ths sdk said:
3、For as long as you return false from this function, each following event (up to and including the final up) will be delivered first here and then to the target's onTouchEvent().
4、If you return true from here, you will not receive any following events: the target view will receive the same event but with the action ACTION_CANCEL, and all further events will be delivered to your onTouchEvent() method and no longer appear here.
But when i use this method,no matter what onInterceptTouchEvent() returns ,it does the same work! And never did MotionEvent.ACTION_MOVE or MotionEvent.ACTION_UP been captured by this method. Can anybody help me figure it out?
ths!
Normally the touch goes from most upper view to the lowest through onInterceptTouchEvent and then it goes back via onTouchEvent.
If you return true in onInterceptTouchEvent you forbid it to continue and the view where you returned true is the last one to receive the touch, you consume it
You can also disallow your parent view to consume the event by
requestDisallowInterceptTouchEvent(true);
When none of the children of your view return true in onTouchEvent, onInterceptTouchEvent will ONLY be called for MotionEvent.ACTION_DOWN.
Here is a complete description of the MotionEvent processing.

Categories

Resources