【view】- NestedScrolling機(jī)制

簡介

這篇文章講解Android嵌套滾動機(jī)制,像滑動實現(xiàn)頂部懸停,SDK中的SwipeRefreshLayout下拉刷新組件都用到了嵌套滾動機(jī)制。下面將以一個最簡單的模型來分析嵌套滾動的執(zhí)行流程,滾動事件的消耗邏輯。

模型

截屏2020-04-14上午10.48.46.png
  • StickyNavLayoutTest


    截屏2020-04-14上午10.56.20.png

StickyNavLayoutTest是一個實現(xiàn)了NestedScrollingParent全部方法的類。ViewGroup也實現(xiàn)了,里面有一些分發(fā)嵌套滾動事件的邏輯。一會分析。

分發(fā)

RecyclerView攔截觸摸事件,看一下RecyclerView怎么處理的。定位到RecyclerView的onTouchEvent方法。

按下事件
  • 設(shè)置嵌套滾動的偏移量
    mNestedOffsets[0] = mNestedOffsets[1] = 0;
  • 修正坐標(biāo)
    vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]);
  • 分發(fā)嵌套滾動開始事件
    startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
    1. nestedScrollAxis:是橫軸滾動還是豎軸滾動
    2. TYPE_TOUCH:0,觸摸事件類型
    public boolean startNestedScroll(int axes, int type) {
         return getScrollingChildHelper().startNestedScroll(axes, type);
    }
    
    創(chuàng)建一個NestedScrollingChildHelper幫助類來分發(fā)
    1. 判斷是否存在嵌套滾動父類且正處于嵌套滾動事件中,是者直接返回。

      if (hasNestedScrollingParent(type)) {return true;}
      
    2. 嵌套滾動使能,獲取當(dāng)前View(即RecyclerView)的父View

      if (isNestedScrollingEnabled()) {
          ViewParent p = mView.getParent();
          View child = mView;
          ...
      }
      
    3. 分發(fā)給父View
      while循環(huán)找到支持嵌套滾動的父View,并傳入該View的直接子View和啟動嵌套滾動的View。

      while (p != null) {
          if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) {
                setNestedScrollingParentForType(type, p);
                ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type);
                return true;
      }
      

      看一下ViewParentCompat.onStartNestedScroll

      public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
          int nestedScrollAxes, int type) {
          if (parent instanceof NestedScrollingParent2) {
               return ((NestedScrollingParent2) parent).onStartNestedScroll(child, target,
            nestedScrollAxes, type);
           } else if (type == ViewCompat.TYPE_TOUCH) {
                if (Build.VERSION.SDK_INT >= 21) {
                      try {
                         return parent.onStartNestedScroll(child, target, nestedScrollAxes);
                      } catch (AbstractMethodError e) {
                 }
            } else if (parent instanceof NestedScrollingParent) {
                  return ((NestedScrollingParent) parent).onStartNestedScroll(child, target,nestedScrollAxes);
            }
          }
          return false;
      }
      

      主要是調(diào)用父類的onStartNestedScroll方法。參數(shù)分別是支持嵌套滾動的父View的直接子View,啟動嵌套滾動的View,滾動方向,滾動類型。

      如果找到并成功調(diào)用onStartNestedScroll方法,這將接收嵌套滾動的父View保存到mNestedScrollingParentTouch變量。然后執(zhí)行該父View的onNestedScrollAccepted方法。

  • 總結(jié)
    按下事件,尋找可以處理嵌套滾動的父View,調(diào)用該View的onStartNestedScroll,onNestedScrollAccepted方法。
移動事件

調(diào)用dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)方法分發(fā)預(yù)滾動事件。

  • dx:x軸偏移量,由按下時坐標(biāo)減去移動過程中的坐標(biāo)。
  • dy:y軸偏移量。
  • mScrollConsumed:保存x,y軸滾動消耗的數(shù)組
  • mScrollOffset:保存滾動偏移量的數(shù)組
  • TYPE_TOUCH:觸摸事件類型

在dispatchNestedPreScroll會調(diào)用onNestedPreScroll(parent, mView, dx, dy, consumed, type)方法,consumed是一個用于保存接收嵌套滾動父View消耗的數(shù)據(jù)的數(shù)組。

如果找到并調(diào)用成功。

if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) {
    dx -= mScrollConsumed[0];
    dy -= mScrollConsumed[1];
    ...
}

dx,dy減去消耗掉的距離 。

看一下scrollByInternal(canScrollHorizontally ? dx : 0,canScrollVertically ? dy : 0, vtev)方法。

經(jīng)過一些計算,調(diào)用dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,TYPE_TOUCH)繼續(xù)分發(fā)。

繼續(xù)向下看,會調(diào)用onNestedScroll(parent, mView,dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed)方法。根據(jù)變量名可知道大概意思。

MotionEvent.ACTION_UP

抬起事件,調(diào)用 fling((int) xvel, (int) yvel))方法,執(zhí)行onNestedPreFling和onNestedFling事件,最后執(zhí)行stopNestedScroll(TYPE_TOUCH)方法。

總結(jié)

下面只是大體執(zhí)行流程,具體請自己查看源碼

  • ACTION_DOWN
    onStartNestedScroll -> onNestedScrollAccepted方法。
  • ACTION_MOVW
    onNestedPreScroll -> onNestedScroll
  • ACTION_UP
    onNestedPreFling -> onNestedFling -> stopNestedScroll

如果onStartNestedScroll方法返回false,后面的事件都不會再分發(fā)給父View。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容