关于 NestedScrollView 和CoordinateLayout的交互 以及CoordinateLayout的分发

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/pczjzwok/article/details/50979828

上一次说到,一般很少有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 来完成上面的一系列动作,思路类似,也不继续分析了

展开阅读全文

没有更多推荐了,返回首页