Android嵌套滑动造成的滑动冲突原理分析
- 游戏开发
- 2025-09-05 04:45:02

嵌套滑动造成的滑动冲突原理分析 场景复现:
CoordinatorLayout + AppBarLayout + Vertical RecyclerView + Horizontal RecycleView
Horizontal RecycleView 是Vertical RecyclerView的一个子view, CoordinatorLayout 实现了AppBarLayout 和 RecyclerView的协调联动,在向上滑动RecyclerView的时候,会先滑动AppBarLayout,再滑动RecyclerView,问题场景是在点击Horizontal RecycleView,然后向上滑动时,造成了Vertical RecyclerView 滑动,而AppBarLayout 没有滑动。
原理解析:Behavior的概念
Behavior用于为特定的子 View 定义自定义的交互行为,它通常与 CoordinatorLayout 一起使用,允许开发者在 View 之间创建复杂的滚动、滑动、拖拽等行为。
Behavior 提供了一些关键的回调方法,用于处理事件:
方法描述onInterceptTouchEvent是否拦截触摸事件。onTouchEvent处理触摸事件。onStartNestedScroll是否开始处理嵌套滑动事件。onNestedScrollAccepted当嵌套滑动被接受时调用。onNestedPreScroll嵌套滑动事件之前调用,用于消费部分或全部滑动。onNestedScroll在嵌套滑动期间调用,用于处理滑动的剩余部分。onStopNestedScroll嵌套滑动结束时调用。layoutDependsOn定义该 Behavior 是否依赖另一个 View。onDependentViewChanged当依赖的 View 发生变化时调用(如位置或大小变化)。CoordinationLayout + AppBarLayout + Vertical RecycleView是如何实现联动的呢?
1.点击AppBarLayout 位置时
AppBarLayout 不会消费事件,会将事件传递给CoordinationLayout#onTouchEvent()
if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) { // Safe since performIntercept guarantees that // mBehaviorTouchView != null if it returns true //mBehaviorTouchView 就是AppBarLayout final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams(); final Behavior b = lp.getBehavior(); if (b != null) { handled = b.onTouchEvent(this, mBehaviorTouchView, ev); } }mBehaviorTouchView 就是AppBarLayout,这时就调用了AppbarLayout#Behavior#onTouchEvent()事件,在这里处理了滑动事件scroll。
2.点击RecyclerView位置滑动时,
首先先进行事件传递,在Action_Down的时候会调用 startNestedScroll(nestedScrollAxis, TYPE_TOUCH),去询问嵌套布局CoordinatorLayout是否可以滑动,嵌套布局怎么来的呢?
if (isNestedScrollingEnabled()) { ViewParent p = mView.getParent(); View child = mView; while (p != null) { if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) { setNestedScrollingParentForType(type, p); ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type); return true; } if (p instanceof View) { child = (View) p; } p = p.getParent(); } } //ViewParentCompat.class public static boolean onStartNestedScroll(@NonNull ViewParent parent, @NonNull View child, @NonNull View target, int nestedScrollAxes, int type) { if (parent instanceof NestedScrollingParent2) { return ... } }重点看上面的while循环,你会发现它会遍历自己的父view 直到找到实现了NestedScrollingParent2的View ,这里就是指CoordinationLayout了,当找到的时候,将会遍历CoordinationLayout 所有子View 的Behavior是否可以实现嵌套联动的滑动,
// CoordinationLayout.class public boolean onStartNestedScroll(View child, View target, int axes, int type) { boolean handled = false; final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View view = getChildAt(i); if (view.getVisibility() == View.GONE) { // If it's GONE, don't dispatch continue; } final LayoutParams lp = (LayoutParams) view.getLayoutParams(); final Behavior viewBehavior = lp.getBehavior(); if (viewBehavior != null) { // 在这里会去调用子view的Behavior去判断是否支持嵌套滚动 final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target, axes, type); handled |= accepted; // 设置子View是否支持嵌套滚动,指AppBarlayout是否支持 lp.setNestedScrollAccepted(type, accepted); } else { lp.setNestedScrollAccepted(type, false); } } return handled; }设置完是否支持嵌套滚动后,滚动事件就会到Action_Move事件中,这时RecyclerView 就会消费这个事件,主要是以下两行代码:
//RecyclerView.class //这行代码将x,y传递给CoordinationLayout,然后CoordinationLayout#NestedScroll()方法将x,y传递给AppBarlayout消费 dispatchNestedPreScroll(canScrollHorizontally ? dx : 0, canScrollVertically ? dy : 0, mReusableIntPair, mScrollOffset, TYPE_TOUCH) //消费,最后自己消费剩余的滚动 if (consumedX != 0 || consumedY != 0) { dispatchOnScrolled(consumedX, consumedY); }以上就实现了联动滑动的效果。
问题来了,为什么在Vertical RecyclerView 再加一个Horizontal RecycleView的时候就会是Vertical RecyclerView来消费了呢,原因就是在onStartNestedScroll 通知到AppBarlayout#Behavior时,Horizontal 和Vertical 都通知了一遍,Horizontal时后面通知的,看一下通知后的代码:
//AppBarLayout.class public boolean onStartNestedScroll( @NonNull CoordinatorLayout parent, @NonNull T child, @NonNull View directTargetChild, View target, int nestedScrollAxes, int type) { // Return true if we're nested scrolling vertically, and we either have lift on scroll enabled // or we can scroll the children. //这里判断了ViewCompat.SCROLL_AXIS_VERTICAL 如果不是VERTICAL的时候就不允许嵌套滑动了 final boolean started = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0 && (child.isLiftOnScroll() || canScrollChildren(parent, child, directTargetChild)); 。。。。 return started; }然后Horizontal RecycleView在竖向滑动时是不支持的,所以事件由Vertical RecyclerView滑动,这时呢,嵌套滑动又被拒绝了,只能是
Vertical RecyclerView来响应滑动事件了。
解决方案:
recyclerView.isNestedScrollingEnabled = falseAndroid嵌套滑动造成的滑动冲突原理分析由讯客互联游戏开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Android嵌套滑动造成的滑动冲突原理分析”