Search code examples
androidandroid-viewpagerhorizontalscrollviewtouch-event

Vertical ViewPager with HorizontalScrollView inside Fragment


I have a VerticalViewPager whose Fragment content I want to scroll horizontally. The vertical paging works fine until the horizontal content is big enough to scroll. After that the touch events are not passed on to the ViewPager and so paging no longer works.

MVCE

This can be reproduced by setting up a simple project as given in this answer that I just posted.

Replace the fragment_one.xml file with

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent" >

    <HorizontalScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true" >

        <TextView
            android:id="@+id/textview"
            android:textSize="30sp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </HorizontalScrollView>

</RelativeLayout>

and set the text to anything long enough to scroll horizontally.

Things I've tried

  • NestedScrollView: That quickly turned out to be a dead end since it is apparently not available for horizontal scrolling. I thought about converting the source code to a HorizontalNextedScrollView but that would not be a trivial project.
  • Custom HorizontalScrollView: I tried converting answers like this and this for horizontal scrolling, but was unsuccessful. I'm going to continue experimenting in this area.
  • This is not a DoubleViewPager but I feel like the solution should be similar.

Solution

  • In order to answer this and other similar questions, it is very helpful to understand how the Android Framework handles touch events. My fuller answer on that is here, but I will include the summary diagram below.

    enter image description here

    The HorizontalScrollView (ViewGroup B in the diagram) is handling all touch events before the VerticalViewPager (ViewGroup A) has a chance to. So the solution is to use onInterceptTouchEvent() in the VerticalViewPager to selectively filter out vertical scrolls. For that it is easiest to just use a GestureDetector.SimpleOnGestureListener.onScroll().

    Here are the relevant changes you need to make to the VerticalViewPager class to accomplish that:

    public class VerticalViewPager extends ViewPager {
    
        GestureDetector mDetector;
    
        private void init() {
            // ...
            mDetector = new GestureDetector(getContext(), new VerticalScrollListener());
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            super.onInterceptTouchEvent(flipXY(ev));
            flipXY(ev);
    
            return mDetector.onTouchEvent(ev);
        }
    
        class VerticalScrollListener extends GestureDetector.SimpleOnGestureListener {
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, 
                                    float distanceX, float distanceY) {
                return Math.abs(distanceY) > Math.abs(distanceX);
            }
        }
    }