当前位置: 首页 > article >正文

Android嵌套滑动造成的滑动冲突原理分析

嵌套滑动造成的滑动冲突原理分析

场景复现:

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 = false

http://www.kler.cn/a/568852.html

相关文章:

  • 补题蓝桥杯14届JavaB组第4题
  • 搭建elasticsearch集群,8.17.0版本
  • SpringCloud基础学习
  • 单片机知多少-STM32-GPIO-寄存器
  • 蓝桥杯刷题(Cows in a Skyscraper G,炮兵阵营)
  • java23种设计模式-迭代器模式
  • 数据挖掘实习面经一
  • CSS 系列之:选择器
  • Python接口自动化中操作Excel文件的技术方法
  • 【漫话机器学习系列】110.线性可分(Linearly Separable)
  • Android Audio基础(55)——音频常见指标
  • 【网络安全 | 渗透工具】自动化SSRF工具autossrf
  • 牛客周赛83:A:JAVA
  • JavaWeb个人笔记
  • 2024年中国城市统计年鉴(PDF+excel)
  • 大白话跨域问题怎么破,解决方法有啥?
  • 行为型模式 - 迭代器模式 (Iterator Pattern)
  • 蓝桥备赛(六)- C/C++输入输出
  • 设计模式Python版 观察者模式
  • 深度学习-14.深度强化学习:近端策略优化