2

I have a VerticalViewPager where each child fragment contain something like

<MyScrollView>
    <LinearLayout orientation=vertical>
        ...
        ...
    </LinearLayout>
</MyScrollView>

When MyScrollView is scrolled to the bottom, I'd like the VerticalViewPager to kick in and do its magic. Likewise if I scroll the MyScrollView to the very top.

I've experimented a bunch with intercepting the touch events in the ScrollView to somehow dispatch the MotionEvent to the VerticalViewPager without any good luck.

This obviously doesn't work, but to get the conversation going I currently do something like the following in MyScrollView

override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
    return super.onInterceptTouchEvent(ev) && atBottom
}

override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
    atBottom = getChildAt(0).bottom <= (height + scrollY)
    super.onScrollChanged(l, t, oldl, oldt)
}

This works shaky and untrustworthy and not really at all. Any input is greatly appreciated!

Algar
  • 5,734
  • 3
  • 34
  • 51
  • Based on your logic, have you tried returning `false` from `onTouch` listeners in ScrollView? I think returning false would signal the SDK that you don't want the scrollview to handle touch event and it probably would be propagated further. – Shobhit Puri Mar 14 '18 at 21:54
  • You have a `ScrollView` inside a `ScrollView` (`MyScrollView` inside `VerticalScrollView`). Make sure, that `MyScrollView` is an instance of [`NestedScrollView`](https://developer.android.com/reference/android/support/v4/widget/NestedScrollView.html). – azizbekian Mar 15 '18 at 06:36
  • @azizbekian thanks, but I've tried that as well. Doesn't change anything. – Algar Mar 15 '18 at 08:23
  • Is the `VerticalScrollView` written by yourself, or a library? If you're using a library, can you add a link to it, or tell us its name? – cjurjiu Mar 16 '18 at 21:45
  • @cjurjiu Thanks for commenting. But no, no libraries. And just to be clear - `MyScrollView` is just a custom `NestedScrollView`, to make it easier for me to write touch interception code. But maybe you meant to ask about the `VerticalViewPager`? Thats just the same as any google hit would tell you to do. E.g. https://android.googlesource.com/platform/packages/apps/DeskClock/+/master/src/com/android/deskclock/VerticalViewPager.java – Algar Mar 16 '18 at 22:13
  • Rather than trying to intercept touch events, have you looked at using the method `canScrollVertically` inside of an `OnScrollChangeListener` and then manually advancing the view pager? You might find some inspiration in the `BottomSheetBehavior` class. – Victor Rendina Mar 19 '18 at 15:58

2 Answers2

0

Solution is probably very similar to this one over here: (original answer is for webview, but logic is the same)

Scroll webview horizontally inside a ViewPager

MarkySmarky
  • 1,609
  • 14
  • 17
  • Thanks for the answer. Maybe I'm doing something wrong but I didn't get it working with that approach. Maybe it has something to do with the from-horizontal-to-vertical stuff to do. – Algar Mar 18 '18 at 18:42
  • Yeah - you need to catch the pointer move event, understand the motion (vertical/horizontal) and pass the event to the responsible component. That's exactly what is described in my previous answer. – MarkySmarky Mar 19 '18 at 08:51
0

I think your idea to have the ScrollView dictate when the ViewPager should kick in is pretty good, it's already using requestDisallowInterceptTouchEvent to prevent the ViewPager from intercepting the scroll. What you need is use this same method to allow the intercept in the circumstances you've indicated, which is when the ScrollView has reached the top or bottom.

So if the ScrollView handles an ACTION_MOVE touch event but the resulting scroll offset hasn't changed we can assume that we've scrolled past the top or bottom (and are now overscrolling). There may be a better way to detect overscroll so have an experiment with this as a starting point:

//MyScrollView.java

@Override
public boolean onTouchEvent(MotionEvent ev) {
    boolean handled = super.onTouchEvent(ev);
    if (handled && ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
        int y = getScrollY();
        boolean moved = y != prevY;
        prevY = y;
        // This is the important part to allow the parent to intercept
        if (!moved) {
            ViewParent parent = getParent();
            if (parent != null) {
                parent.requestDisallowInterceptTouchEvent(false);
            }
        }
        // Also important to re
        return moved;
    }
    return handled;
}

EDIT - I had a quick play around and think this works nicely

public class MyScrollView extends ScrollView {

    private boolean clampedY;

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

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

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

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean handled = super.onTouchEvent(ev);
        if (handled) {
            ViewParent parent = getParent();
            switch (ev.getActionMasked()) {
                case MotionEvent.ACTION_MOVE:
                    if (clampedY && parent != null) {
                        parent.requestDisallowInterceptTouchEvent(false);
                    }
                    break;
                case MotionEvent.ACTION_DOWN:
                    clampedY = false;
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                    break;
            }
        }
        return handled;
    }

    @Override
    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
        super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
        this.clampedY = clampedY;
    }
}

EDIT 2 - This will keep ownership of the touch if there has been any scroll, so a separate touch is required to switch page

public class MyScrollView extends ScrollView {

    private boolean scrolled;

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

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

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

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean handled = super.onTouchEvent(ev);
        if (handled) {
            ViewParent parent = getParent();
            switch (ev.getActionMasked()) {
                case MotionEvent.ACTION_MOVE:
                    if (!scrolled && parent != null) {
                        parent.requestDisallowInterceptTouchEvent(false);
                    }
                    break;
                case MotionEvent.ACTION_DOWN:
                    scrolled = false;
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                    break;
            }
        }
        return handled;
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (t != oldt) {
            scrolled = true;
        }
    }
}
darnmason
  • 2,672
  • 1
  • 17
  • 23
  • Thank you for your time! I didn't get this to work perfectly with my `VerticalViewPager`, which essentially is https://android.googlesource.com/platform/packages/apps/DeskClock/+/master/src/com/android/deskclock/VerticalViewPager.java. Did you? It's pretty good if the `MyScrollView` is scrolled to the bottom, and you make a new touch to scroll even more. But there's a large jump when you continue into the overscroll directly. Do you see the same behaviour? – Algar Mar 19 '18 at 13:52
  • It's as if it believes that the distance I've scrolled before reaching the end, should be part of the overscroll distance. – Algar Mar 19 '18 at 18:12
  • I used the same Vertical view pager and didn't notice that behaviour, although I was using an emulator so probably didn't get a good feel for it, it might also depend on how big the scroll views are, I just dropped 50 Text Views into them – darnmason Mar 19 '18 at 21:12
  • It should be pretty straight forward to get the behaviour you want, requiring a new touch to scroll the page. In this case you'd want to keep the touch event if there has been any scrolling since the last ACTION_DOWN, so can add an extra condition to the ACTION_MOVE case – darnmason Mar 19 '18 at 21:16
  • Again, thank you for your time and effort. There was a misunderstanding into your "Edit 2" (which works pretty fine btw, but not what I meant). I don't want to require a new scroll to go into overscroll. It's just that with "Edit 1", the overscroll can mess things up. Check this sample video out https://youtu.be/JJIG13UwCjc . It shows how it all jumps oddly. And also, it's buggy in that you sometimes have to drag multiple times (almost always (if not always) when you drag down, to go upp in the view heirarchy). I'd love to give you the bounty but its not possible to use this in a real app yet. – Algar Mar 20 '18 at 08:16
  • Your effort/help deserves the bounty so you got it. But I can't mark the answer as accepted since it isn't possible to use this in a real app. I'll get back here when I have something running smoothly. Thanks anyway! – Algar Mar 21 '18 at 08:12