HorizontalScrollView within ScrollView Touch Handling HorizontalScrollView within ScrollView Touch Handling android android

HorizontalScrollView within ScrollView Touch Handling


Update: I figured this out. On my ScrollView, I needed to override the onInterceptTouchEvent method to only intercept the touch event if the Y motion is > the X motion. It seems like the default behavior of a ScrollView is to intercept the touch event whenever there is ANY Y motion. So with the fix, the ScrollView will only intercept the event if the user is deliberately scrolling in the Y direction and in that case pass off the ACTION_CANCEL to the children.

Here is the code for my Scroll View class that contains the HorizontalScrollView:

public class CustomScrollView extends ScrollView {    private GestureDetector mGestureDetector;    public CustomScrollView(Context context, AttributeSet attrs) {        super(context, attrs);        mGestureDetector = new GestureDetector(context, new YScrollDetector());        setFadingEdgeLength(0);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev);    }    // Return false if we're scrolling in the x direction      class YScrollDetector extends SimpleOnGestureListener {        @Override        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {                         return Math.abs(distanceY) > Math.abs(distanceX);        }    }}


Thank you Joel for giving me a clue on how to resolve this problem.

I have simplified the code(without need for a GestureDetector) to achieve the same effect:

public class VerticalScrollView extends ScrollView {    private float xDistance, yDistance, lastX, lastY;    public VerticalScrollView(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN:                xDistance = yDistance = 0f;                lastX = ev.getX();                lastY = ev.getY();                break;            case MotionEvent.ACTION_MOVE:                final float curX = ev.getX();                final float curY = ev.getY();                xDistance += Math.abs(curX - lastX);                yDistance += Math.abs(curY - lastY);                lastX = curX;                lastY = curY;                if(xDistance > yDistance)                    return false;        }        return super.onInterceptTouchEvent(ev);    }}


I think I found a simpler solution, only this uses a subclass of ViewPager instead of (its parent) ScrollView.

UPDATE 2013-07-16: I added an override for onTouchEvent as well. It could possibly help with the issues mentioned in the comments, although YMMV.

public class UninterceptableViewPager extends ViewPager {    public UninterceptableViewPager(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        boolean ret = super.onInterceptTouchEvent(ev);        if (ret)            getParent().requestDisallowInterceptTouchEvent(true);        return ret;    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        boolean ret = super.onTouchEvent(ev);        if (ret)            getParent().requestDisallowInterceptTouchEvent(true);        return ret;    }}

This is similar to the technique used in android.widget.Gallery's onScroll().It is further explained by the Google I/O 2013 presentation Writing Custom Views for Android.

Update 2013-12-10: A similar approach is also described in a post from Kirill Grouchnikov about the (then) Android Market app.