返回介绍

NestedScrolling 事件机制源码解析

发布于 2024-12-23 22:07:07 字数 8243 浏览 0 评论 0 收藏 0

Android 在发布 5.0(Lollipop)版本之后,Google 为我们提供了嵌套滑动(NestedScrolling) 的特性,今天就由我带大家去看看嵌套滑动机制是怎样的原理?

首先,请随意瞄一瞄以下 4 个类:

有个大概印象就好,如果你一看就懂,那就不要浪费时间继续看下去了,啊哈哈哈!

NestedScrollingChild 说起,它是个接口,定义如下:

public interface NestedScrollingChild {  
  /** 
   * 设置嵌套滑动是否能用
   * 
   *  @param enabled true to enable nested scrolling, false to disable
   */  
  public void setNestedScrollingEnabled(boolean enabled);  
  
  /** 
   * 判断嵌套滑动是否可用 
   * 
   * @return true if nested scrolling is enabled
   */  
  public boolean isNestedScrollingEnabled();  
  
  /** 
   * 开始嵌套滑动
   * 
   * @param axes 表示方向轴,有横向和竖向
   */  
  public boolean startNestedScroll(int axes);  
  
  /** 
   * 停止嵌套滑动 
   */  
  public void stopNestedScroll();  
  
  /** 
   * 判断是否有父 View 支持嵌套滑动 
   * @return whether this view has a nested scrolling parent
   */  
  public boolean hasNestedScrollingParent();  
  
  /** 
   * 在子 View 的 onInterceptTouchEvent 或者 onTouch 中,调用该方法通知父 View 滑动的距离
   *
   * @param dx  x 轴上滑动的距离
   * @param dy  y 轴上滑动的距离
   * @param consumed 父 view 消费掉的 scroll 长度
   * @param offsetInWindow   子 View 的窗体偏移量
   * @return 支持的嵌套的父 View 是否处理了 滑动事件 
   */  
  public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);  

  /** 
   * 子 view 处理 scroll 后调用
   *
   * @param dxConsumed x 轴上被消费的距离(横向) 
   * @param dyConsumed y 轴上被消费的距离(竖向)
   * @param dxUnconsumed x 轴上未被消费的距离 
   * @param dyUnconsumed y 轴上未被消费的距离 
   * @param offsetInWindow 子 View 的窗体偏移量
   * @return  true if the event was dispatched, false if it could not be dispatched.
   */  
  public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,  
      int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);  
  

  
  /** 
   * 滑行时调用 
   *
   * @param velocityX x 轴上的滑动速率
   * @param velocityY y 轴上的滑动速率
   * @param consumed 是否被消费 
   * @return  true if the nested scrolling parent consumed or otherwise reacted to the fling
   */  
  public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);  
  
  /** 
   * 进行滑行前调用
   *
   * @param velocityX x 轴上的滑动速率
   * @param velocityY y 轴上的滑动速率 
   * @return true if a nested scrolling parent consumed the fling
   */  
  public boolean dispatchNestedPreFling(float velocityX, float velocityY);  
}  

以上方法数不多,请详细看看。

那它的作用是什么呢?让我们想想一种场景:CoordinatorLayout 里嵌套着 RecyclerView 和 Toolbar,我们上下滑动 RecyclerView 的时候,Toolbar 会随之显现隐藏,这是典型的嵌套滑动机制情景。这里,RecyclerView 作为嵌套的子 View,我们猜测,它一定实现了 NestedScrollingChild 接口(去看看它的定义就知道了,猜你个头)

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild {
..................................................................................
}

所以 RecyclerView 实现了 NestedScrollingChild 接口里的方法,我们在跟进去看看各个方法是怎么实现的?

  @Override
  public void setNestedScrollingEnabled(boolean enabled) {
    getScrollingChildHelper().setNestedScrollingEnabled(enabled);
  }

  @Override
  public boolean isNestedScrollingEnabled() {
    return getScrollingChildHelper().isNestedScrollingEnabled();
  }

  @Override
  public boolean startNestedScroll(int axes) {
    return getScrollingChildHelper().startNestedScroll(axes);
  }

  @Override
  public void stopNestedScroll() {
    getScrollingChildHelper().stopNestedScroll();
  }

  @Override
  public boolean hasNestedScrollingParent() {
    return getScrollingChildHelper().hasNestedScrollingParent();
  }

  @Override
  public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
      int dyUnconsumed, int[] offsetInWindow) {
    return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed,
        dxUnconsumed, dyUnconsumed, offsetInWindow);
  }

  @Override
  public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
    return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
  }

  @Override
  public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
    return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed);
  }

  @Override
  public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
    return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY);
  }

从上面的代码可以看出,全部都交给 getScrollingChildHelper() 这个方法的返回对象处理了,看看这个方法是怎么实现的。

   private NestedScrollingChildHelper getScrollingChildHelper() {
    if (mScrollingChildHelper == null) {
      mScrollingChildHelper = new NestedScrollingChildHelper(this);
    }
    return mScrollingChildHelper;
  }

对,NestedScrollingChild 接口的方法都交给 NestedScrollingChildHelper 这个代理对象处理了。现在我们继续深入,随意挑个,分析下 NestedScrollingChildHelper 中开始嵌套滑动 startNestedScroll(int axes) 方法是怎么实现的。

NestedScrollingChildHelper#startNestedScroll

  public boolean startNestedScroll(int axes) {
    if (hasNestedScrollingParent()) {
       return true;
    }
    if (isNestedScrollingEnabled()) {//判断是否可以滑动
      ViewParent p = mView.getParent();
      View child = mView;
      while (p != null) {
        if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {//回调了父 View 的 onStartNestedScroll 方法
          mNestedScrollingParent = p;
          ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
          return true;
        }
        if (p instanceof View) {
          child = (View) p;
        }
        p = p.getParent();
      }
    }
    return false;
  }

以上方法主要做了:

  1. 判断是否有嵌套滑动的父 View,返回值 true 表示找到了嵌套滑动的父 View 和同意一起处理 Scroll 事件。
  2. 用 While 的方式寻找最近嵌套滑动的父 View ,如果找到调用父 view 的 onNestedScrollAccepted .

从这里至少可以得出 子 view 在调用某个方法都会回调嵌套父 view 相应的方法,比如子 view 开始了 startNestedScroll ,如果嵌套父 view 存在,就会回调父 view 的 onStartNestedScrollonNestedScrollAccepted 方法。

有兴趣的朋友在去看看

NestedScrollingChildHelper#dispatchNestedPreScroll
NestedScrollingChildHelper#dispatchNestedScroll
NestedScrollingChildHelper#stopNestedScroll

的实现。

接下来,在来看看嵌套滑动父 view NestedScrollingParent,定义如下

public interface NestedScrollingParent {

  public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);

  public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);

  public void onStopNestedScroll(View target);

  public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
      int dxUnconsumed, int dyUnconsumed);

  public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);

  public boolean onNestedFling(View target, float velocityX, 
                            float velocityY,boolean consumed);
 
  public boolean onNestedPreFling(View target, float velocityX, float velocityY);

  public int getNestedScrollAxes();
}

你会发现,其实和子 view 差不多的方法,大致一一对应关系,而且它的具体实现也交给了 NestedScrollingParentHelper 这个代理类,这和我们上文的方式是一样的,就不再重复了。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文