簡介
這篇文章講解Android嵌套滾動機(jī)制,像滑動實現(xiàn)頂部懸停,SDK中的SwipeRefreshLayout下拉刷新組件都用到了嵌套滾動機(jī)制。下面將以一個最簡單的模型來分析嵌套滾動的執(zhí)行流程,滾動事件的消耗邏輯。
模型
-
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);- nestedScrollAxis:是橫軸滾動還是豎軸滾動
- TYPE_TOUCH:0,觸摸事件類型
創(chuàng)建一個NestedScrollingChildHelper幫助類來分發(fā)public boolean startNestedScroll(int axes, int type) { return getScrollingChildHelper().startNestedScroll(axes, type); }
-
判斷是否存在嵌套滾動父類且正處于嵌套滾動事件中,是者直接返回。
if (hasNestedScrollingParent(type)) {return true;}
-
嵌套滾動使能,獲取當(dāng)前View(即RecyclerView)的父View
if (isNestedScrollingEnabled()) { ViewParent p = mView.getParent(); View child = mView; ... }
-
分發(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。