piveloper
piveloper

Reputation: 185

Scrolling behaviour: HorizontalScrollView inside ViewPager

I have a View Pager (VP) which contains a Horizontal Scroll View (HSV). If the HSV reaches one of its edges or is not able to scroll at all, on a new swipe in the blocked direction VP should take over scrolling to the next page. I hesitated to ask this question because I found similar ones like these:

Can I use Horizontal Scrollview Inside a Viewpager in Android?

or

horizontalscrollview inside viewpager

But the solution did not work for me. 'v instanceof HorizontalScrollView' gets true but viewPager does not scroll

Any other ideas how to achieve the desired behaviour?

public class MyViewPager extends ViewPager {

public MyViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
}
// Update 1
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return true;
    //return super.onInterceptTouchEvent(ev);
}

/**
 * https://stackoverflow.com/questions/22781496/can-i-use-horizontal-scrollview-inside-a-viewpager-in-android
 */
@Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
    if (v instanceof HorizontalScrollView) {
        return true;
    }
    return super.canScroll(v, checkV, dx, x, y);
}

}

child view: view_pager_page.xml:

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    >
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center">
            <HorizontalScrollView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="center">

                    <include layout="@layout/" />
                </LinearLayout>
            </HorizontalScrollView>
        </LinearLayout>
    </FrameLayout>
</RelativeLayout>

parent view: view_pager.xml

<android.support.design.widget.CoordinatorLayout>
...
<LinearLayout>
<packagepath.MyViewPager
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">
</packagepath.MyViewPager>
</LinearLayout>
...
<android.support.design.widget.CoordinatorLayout>

Update 1: When overriding 'onInterceptTouchEvent' and let it always return true VP scrolls, but HSV doesn't. I think this must return true only if HSV reaches edges right? How can I figure out in this method if it is the case?

Update 2: I reconstructed the touch event mechanism of android hoping to get some insight of how to intercept the motion event flow. E.g. in HSV I can simply return false to let VP consume this and all subsequent motion events. Unfortunately I need two motion events of type MotionEvent.MOVE to decide if HSV or VP should scroll when reaching an edge (if HSV has reached right edge, a right swipe scrolls HSV back and a left swipe scrolls to next page of VP). But if I skip the MotionEvent.DOWN action neither HSV or VP starts scrolling... so hard to solve. Any ideas?

Touchevent Mechanism in Android

(Warning: Graphic is not complete and will contain mistakes, everyone is invited to correct it :-))

Update 3: Finally I got it working. Understanding the Touchevent mechanism helped a lot and also the first comment of ZeroOne. I will post my solution when I have time for it.

Upvotes: 0

Views: 4523

Answers (2)

Chris Knight
Chris Knight

Reputation: 25064

I solved this with a custom HorizontalScrollView. The key is to override the onTouchEvent() method and return false if you are at the left edge and swiping right, or the right edge and swiping left. Returning false means this view didn't consume the touch event and this event can bubble back up the view hierarchy to be handled by the ViewPager.

public class HorizontalScrollViewForViewPager extends HorizontalScrollView {

float old_x, old_y;

public HorizontalScrollViewForViewPager(Context context) {
    super(context);
}

public HorizontalScrollViewForViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public HorizontalScrollViewForViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    int action = ev.getActionMasked();

    if (action == MotionEvent.ACTION_DOWN) {
        //Start of touch.  Could be tap, could be drag.
        old_x = ev.getX();
        old_y = ev.getY();
    } else if (action == MotionEvent.ACTION_MOVE) {
        //Drag movement underway
        float deltaX = ev.getX() - old_x;
        float deltaY = ev.getY() - old_y;

        if (Math.abs(deltaX) > Math.abs(deltaY)) {
            //scrolling more left/right than up/down
            if (deltaX > 0 && getScrollX() == 0) {
                //dragging left, at left edge of HorizontalScrollView.  Don't handle this touch event, let it bubble up to ViewPager
                return false;
            } else {
                //dragging right.  Use first child to determine width of content inside HorizontalScrollView
                int childWidth = getChildAt(0).getWidth();
                if (deltaX < 0 && (this.getScrollX() + this.getWidth()) >= childWidth) {
                    //swiping left, and at right edge of HorizontalScrollView.  Don't handle this touch event, let it bubble up to ViewPager
                    return false;
                }
            }
        }
    }

    return super.onTouchEvent(ev);
}

}

Upvotes: 2

piveloper
piveloper

Reputation: 185

1.Extend ViewPager Class:

public class ViewPagerContainingHorizontalScrollView extends ViewPager {
private Float x_old;
private boolean bDoIntercept = false;
private boolean bHsvRightEdge = false;
private boolean bHsvLeftEdge = true;


public ViewPagerContainingHorizontalScrollView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

private float calculateDistanceSwipe(MotionEvent ev){
    float distance = 0;
    if (x_old == null) {
        x_old = ev.getX();
    } else {
        distance = ev.getX() - x_old;
        x_old = null;
    }
    return distance;
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    mDoIntercept = false;
    if(ev.getAction() == MotionEvent.ACTION_MOVE) {
        float distance = calculateDistanceSwipe(ev);

        if (distance < 0) {//scrolling left direction
            if (bHsvRightEdge) { //HSV right edge
                bDoIntercept = true;
                //When scrolling slow VP may not switch page.
                //Then HSV snaps back into old position.
                //To allow HSV to scroll into non blocked direction set following to false.
                bHsvRightEdge = false;
            }
            bHsvLeftEdge = false;//scrolling left means left edge not reached
        } else if (distance > 0) {//scrolling right direction
            if (bHsvLeftEdge) { //HSV left edge
                bDoIntercept = true;
                //When scrolling slow VP may not switch page.
                //Then HSV snaps back into old position.
                //To allow HSV to scroll into non blocked direction set following to false.
                bHsvLeftEdge = false;
            }
            bHsvRightEdge = false;//scrolling right means right edge not reached
        }
    }
    return super.dispatchTouchEvent(ev);
}


@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if(bDoIntercept){
        return true;
    }else{
        return super.onInterceptTouchEvent(ev);
    }
}

@Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
    if (v instanceof HorizontalScrollView) {
        HorizontalScrollView hsv = (HorizontalScrollView) v;
        int max_scrollX = hsv.getChildAt(0).getWidth() - hsv.getWidth();
        int min_scrollX = 0;
        int current_scroll_x = hsv.getScrollX();

        if (current_scroll_x == max_scrollX) { //HSV right edge
            bHsvRightEdge = true;
        }

        if (current_scroll_x == min_scrollX) { //HSV left edge
            bHsvLeftEdge = true;
        }
        return true;
    }
    return super.canScroll(v, checkV, dx, x, y);
}
}
  1. Use this custom VP in XML.
  2. Enjoy nested HSV scrolling in VP :-)

Touch Event Mechanism Overview for this specific case

Upvotes: 1

Related Questions