Delegate touch event from ScrollView to vertical V

2019-05-26 19:50发布

问题:

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!

回答1:

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



回答2:

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;
        }
    }
}