Handling Intercept touch event without extending ViewGroup - android

I have a one RelativeLayout and this layout is having nearly 10 views in it.
I have set OnTouchListener to this Layout and doing some work in it and returning true.
this listener is working fine when I touch the layout where there are no View (mean on Empty area). If I touch on child views of this layout, this listener is not firing...
and from the documentation, I understood that we can override onInterceptTouchEvent() by extending ViewGroup (so here RelativeLayout) and handle touch events before child views consume this event...
this will do a trick, but I need to modify many xml files where I need this functionality by replacing RelativeLayout with my CustomRelativeLayout.
so my question is:
is there any way to handle touch event for RelativeLayout (ofcourse ViewGroup) before child views in RelativeLayout consumes event? I don't want to extend RelativeLayout...

Try to override
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}
of Activity.
This method is the first that process touch events.
But in this case you need to work with current coordinates of views

While not necessarily being ideal, this works well for smaller layouts that won't have dynamic content.
In xml, set android:clickable="false" to all descendants of the ViewGroup in the layout (including nested ViewGroups and their children). Each child that gets clicked on will then propagate the click to its parent, eventually getting to the ViewGroup's default touch handlers. Make sure to set root ViewGroup, where you want to get the click events as android:clickable="true" or viewgroup.setClickable(true)
If you add views dynamically, make sure to call view.setClickable(false); before adding it to the view hierarchy.

Try add onTouchListener to RelativeLayout
RelativeLayout rl = (RelativeLayout) findViewById( R.id.relativeLauout );
rl.setOnTouchListener( new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// do something
return false;
}
});
or use method onTouchEvent from Activity
#Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
return super.onTouchEvent(event);
}

Try to return false in the onTouchEvent() method of child views when processing ACTION_DOWN event. So the forwarding touch events will not be sent to child views, aka event not consumed.

You can simply set in your xml:
android:clickable="true"

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 touch event to Parent View's onTouchListener in Android

I have a list view whose item is a linearlayout which has a button as it's child view. I want the ontouchLIstener of the linearlayout to work. I don't want to use onInterceptTouchEvent. Is there a way a I can pass on the touch form the button to the parent listview. I tried this
- returning true from the button's onTouchListener
private View.OnTouchListener buttonListener = new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Log.i(TAG, "Button On Touch");
return true;
}
};
But this does not work. It does not pass on the touch event to the linearlayout's onTouchListener.
There must be someway it should work.
The chain of events for a View group works like this - The dispatchEvent is called. Then dispatchEvent calls onInterceptTouchEvent. if it returns false, then the touch events are passed on to the children of the ViewGroup. If any of the children consume the event (in this case the button consumes the event) i.e if they return true then the motionevent is not passed on to other methods. Since the button is clickable it returns true in this case. If the onInterceptTouchEvent method returns true then the child views are not given the motion event and instead that ViewGroup's onTouchListener or onTouch method are called. Hence to pass on the touch event to the parent's (View Group) onTouchListener make sure to return true in the onInterceptTouchEvent method of the Parent (ViewGroup). You don't have to override onDispatchTouchEvent()
#Override
public boolean onInterceptTouchEvent(MotionEvent motionEvent) {
Log.i(TAG,"PARENT.onInterceptTouchEvent");
return true;
}
For more details about how the touch navigation is done, please see this stack post
Set your button clickable property to false, using:
button.setClickable(false);
Then in onTouch of button:
return false;
Note: This behavior is specific to button (and any other view that has clickable property set to true) that even if you return false from onTouch it will not propagate event to the parent and onClick method of the button will be called anyway.
EDIT: Another way is extending ListView class and overriding onInterceptTouch:
public class CustomListView extends ListView {
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// here you can listen for button touch events
}
}

Android - ignore parent view touch on child view touch

I have a layout like
RelativeLayout_Parent
-> RelativeLayout_Child
both views having touch event.
But when I touch on RelativeLayout_Child, touch event for parent is also fired.
How to ignore parent view touch on child view touch?
Very simple, implement OnTouchListener over your child view, and upon receiving Touch Event just return true fro child, this will make sure touch event is not propagated to others.
child_view.setOnTouchListener(new OnTouchListener(){
#Override
public boolean onTouch(View arg0, MotionEvent arg1) {
// this will make sure event is not propagated to others, nesting same view area
return true;
}
});

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.

OnTouchEvent not working on child views

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()

Categories

Resources