Note: I have changed the original title of this question. Initially the title was "How to use several GestureDetector instances at once?", but I have found that the problem is related not to several detectors but to the hierarchy of views. The rest of the question is kept as is, I have decided not to delete anything, just read it through. Please advice if I should throw off the obsolete parts, I'm ready to do that but not sure if I should.
I have one view (button) inside another one (container), and I want them to do two things: 1) If user scrolled any view (tapped and moved), the whole container is scrolled. 2) if user clicked the button, the click handler is called for the button.
My container is not a simple view, it allows scrolling in both directions (here are the details), so I have set up a GestureDetector that detects scrolling events and scrolls the container, thanks 323go for his advice. This detector is called from onTouch method of the OnTouchListener attached to the main container view.
My problem is the button.
If I set the onClick handler, it seems to sink all motion events completely, so I cannot scroll the view if I began scrolling on the button.
OK, perhaps I could set another GestureDetector on the button so it would handle the clicks and the scrolling?
Unfortunately that doesn't work. The onScroll handler of the button receives crazy values in distanceX and distanceY parameters. It looks like both the container view and the button have their own scroll counters, so when I scroll the view, some offset is accumulated for the button.
So my question is: what is the intended way of using the GestureDetector class? Can I use several detectors in a hierarchy of views, and is it possible to delegate handling from one detector to another? I. e., a parent detector could handle the event if the child one returned false.
Added: in fact, I'd be happy with the simple "parent to child" solution like this: there is the only GestureDetector attached to the topmost view in the hierarchy, it receives all events and decides how to handle them, so if it detected scrolling—it simply scrolls the whole view and doesn't notify anyone, but if it detected a click—it finds the child view below the click position and forwards the click to it. But is that a true Android way? And is that even possible?
Further investigation: I've tried to follow the advice given in comments below and made my child gesture detector to return false from its onScroll method. That hadn't helped: if the child handler returned false, the parent one isn't called at all.
But I have noticed that if the child handler doesn't try to scroll the parent view, it will receive correct values.
Here is the code of my handlers:
private class ParentHandler extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Log.d("UI", "Parent Scroll X: " + String.valueOf(distanceX) + " Y: " + String.valueOf(distanceY));
scrollBy((int)distanceX, (int)distanceY);
return true;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {return true;}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {return true;}
@Override
public boolean onDown(MotionEvent e) {return true;}
};
private class ChildHandler extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Log.d("UI", "Child Scroll X: " + String.valueOf(distanceX) + " Y: " + String.valueOf(distanceY));
// The line below drives the handler crazy!
// But if I remove it, the view will not be scrolled at all
// because the parent handler is not called
// even if this handler returned false.
scrollBy((int)distanceX, (int)distanceY);
return false;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {return true;}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {return true;}
@Override
public boolean onDown(MotionEvent e) {return true;}
};
Note the comment in the second handler.
Here is what I get in logs if the crazy call is removed:
Parent Scroll X: -1.0 Y: 2.0
Parent Scroll X: 0.0 Y: 2.0
Parent Scroll X: -1.0 Y: 1.0
Parent Scroll X: -1.0 Y: 2.0
Parent Scroll X: 0.0 Y: 2.0
...
Child Scroll X: -1.0 Y: 2.0
Child Scroll X: 0.0 Y: 2.0
Child Scroll X: -1.0 Y: 1.0
Child Scroll X: -1.0 Y: 2.0
Child Scroll X: 0.0 Y: 2.0
But as soon as I try to move the view in the child handler, the values turn into something like this:
Child Scroll X: 13.0 Y: 22.0
Child Scroll X: -11.0 Y: -15.9999695
Child Scroll X: 11.0 Y: 17.00003
Child Scroll X: -10.0 Y: -16.00003
Child Scroll X: 11.0 Y: 19.99997
Note that values are alternating: 13 is followed by -11, and 11 goes next, and then -10, and so on. So the average drift seems to be OK, but if I just pass these values to the scrollBy call, the view will jitter and jump here and there all the time I'm scrolling it.
Obviously there is some problem with calling the scrollBy within the onScroll fired for the child view. There is no problem if doing the same call from the parent handler.
What may be wrong there?
Added: I've removed the GestureDetector that is attached to the parent, but the behaviour is the same.
So the problem is actually as follows. I need to scroll the entire view when scrolling is detected by the GestureDetector. To scroll the view, I call scrollBy. This does work if the original onTouch event is fired for the parent view, but it doesn't if the onTouch is fired for the child view: in the latter case trying to scroll the parent view affects values that the GestureHandler receives.
So this works:
parent.onTouch -> detector.onTouchEvent -> parent.scrollBy
and this doesn't:
child.onTouch -> detector.onTouchEvent -> parent.scrollBy
where detector is the same.