Android CoordinatorLayout和Behavior的源码分析(三)

二、 嵌套滑动机制(NestedScroll)

上面看了Col的使用和自定义Behavior,下面我们来说一下这个效果实现原理,这里加入NestedScroll这个概念,是因为Col实质也是通过NestedScroll实现的各种效果。

在Android嵌套滑动机制出现之前,Android UI的滑动的组件有ListView和ScrollView,ViewPager就会有很多需求是要:ListView和ScrollView自己嵌套,或者互相嵌套。这时经常会遇到一些问题:

  • 点击事件被屏蔽
  • view的滑动不流畅
  • view的滑动条件不符合设计

图片1 滑动图片

以上面图片滑动的效果为例,看到parent滑动到一定距离后,子view开始滑动,中间连贯不间断。我们分别从传统事件分发机制和NestedScroll机制对比去看怎么处理:

按照传统的事件分发机制:

开始我们滑动的是子view的内容区域,移动却是外部的Parent,这种实现一定是外部的Parent拦截了内部的子view事件,那么这次Down-->Move–>Move–>Up的所有事件都会交给Parent处理。

不了解事件分发机制的,可以参考:http://www.cnblogs.com/linjzong/p/4191891.html

NestedScroll机制:

子view开始滚动时,将dx,dy交给NestedScrollingParent,NestedScrollingParent进行部分消耗,剩余的还给内部的子view。

那么我分三步详细看下NestedScroll机制怎么实现的上面效果:

1 NestedScrollingParent都提供了什么方法?

2 parent如何实现 NestedScrollingParent这个接口?

3 子view如何拦截了事件并回调NestedScrollingParent的各种方法?

1.NestedScrollingParent接口

public interface NestedScrollingParent {


     /**
     * 有嵌套滑动到来了,问下该parent是否接受嵌套滑动
     *
     * @param child parent的其它子view
     * @param target parent中滑动的子View
     * @param nestedScrollAxes 支持嵌套滚动轴。水平方向,垂直方向,或者不指定
     * @return 是否接受该嵌套滑动
     */
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
    ...

    /**
     * 子view在未滑动之前传过来的准备滑动情况
     *
     * @param dx 水平方向子view想要滑动的距离
     * @param dy 垂直方向子view想要滑动的距离
      * @param consumed 告诉子view,当前父view消耗的距离:consumed[0] 水平消耗的距离,consumed[1] 垂直消耗的距离
     */
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
    /**
     * 子view在fling之后传过来的fling情况
     * @param velocityX 水平方向速度
     * @param velocityY 垂直方向速度
      * @param consumed 子view是否fling
      * @return true parent是否消耗了fling
     */
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
    ...

}

NestedScrollingParent一共提供了8个方法,这里我们只说上面3个:

onStartNestedScroll方法:

改方法决定Parent是否能接受到其内部子view(子view可以不是直接子view)滑动时的参数,假如只涉及到垂直方向滑动的嵌套,nestedScrollAxes指定垂直方向。

onNestedPreScroll方法:

传入子view移动的dx,dy,如果parent需要消耗部分dx,dy,就通过参数consumed进行指定,例如,parent消耗一半的dy,就可以写consumed[1]= dy/2。

onNestedFling方法:

传入子view的fling事件,return true:parent拦截子view的fling,false:不拦截

2. Parent实现NestedScrollingParent

public class ParentLauyout extends LinearLayout implements NestedScrollingParent
{
    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes)
    {
        return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }
    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed)
    {
        boolean hiddenTop = dy > 0 && getScrollY() < mTopViewHeight;
        boolean showTop = dy < 0 && getScrollY() > 0 && !ViewCompat.canScrollVertically(target, -1);

        if (hiddenTop || showTop)
        {
            scrollBy(0, dy);
            consumed[1] = dy;
        }
    }
    ...
}

onStartNestedScroll方法:
在什么方向上我们选择接受嵌套滑动,和子view配合处理滑动。

onNestedPreScroll方法:
我们判断,如果是上滑且顶部控件未完全隐藏,则消耗掉dy,即consumed[1]=dy;如果是下滑且内部View已经无法继续下拉,则消耗掉dy,即consumed[1]=dy,消耗掉的意思,就是自己去执行scrollBy,实际上就是我们的ParentLauyout滑动。

3. 子view回调的NestedScrollingParent的各种方法

知道了这些,我们去看下子view如何拦截的事件:(这里以NestedscrollView为例)

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    ...
    case MotionEvent.ACTION_DOWN: {
            final int y = (int) ev.getY();
            if (!inChild((int) ev.getX(), (int) y)) {
                mIsBeingDragged = false;
                recycleVelocityTracker();
                break;
            }

            mLastMotionY = y;
            mActivePointerId = MotionEventCompat.getPointerId(ev, 0);

            initOrResetVelocityTracker();
            mVelocityTracker.addMovement(ev);

            mScroller.computeScrollOffset();
            mIsBeingDragged = !mScroller.isFinished();
            startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
            break;
   }
    ...
   return mIsBeingDragged;
}

在ACTION_DOWN时,调用startNestedScroll

@Override
public boolean startNestedScroll(int axes) {
    return mChildHelper.startNestedScroll(axes);
}

调用的是NestedScrollingChildHelper的startNestedScroll:

public boolean startNestedScroll(int axes) {
   if (hasNestedScrollingParent()) {
       // Already in progress
       return true;
   }
   if (isNestedScrollingEnabled()) {
       ViewParent p = mView.getParent();
       View child = mView;
       while (p != null) {
           //循环向上寻找parent中在onStartNestedScroll方法中返回true(嵌套滑动到来时,该parent接受嵌套滑动)
           if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
               mNestedScrollingParent = p;
               ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
               return true;
           }
           if (p instanceof View) {

I }
p = p.getParent();
}
}
return false;
}

从子view循环向上层找parent中是不是有实现了NestedScrollingParent接口的,找到这个与之合作的父父…父view的时候,并且通过该parent对应的onStartNestedScroll方法返回的true(接受嵌套滚动)或者 false(不接受嵌套滚动)。

这样子view通过回调父view的NestedScrollingParent方法可以获知父view情况~

以上总结:

  • parent 实现 NestedScrollingParent接口, 子view 实现 NestedScrollingChild接口,这两个接口 有两个辅助类NestedScrollingParentHelper(NestedScrollingChildHelper)来帮助实现parent和child的交互逻辑。

  • 滑动动作是Child主动发起,Parent收到滑动回调并作出相应。

  • 滑动开始后,child调用startNestedScroll(),Parent 收到 onStartNestedScroll() 回调,决定是否需要配合 Child 一起进行处理滑动,如果需要配合,还会回调 onNestedScrollAccepted()。

  • 每次滑动前,Child 先询问 Parent 是否需要滑动,即 dispatchNestedPreScroll(),这就回调到 Parent 的 onNestedPreScroll(),Parent 可以在这个回调中“劫持”掉 Child 的滑动,也就是先于 Child 滑动。

  • Child 滑动以后,会调用 onNestedScroll(),回调到 Parent 的 onNestedScroll(),这里就是 Child 滑动后,剩下的给 Parent 处理,也就是 后于 Child 滑动。

  • 最后,滑动结束,调用 onStopNestedScroll() 表示本次处理结束。

详细了解嵌套滑动机制中的NestedScrollingParent和NestedScrollingChild参考资料:http://blog.csdn.net/x87648510/article/details/51882040

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值