The specific phenomenon

Horizontal ViewPager2 nesting uses vertical lists. When sliding up and down to the bottom of the list, a slightly tilted finger triggers ViewPager2 to slide left and right, while when the list is not sliding to the bottom, it slides normally.

Problem analysis

According to the rules of event distribution, list should be used within the internal intercept method, namely the internal list receives the slide after the event, call requestDisallowInterceptTouchEvent (true) request don’t intercept the parent controls. Here use RecyclerView to view the source code of onTouchEvent, and it is true:

@Override
    public boolean onTouchEvent(MotionEvent e) {...switch (action) {
            case MotionEvent.ACTION_MOVE: {
                ...
                if (scrollByInternal(
                            canScrollHorizontally ? dx : 0,
                            canScrollVertically ? dy : 0,
                            e, TYPE_TOUCH)) {
                        getParent().requestDisallowInterceptTouchEvent(true); }... }}}Copy the code

May in turn push, since ViewPager2 slip by mistake, also means that the requestDisallowInterceptTouchEvent (true) not executed, look closely, the method of trigger is conditional, namely scrollByInternal method to return true. So if you look at the source code for scrollByInternal, the comment says

@return Whether any scroll was consumed in either direction.

True is returned when distance has been consumed in the sliding direction. Looking back at the phenomenon, it is also a good explanation why sliding to the bottom is easy to mistakenly touch ViewPager2 sliding, at this time, RecyclerView has no sliding distance to consume sliding events.

The solution

It’s easy, just figure out how to consume the sliding distance. Here a custom layout, wrapped RecyclerView, using nested sliding mechanism to consume the remaining rolling distance.

import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import androidx.core.view.NestedScrollingParent3
import androidx.core.view.ViewCompat

class TouchConsumerLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr), NestedScrollingParent3 {

    override fun onNestedScrollAccepted(child: View, target: View, axes: Int, type: Int){}override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean {
        return axes == ViewCompat.SCROLL_AXIS_VERTICAL
    }

    override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int){}override fun onNestedScroll(
        target: View,
        dxConsumed: Int,
        dyConsumed: Int,
        dxUnconsumed: Int,
        dyUnconsumed: Int,
        type: Int
    ) {
        onNestedScroll(
            target,
            dxConsumed,
            dyConsumed,
            dxUnconsumed,
            dyUnconsumed,
            type,
            intArrayOf(0.0))}override fun onNestedScroll(
        target: View,
        dxConsumed: Int,
        dyConsumed: Int,
        dxUnconsumed: Int,
        dyUnconsumed: Int,
        type: Int,
        consumed: IntArray
    ) {
        // The key point, just this line of code, consumes all the remaining scroll
        consumed[1] = dyUnconsumed
    }


    override fun onStopNestedScroll(target: View, type: Int){}}Copy the code