简介
这篇文章讲解Android嵌套滚动机制,像滑动实现顶部悬停,SDK中的SwipeRefreshLayout下拉刷新组件都用到了嵌套滚动机制。下面将以一个最简单的模型来分析嵌套滚动的执行流程,滚动事件的消耗逻辑。
模型
-
StickyNavLayoutTest
StickyNavLayoutTest是一个实现了NestedScrollingParent全部方法的类。ViewGroup也实现了,里面有一些分发嵌套滚动事件的逻辑。一会分析。
分发
RecyclerView拦截触摸事件,看一下RecyclerView怎么处理的。定位到RecyclerView的onTouchEvent方法。
按下事件
- 设置嵌套滚动的偏移量
mNestedOffsets[0] = mNestedOffsets[1] = 0; - 修正坐标
vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]); - 分发嵌套滚动开始事件
startNestedScroll(nestedScrollAxis, TYPE_TOUCH);- nestedScrollAxis:是横轴滚动还是竖轴滚动
- TYPE_TOUCH:0,触摸事件类型
创建一个NestedScrollingChildHelper帮助类来分发public boolean startNestedScroll(int axes, int type) { return getScrollingChildHelper().startNestedScroll(axes, type); }
-
判断是否存在嵌套滚动父类且正处于嵌套滚动事件中,是者直接返回。
if (hasNestedScrollingParent(type)) {return true;}
-
嵌套滚动使能,获取当前View(即RecyclerView)的父View
if (isNestedScrollingEnabled()) { ViewParent p = mView.getParent(); View child = mView; ... }
-
分发给父View
while循环找到支持嵌套滚动的父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; }
主要是调用父类的onStartNestedScroll方法。参数分别是支持嵌套滚动的父View的直接子View,启动嵌套滚动的View,滚动方向,滚动类型。
如果找到并成功调用onStartNestedScroll方法,这将接收嵌套滚动的父View保存到mNestedScrollingParentTouch变量。然后执行该父View的onNestedScrollAccepted方法。
- 总结
按下事件,寻找可以处理嵌套滚动的父View,调用该View的onStartNestedScroll,onNestedScrollAccepted方法。
移动事件
调用dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)方法分发预滚动事件。
- dx:x轴偏移量,由按下时坐标减去移动过程中的坐标。
- dy:y轴偏移量。
- mScrollConsumed:保存x,y轴滚动消耗的数组
- mScrollOffset:保存滚动偏移量的数组
- TYPE_TOUCH:触摸事件类型
在dispatchNestedPreScroll会调用onNestedPreScroll(parent, mView, dx, dy, consumed, type)方法,consumed是一个用于保存接收嵌套滚动父View消耗的数据的数组。
如果找到并调用成功。
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) {
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
...
}
dx,dy减去消耗掉的距离 。
看一下scrollByInternal(canScrollHorizontally ? dx : 0,canScrollVertically ? dy : 0, vtev)方法。
经过一些计算,调用dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,TYPE_TOUCH)继续分发。
继续向下看,会调用onNestedScroll(parent, mView,dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed)方法。根据变量名可知道大概意思。
MotionEvent.ACTION_UP
抬起事件,调用 fling((int) xvel, (int) yvel))方法,执行onNestedPreFling和onNestedFling事件,最后执行stopNestedScroll(TYPE_TOUCH)方法。
总结
下面只是大体执行流程,具体请自己查看源码
- ACTION_DOWN
onStartNestedScroll -> onNestedScrollAccepted方法。 - ACTION_MOVW
onNestedPreScroll -> onNestedScroll - ACTION_UP
onNestedPreFling -> onNestedFling -> stopNestedScroll
如果onStartNestedScroll方法返回false,后面的事件都不会再分发给父View。