OnTouchEvent not working on child views - android

I have a Linear Layout that has a Button and a TextView on it. I have written a OnTouchEvent for the activity. The code works fine if I touch on the screen, but if I touch the button the code does not work. What is the possible solution for this?
public boolean onTouchEvent(MotionEvent event) {
int eventaction=event.getAction();
switch(eventaction)
{
case MotionEvent.ACTION_MOVE:
reg.setText("hey");
break;
}
return true;
}

The problem is the order of operations for how Android handles touch events. Each touch event follows the pattern of (simplified example):
Activity.dispatchTouchEvent()
ViewGroup.dispatchTouchEvent()
View.dispatchTouchEvent()
View.onTouchEvent()
ViewGroup.onTouchEvent()
Activity.onTouchEvent()
But events only follow the chain until they are consumed (meaning somebody returns true from onTouchEvent() or a listener). In the case where you just touch somewhere on the screen, nobody is interested in the event, so it flows all the way down to your code. However, in the case of a button (or other clickable View) it consumes the touch event because it is interested in it, so the flow stops at Line 4.
If you want to monitor all touches that go into your Activity, you need to override dispatchTouchEvent() since that what always gets called first, onTouchEvent() for an Activity gets called last, and only if nobody else captured the event. Be careful to not consume events here, though, or the child views will never get them and your buttons won't be clickable.
public boolean dispatchTouchEvent(MotionEvent event) {
int eventaction=event.getAction();
switch(eventaction) {
case MotionEvent.ACTION_MOVE:
reg.setText("hey");
break;
default:
break;
}
return super.dispatchTouchEvent(event);
}
Another option would be to put your touch handling code into a custom ViewGroup (like LinearLayout) and use its onInterceptTouchEvent() method to allow the parent view to steal away and handle touch events when necessary. Be careful though, as this interaction is one that cannot be undone until a new touch event begins (once you steal one event, you steal them all).
HTH

Let me add one more comment to this excellent post by #Devunwired.
If you've also set an onTouchListener on your View, then its onTouch() method will be called AFTER the dispatch methods, but BEFORE any onTouchEvent() method, i.e. in between no.3 and no.4 on #Devunwired's answer.

Try to set the descendantFocusability attribute of your layout to blocksDescendants

Activity::onTouchEvent will be called only when non of the views in the Activity WIndow consumes/handles the event. If you touch the Button, the Button will consume the events, so the Activity won't be able to handle it.
Check out following articles for more about Android Touch Event handling pipeline.
http://pierrchen.blogspot.jp/2014/03/pipeline-of-android-touch-event-handling.html

you can also try onUserInteraction():
#Override
public void onUserInteraction(){
//your code here
super.onUserInteraction();
}
works well for me!

RecyclerView list_view = findViewById(R.id.list_view);
list_view.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener(){
#Override
public boolean onInterceptTouchEvent(#NonNull RecyclerView rv, #NonNull MotionEvent e) {
View child = rv.findChildViewUnder(e.getX(), e.getY());
Log.i("Hello", "World");
return false;
}
});

use public boolean dispatchTouchEvent(MotionEvent event) instead on onTouchEvent()

Related

How to handle click in the child Views, and touch in the parent ViewGroups?

In my layout I have a structure like that:
--RelativeLayout
|
--FrameLayout
|
--Button, EditText...
I want to handle touch events in the RelativeLayout and in the FrameLayout, so I set the onTouchListener in these two view groups. But only the touch in the RelativeLayout is captured.
To try solve this, I wrote my own CustomRelativeLayout, and override the onInterceptTouchEvent, now the click in the child ViewGroup (FrameLayout) is captured, but the click in the buttons and other views doesn't make any effect.
In my own custom layout, I have this:
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}
You need to override the onInterceptTouchEvent() for each child, otherwise it will remain an onTouchEvent for the parent.
Intercept Touch Events in a ViewGroup
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/*
* This method JUST determines whether we want to intercept the motion.
* If we return true, onTouchEvent will be called and we do the actual
* scrolling there.
*/
...
// In general, we don't want to intercept touch events. They should be
// handled by the child view.
return false;
}
You need to return false to have the child handle it, otherwise you are returning it to the parent.
Your custom solution will capture touch events from anywhere in your relative layout since the overridden method is set to always throw true.
For your requirement I guess its better to use
the onClick method rather than using onTouch.
OnTouch method invokes different threads on every TouchEvent and I guess that is the cause of your problem
Rather than handling these events its better to try onClick method.
I was able to solve this problem with the following code:
Step 1: declare the EditText above the onCreate () method
public EditText etMyEdit;
Step 2: in the onResume () method the configuration ends:
etMyEdit = (EditText) findViewById (R.id.editText);
etMyEdit.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
v.getParent().requestDisallowInterceptTouchEvent(true);
switch (event.getAction() & MotionEvent.ACTION_MASK){
case MotionEvent.ACTION_UP:
v.getParent().requestDisallowInterceptTouchEvent(false);
return false;
}
return false;
}
});
Hope it helps someone!

Pass ontouchlistener to parent

I have 4 buttons in my activity and each of these buttons have a onTouchListener. I want to pass that event to the button's parent which is a Linear Layout. To achieve that, I have used
THIS :
button.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
public boolean onInterceptTouchEvent(MotionEvent ev)
{
onTouchEvent(ev);
return true;
}
});
but it doesn't work. What could be the problem?
OnTouchListener documentation indicates that onTouch() returns
True if the listener has consumed the event, false otherwise.
So since you return true, you indicate that the touch listener of your button consumed the event, and the even doesn't get propagated any further.
Returning false instead will make the event be propagated further up the view hierarchy.
Note though, that a button which unconditionally doesn't listen to touch events isn't a button. I would make sure whether a TextView for example isn't enough.
Although this would be true and sufficient for a simple TextView (or any View for that matter), you should note that Button is a clickable view and by default behaves as such. This means that whatever you return as a result of onTouch(), the touch event won't be propagated further and the Button's onClick() method will be called.
To disable this behavior and have it behave as you expect, just make it non-clickable:
button.setClickable(false);
Again, using a button doesn't make much sense anymore though.

onInterceptTouchEvent, onTouchEvent only see ACTION_DOWN

I have a top level ViewGroup, which I call SliderView, in which I want to detect swiping. This is mostly working, but one weird failure persists.
The essence of SliderView is to override onInterceptTouchEvent and, once the user is actually swiping, return "true" to prevent other views from seing the MotionEvent. Here is a snip of code:
public class SliderView extends ViewGroup
{
enum MoveState { MS_NONE, MS_HSCROLL, MS_VSCROLL };
private MoveState moveState = MoveState.MS_NONE;
... other code ...
public boolean onInterceptTouchEvent(MotionEvent e)
{
final int action = e.getAction();
switch (action & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
moveState = MoveState.MS_NONE;
break;
case MotionEvent.ACTION_MOVE:
if (moveState == MoveState.MS_NONE)
{
if (motion is horizontal)
{
moveState = MoveState.MS_VSCROLL;
return true;
}
else
moveState = MoveState.MS_VSCROLL; // let child window handl MotionEvent
}
else if (moveState == MoveState.MS_HSCROLL)
return true; // don't let children see motion event.
}
return super.onInterceptTouchEvent (e);
}
... other code ...
}
It is my understanding that my SliderView (which is the outermost view) should always recevie onInterceptTouchEvent. In one of my tests, where the top level child is a However, in the following case, this appears not to be.
When the top level child is a ScrollView, onInterceptTouchEvent gets ACTION_MOVE and my code does what I want. In another case, where the top level child is a LinearLayout, it fails sometimes: it always gets ACTION_DOWN but gets ACTION_MOVE only if the user touches a widget inside the LinearLayout; if touching blank area, only ACTION_DOWN comes through.
I'll note that it behaves as if the fail-case touches are happening outside the SliderView. However, if that were the case, why would I get the ACTION_DOWN events?
Second note: looking at the source code for ScrollView, I see it checking for "inChild"; I have not figured out what that's for and how it might be relevant.
Due to the answer of user123321 here
onInterceptTouchEvent only get called if the parent has a child view which returns "true" from onTouchEvent. Once the child returns true, the parent now has a chance to intercept that event
All you need is to call
requestDisallowInterceptTouchEvent(true);
on the parent view, like this -
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
view.getParent().requestDisallowInterceptTouchEvent(true);
switch(motionEvent.getActio){
}
return false;
}
From Android developer's reference (http://developer.android.com/reference/android/view/ViewGroup.html#onInterceptTouchEvent(android.view.MotionEvent)):
"2.
.... Also, by returning true from onTouchEvent(), you will not receive any following events in onInterceptTouchEvent() and all touch processing must happen in onTouchEvent() like normal."
Maybe because your onTouchEvent always returns true..?
When intercepting onTouchEvent, there are two things to do to properly intercept the touches (all else being default).
Return false in onInterceptTouchEvent()
#Override
public boolean onInterceptTouchEvent(MotionEvent me) {
return false;
}
Return true in onTouchEvent()
#Override
public boolean onTouchEvent(MotionEvent me) {
switch (me.getAction()) {
case MotionEvent.ACTION_DOWN:
log("MotionEvent.ACTION_DONE");
break;
case MotionEvent.ACTION_MOVE:
log("MotionEvent.ACTION_MOVE");
break;
case MotionEvent.ACTION_CANCEL:
log("MotionEvent.ACTION_CANCEL");
userActionDown = false;
break;
case MotionEvent.ACTION_UP:
log("MotionEvent.ACTION_UP");
break;
}
return true;
}
Then, for your case (and others). Do all your calculations in the onTouchEvent() as shown above. The onInterceptTouchEvent() will only be called once for the ACTION_DOWN. But, the onTouchEvent will also get the ACTION_DOWN event, and you'll need to return true there, rather than the super.
For more information regarding onInterceptTouchEvent(): http://developer.android.com/reference/android/view/ViewGroup.html#onInterceptTouchEvent(android.view.MotionEvent)
ps - When you ask questions here, you should also write the description of what you are trying to do. You might quite possibly find much better ways of doing things. For your case of navigation, the real answer you are looking for is ViewPager. It works great and is very easy to implement. You should also check out some other easy navigation patters that Android has to offer developers: link.

How to accept onSingleTapUp() on inner view while accept onFling() on outer view?

I have a view that covers entire screen (let's say ParentView), and child inner view ChildView that covers only portion of it.
I want to make ChildView to respond to onSingleTapUp(), while the ParentView respond to onFling(). I am trying to do so by attaching one SimpleOnGestureListener on ChildView and one SimpleOnGestureListener on ParentView.
To accept onSingleTapUp() from ChildView, its listener's onDown() has to return true.
But once I do that, the listener tied to ParentView does not hear any motion events anymore since it is taken by the ChildView's listener. Even though ChildView's onFling() returns false, the events do not flow to the ParentView's listener.
How can I make the parent view's listener catch the fling gesture while child view's listener catch tap gesture?
I don't think any source code is needed to explain the situation, but here is a snippet that sets up my ChildView listener.
ChildView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
return singleTapGestureDetector.onTouchEvent(motionEvent);
}
});
One workaround could be to have both ParentView and ChildView's listeners to handle onFling() while only ChildView's listener handle onSingleTapUp(), but in that case, fling won't be able to happen across the ChildView (like start outside the child and then end within the child), I believe.
I don't like my solution, but I found a way to do this. Hopefully somebody else will post better answer in the future, or at least my workaround is useful to somebody otherwise.
As I described in the question, the problem lies on how gesture listener works. For child view to catch onSingleTapUp() event, you return true on onDown(). But once you do that, the subsequent series of events won't go to the parent view even after your child view's onTouch() declares it is no longer interested in the event. If you forcefully call the parent's onTouch() within the child's onTouch() when its gesture detector returns false, yes the parent's onFling() will be invoked but the first MouseEvent argument will be NULL since it was consumed by the child view's onTouch().
I must be missing something since this seems very basic gesture detection scenario. Anyway, I couldn't find a way to do this in reasonable way.
So, my workaround is to make TouchListenerService as a singleton.
Both child view and parent view have this line:
view.setOnTouchListener(TouchListenerService.Instance());
and TouchListenerService starts like this:
public class TouchListenerService
extends GestureDetector.SimpleOnGestureListener
implements View.OnTouchListener {
// some code to implement singleton
public SingleTapUpHandler SingleTapUpHandler;
public FlingHandler FlingHandler;
private View _touchingView;
GestureDetector gestureDetector;
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (gestureDetector == null)
gestureDetector = new GestureDetector(_touchListenerService);
_touchingView = view;
boolean result = gestureDetector.onTouchEvent(motionEvent);
_touchingView = null;
return result;
}
// and some more code
Since it is the same event handler, parent view catches onFling() event successfully while child view can set SingleTapUpHandler to process click event.

Android: ViewGroup, how to intercept MotionEvent and then dispatch to target or eat it on demand?

Given that there is a ViewGroup with several children. As for this ViewGroup, I'd like to have it managing all MotionEvent for its all children, which says VG will
1. be able to intercept all events before they get dispatched to target (children)
2. VG will first consume the event, and determine if will further dispatch event to target child
3. DOWN, MOVE, UP, I'd like to see them as relatively independent, which means VG could eat DOWN, but give MOVE and UP to children.
I've read SDK guide "Handling UI Event", I knew event listeners, handlers, ViewGroup.onInterceptTouchEvent(MotionEvent), and View.onTouchEvent(MotionEvent).
Here's my sample,
#Override
public boolean onInterceptTouchEvent (MotionEvent event) {
if (MotionEvent.ACTION_DOWN == event.getAction()) {
return true;
}
return super.onInterceptTouchEvent(event);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (MotionEvent.ACTION_DOWN == event.getAction()) {
return true;
}
else {
if (!consumeEvent(event)) {
// TODO: dispatch to target since I didn't want to eat event
//return this.dispatchTouchEvent(event); // infinite loop!!!
}
}
return super.onTouchEvent(event);
}
To be able to eat some events, I have to return true in above both methods when DOWN event occurred, because SDK said so. Then I could see MOVE and up in onTouchEvent. However, in my case, I've no idea about how to dispatch event to children.
Above dispatchTouchEvent led to infinite loop, which was understandable, since VG itself might be the target. I can't tell which would be target at that moment, MotionEvent didn't give a hint, so dispatchTouchEvent was totally useless.
Anyone help me out? Thanks.
There is no easy way to find the source View from onInterceptTouchEvent, nor there is a way to "dispatch" these events. You can dispatch KeyEvents, but not MotionEvents.
A common way to deal with MotionEvents (e.g., for drag and drop) is to handle the MotionEvent.ACTION_DOWN events by the different Views (through the onTouch callback after implementing OnTouchListener), and the MotionEvent.ACTION_MOVE events through the parent Viewgroup's onInterceptTouchEvent method.
But some LOCs say a lot more than a bunch of words. There's a very nice example of what I'm saying here: http://doandroids.com/blogs/tag/codeexample/
If you handle the ACTION_DOWN event in the View itself, then you can store which View started it elsewhere and use that variable for further actions. The Event is bound to the same View until is finished by an ACTION_UP or an ACTION_CANCEL actions.
If you need to keep track the View and execute an action on the ViewGroup during the ACTION_DOWN, then I suggest you to add a public method in your ViewGroup (e.g. public boolean handleActionDown (View v, MotionEvent e) that will be called from the onTouch callback

Categories

Resources