Android笔记(四十):ViewPager2嵌套RecyclerView滑动冲突进一步解决

发布于:2025-02-11 ⋅ 阅读:(68) ⋅ 点赞:(0)

背景

在这里插入图片描述
ViewPager2内嵌套横向滑动的RecyclerView,会有滑动冲突的情况,引入官方提供的NestedScrollableHost类可以解决冲突问题,但是有一些瑕疵,滑动横向RecyclerView到顶部,按住它不放手继续往左拖再往右拖,这时候会发现外层ViewPager2滑动了,而不是横向RecyclerView滑动,于是参考NestedScrollableHost进行逻辑完善

完整代码

  • 主要是增加判断外层ViewPager2是否可滚动来设置是否允许父View拦截事件
open class NestRecyclerView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
): RecyclerView(context, attrs) {
    
    private var initialX = 0f
    private var initialY = 0f
    private val parentViewPager: ViewPager2?
        get() {
            var v: View? = parent as? View
            while (v != null && v !is ViewPager2) {
                v = v.parent as? View
            }
            return v as? ViewPager2
        }

    private fun canViewScroll(target: View?, orientation: Int, delta: Float): Boolean {
        val direction = -delta.sign.toInt()
        return when (orientation) {
            0 -> target?.canScrollHorizontally(direction) ?: false
            1 -> target?.canScrollVertically(direction) ?: false
            else -> throw IllegalArgumentException()
        }
    }

    override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
        val orientation = parentViewPager?.orientation ?: return super.onInterceptTouchEvent(event)
        if (!canViewScroll(this, orientation, -1f) && !canViewScroll(this, orientation, 1f)) {
            return super.onInterceptTouchEvent(event)
        }
        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                initialX = event.x
                initialY = event.y
                parent.requestDisallowInterceptTouchEvent(true)
            }
            MotionEvent.ACTION_MOVE -> {
                val dx = event.x - initialX
                val dy = event.y - initialY
                val isVpHorizontal = orientation == ViewPager2.ORIENTATION_HORIZONTAL
                if (isVpHorizontal == dy.absoluteValue > dx.absoluteValue) {
                    parent.requestDisallowInterceptTouchEvent(false)
                } else {
                    if (canViewScroll(this, orientation, if (isVpHorizontal) dx else dy)) {
                        parent.requestDisallowInterceptTouchEvent(true)
                    } else {
                        if (canViewScroll(parentViewPager, orientation, if (isVpHorizontal) dx else dy)) {
                            parent.requestDisallowInterceptTouchEvent(false)
                        } else {
                            parent.requestDisallowInterceptTouchEvent(true)
                        }
                    }
                }
            }
        }
        return super.onInterceptTouchEvent(event)
    }
}

向上滑动AppBarLayout不联动问题

如果布局CoordinatorLayout + AppBarLayout + ViewPager2内嵌套横向滑动的RecyclerView,这时拖拽横向滑动的RecyclerView向上移,AppBarLayout不会跟着向上移

原因分析

  • 拖拽横向滑动的RecyclerView向上移时,CoordinatorLayout.onNestedPreScroll内的lp.isNestedScrollAccepted(type)返回false,造成AppBarLayout没有执行scroll
    在这里插入图片描述

  • lp.isNestedScrollAccepted(type)被赋值的地方,会根据AppBarLayout$Behavior.onStartNestedScroll返回的accepted进行赋值
    在这里插入图片描述

  • AppBarLayout$Behavior.onStartNestedScroll内,会判断nestedScrollAxes的值不是2就返回false
    在这里插入图片描述

  • RecyclerView也支持嵌套滑动。startNestedScroll是由NestedScrollingChildHelper实现的,它会将嵌套滑动上传,也就是NestedScrollingChild都会将嵌套滑动先交给NestedScrollingParent处理。

class RecyclerView
  ...
     public boolean onInterceptTouchEvent(MotionEvent e) {
        if (mLayoutSuppressed) {
            // When layout is suppressed,  RV does not intercept the motion event.
            // A child view e.g. a button may still get the click.
            return false;
        }

        // Clear the active onInterceptTouchListener.  None should be set at this time, and if one
        // is, it's because some other code didn't follow the standard contract.
        mInterceptingOnItemTouchListener = null;
        if (findInterceptingOnItemTouchListener(e)) {
            cancelScroll();
            return true;
        }

        if (mLayout == null) {
            return false;
        }

        final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
        final boolean canScrollVertically = mLayout.canScrollVertically();

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(e);

        final int action = e.getActionMasked();
        final int actionIndex = e.getActionIndex();

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                if (mIgnoreMotionEventTillDown) {
                    mIgnoreMotionEventTillDown = false;
                }
                mScrollPointerId = e.getPointerId(0);
                mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
                mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);

                if (mScrollState == SCROLL_STATE_SETTLING) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                    setScrollState(SCROLL_STATE_DRAGGING);
                    stopNestedScroll(TYPE_NON_TOUCH);
                }

                // Clear the nested offsets
                mNestedOffsets[0] = mNestedOffsets[1] = 0;

                int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
                if (canScrollHorizontally) {
                    nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
                }
                if (canScrollVertically) {
                    nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
                }
                startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
                break;

                ...
        return mScrollState == SCROLL_STATE_DRAGGING;
    }

这里RecyclerView是横向的,所以nestedScrollAxis会被赋值为1,RecyclerView内调用startNestedScroll会向上层view传递,直到交给CoordinatorLayout处理,而CoordinatorLayout在调用onStartNestedScroll的时候,AppBarLayout$Behavior.onStartNestedScroll又返回false了,造成CoordinatorLayout回调onNestedPreScroll(由RecyclerView在ACTION_MOVE时调用dispatchNestedPreScroll触发)时无法调用AppBarLayout的滚动。

解决方法

在CoordinatorLayout调用onStartNestedScroll的时候不处理横向的情况,就不会导致lp.isNestedScrollAccepted(type)被赋值

class NestedCoordinatorLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
): CoordinatorLayout(context, attrs) {

    override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean {
        return if (axes and ViewCompat.SCROLL_AXIS_HORIZONTAL != 0) {
            false
        } else super.onStartNestedScroll(child, target, axes, type)
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到