上一次说到,一般很少有behavior去重写behavior.onTouchEvent和behavior.onInterceptTouchEvent方法。那么其实我们可以直接忽略这一套流程,直接当他是正常的事件分发啦。
那么现在模拟这么一个情况,我们手指滑动NestedScrollView的项(设定其xml属性:app:layout_behavior=”@string/appbar_scrolling_view_behavior”),
然后AppbarLayout随之滚动。这是怎么发生的呢?
如果是正常的事件分发流程,我们会到达NestedScrollView的onInterceptTouchEvent 然后是onTouchEvent
onInterceptTouchEvent:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
//mIsBeingDragged 关键标志位。如果已经进入滚动状态
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
return true;
}
switch (action & MotionEventCompat.ACTION_MASK) {
case......
..............
这个方法主要是判定是否要拦截垂直滚动的事件给自己消费,如果真的要拦截就返回true。 具体的内部判断就不粘贴了,因为不是今天的主角,大体上看过去,ACTION_MOVE 的话,如果超过一个y轴分量阈值那么就可以判定进入了滚动状态。ACTION_DOWN的话,如果当前滚动动画还没有结束(比如手指fling操作),那么现在按下去的话也算是一种拖动了(这里还不是很确定)
然后如果真的是上下拖动并在onInterceptTouchEvent 里头返回了true,事件流就跑到这个类的onTouchEvent方法内了。
onTouchEvent内部也是一个Switch case来分别处理不同类型的事件
自然的先从 ACTION_DOWN 开始
关键的地方出现了
....
case MotionEvent.ACTION_DOWN: {
....
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
.....
}
....
好了主角来了startNestedScroll
@Override
public boolean startNestedScroll(int axes) {
return mChildHelper.startNestedScroll(axes);
}
跟进去
public boolean startNestedScroll(int axes) {
if (hasNestedScrollingParent()) {
// Already in progress
return true;
}
if (isNestedScrollingEnabled()) {
ViewParent p = mView.getParent();//得到父view
View child = mView;
while (p != null) {
if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {//回调父类的onStartNestedScroll方法!
mNestedScrollingParent = p;
ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);//回调父类的onNestedScrollAccepted方法!
return true;
}
if (p instanceof View) {
child = (View) p;
}
p = p.getParent();//如果回调失败,说明不是CoordinateLayout,那么继续向上找
}
}
return false;
}
其实吧ViewGroup里头也有onStartNestedScroll和onNestedScrollAccepted方法,不过默认返回为false,而CoordinateLayout对他们进行了相应的重写。所以如果调用的父view返回false,那么
一种可能是这个父view不是CoordinateLayout,那么就继续沿着树向上找
另一个可能是这个父view是CoordinateLayout,但是这个父view的子view的behavior全都没有接受这个事件,那么也继续向上看看有没有更高层级的CoordinateLayout来接收
那么接下来就去CoordinateLayout看看实现
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
boolean handled = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
nestedScrollAxes);
handled |= accepted;
lp.acceptNestedScroll(accepted);
//在子view的lp里头记录这个子view的behavior是否接受了事件!以后就靠着这个标志位来分发接下来的流程了!
} else {
lp.acceptNestedScroll(false);
//记录子view的behaviior并没有接受这个事件
}
}
return handled;
}
Coor依次调用了所有子view的behavior(如果有的话)的相应的onStartNestedScroll方法,并且返回是否至少一个behavior处理了这个事件
。
忽然想到了一个很好的比喻,这里Coor起了一个类似集线器的作用,各个子view相当于连在上头的电脑,他们整体构成一个星型拓扑的局域网结构。Coor负责转发某台电脑的请求给所有局域网内的连接。至于那台电脑要如何处理这个请求那就是电脑自己的事情。
继续看:
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
mNestedScrollingDirectChild = child;
mNestedScrollingTarget = target;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
//如果在lp记录的是不接受事件,那直接continue跳过就好了
if (!lp.isNestedScrollAccepted()) {
continue;
}
// 如果刚才lp记录的是接受
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
//进一步调用子view的onNestedScrollAccepted
viewBehavior.onNestedScrollAccepted(this, view, child, target, nestedScrollAxes);
}
}
}
好了回到NestedScroll的onTouchEvent,刚才说完了ACTION_DOWN,现在自然是来看ACTION_MOVE 了
case MotionEvent.ACTION_MOVE:{
....
dispatchNestedPreScroll
...
dispatchNestedScroll
...
...
}
后面的套路其实和之前的onNestedScrollAccepted都差不多,同样也是会回调父view的Coor的相应的方法,然后Coor在相应方法里头遍历所有子view,首先检查子view的lp是否接受了这个事件流,如果是,就接着对子view的behavior进行相应的回调。就不罗嗦太多了
同理我们可以分析Recyclerview,在RecyclerView的onTouchEvent方法里头同样的也是用startNestedScroll 来完成上面的一系列动作,思路类似,也不继续分析了