NestedScrolling

来源:互联网 发布:何晓飞 滴滴辞职 知乎 编辑:程序博客网 时间:2024/05/21 23:52

 对于Android的事件分发流程,大家都知道主要有三个函数:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent,这三个函数将我们的touch事件按照一定的规则传递给相应的view,完成了整个的事件交互。但是这套事件处理机制有一个缺点:如果事件传递流程中的一个子view一旦获得了某个事件的处理权,那么事件的剩余操作都将由这个view来处理,直到下一次事件开始,父view才有机会再次加入到事件的处理过程中。


        为了弥补这个缺陷,在Android5.0系统中,官方引入了NestedScrolling机制,NestedScrolling使得子view在滑动过程中可以先将x和y轴的滑动距离交给父view处理,父view对其处理后,再将剩余滑动距离交由子view,从而实现了滑动过程中父view和子view的信息交互。


        NestedScrolling主要包括NestedScrollingParent、NestedScrollingChild两个接口和NestedScrollingParentHelper、NestedScrollingChildHelper两个工具类,我们先了解下上面的两个接口。


        NestedScrollingParent


public interface NestedScrollingParent {    public boolean onStartNestedScroll(
        View child,         View target,         int nestedScrollAxes);    public void onNestedScrollAccepted(
        View child,         View target,         int nestedScrollAxes);    public void onStopNestedScroll(View target);    public void onNestedScroll(
        View target,         int dxConsumed,         int dyConsumed,        int dxUnconsumed,         int dyUnconsumed);      public void onNestedPreScroll(
        View target,         int dx,         int dy,         int[] consumed);    public boolean onNestedFling(
        View target,         float velocityX,         float velocityY,         boolean consumed);    public boolean onNestedPreFling(
        View target,         float velocityX,         float velocityY);    public int getNestedScrollAxes();}public interface NestedScrollingChild {    public void setNestedScrollingEnabled(
        boolean enabled);    public boolean isNestedScrollingEnabled();    public boolean startNestedScroll(int axes);    public void stopNestedScroll();    public boolean hasNestedScrollingParent();    public boolean dispatchNestedScroll(
        int dxConsumed,         int dyConsumed,        int dxUnconsumed,         int dyUnconsumed,         int[] offsetInWindow);    public boolean dispatchNestedPreScroll(
        int dx,         int dy,         int[] consumed,         int[] offsetInWindow);    public boolean dispatchNestedFling(
        float velocityX,        float velocityY,         boolean consumed);    public boolean dispatchNestedPreFling(
        float velocityX,         float velocityY);}


        NestedScrollingChild接口中每个方法,NestedScrollingParent接口中大概都有相应的onXXX方法回调。


        当子view接收到事件,滚动开始后,会调用startNestedScroll,通过它的返回值得知父view是否支持嵌套滚动。


        如果父view支持嵌套滚动,那么就会调用dispatchNestedPreScroll,这个方法中的dx和dy参数表示本次滚动在x和y方向上的距离,并通过consumed参数数据获得父view消费的距离,consumed的值需要父view在相应的onNestedPreScroll方法中设置。


        在父view完成一部分滑动距离后,子view会继续滑动,并通过dispatchNestedScroll告诉父view它在x和y方向上分别消费了的距离(dxConsumed,dyConsumed)和在x和y方向上滚动的距离中还未消费的部分(dxUnConsumed,dyUnConsumed),父view通过这些信息来决定它是否还要继续滑动。


        如果有fling操作,则通过dispatchNestedFling和dispatchNestedPreFling来完成。


        最后由stopNestedScroll结束整个滑动事件。


        当然,除了上面的基本操作,还有一些辅助性的方法,比如onNestedScrollAccepted方法中可以执行一些初始化工作。

    

        整个NestedScrolling机制流程如下:

        子view-> startNestedScroll

        父view-> onStartNestedScroll、onNestedScrollAccepted

        子view-> dispatchNestedPreScroll

        父view  -> onNestedPreScroll

        子view-> dispatchNestedScroll

        父view-> onNestedScroll

        子view-> stopNestedScroll

        父view-> onStopNestedScroll


        那么NestedScrollingParentHelper、NestedScrollingChildHelper是做什么的呢?事实上,几乎所有的工作都是由这两个工具类完成的,Helper已经实现好了子view和父view交互的具体逻辑,因此我们实现NestedScrolling机制也更加简单,只需要让父view和子view实现NestedScrollingParent和NestedScrollingChild接口,然后在接口方法中调用Helper类中相应的方法。


        NestedScrollingParentHelper中的代码很简单,我们看看NestedScrollingChildHelper中的代码:


public boolean startNestedScroll(int axes) {    if (hasNestedScrollingParent()) {        // Already in progress        return true;    }    if (isNestedScrollingEnabled()) {        ViewParent p = mView.getParent();        View child = mView;        while (p != null) {            if (ViewParentCompat.onStartNestedScroll(p,                                    child,                                   mView,                                   axes)) {                mNestedScrollingParent = p;                ViewParentCompat.onNestedScrollAccepted(
                                  p,
                                  child,
                                  mView,
                                  axes);                return true;            }            if (p instanceof View) {                child = (View) p;            }            p = p.getParent();        }    }    return false;}

        startNestedScroll方法的主要工作就是就是寻找嵌套滚动的父view,如果找到就返回true,表示view使用嵌套滚动机制;如果没有找到就返回false,表示禁用嵌套机制。


public boolean dispatchNestedPreScroll(
        int dx,         int dy,         int[] consumed,         int[] offsetInWindow) {            if (isNestedScrollingEnabled()                 && mNestedScrollingParent != null) {                if (dx != 0 || dy != 0) {                int startX = 0;                int startY = 0;                if (offsetInWindow != null) {                    mView.getLocationInWindow(offsetInWindow);                    startX = offsetInWindow[0];                    startY = offsetInWindow[1];                }                if (consumed == null) {                    if (mTempNestedScrollConsumed == null) {                        mTempNestedScrollConsumed = new int[2];                    }                    consumed = mTempNestedScrollConsumed;                }                consumed[0] = 0;                consumed[1] = 0;                ViewParentCompat.onNestedPreScroll(
                    mNestedScrollingParent,  
                    mView,                     dx,                     dy,                     consumed);                if (offsetInWindow != null) {                    mView.getLocationInWindow(offsetInWindow);                    offsetInWindow[0] -= startX;                    offsetInWindow[1] -= startY;                }                return consumed[0] != 0 || consumed[1] != 0;            } else if (offsetInWindow != null) {                offsetInWindow[0] = 0;                offsetInWindow[1] = 0;            }        }        return false;}


        dispatchNestedPreScroll会先调用getLocationInWindow获取自己在父view中的位置,然后通过ViewParentCompat调用onNestedPreScroll,ViewParentCompat是一个兼容类,用来和父view进行交互,它最终会调用到父view的onNestedPreScroll方法从而让父view根据自身的逻辑进行滚动,然后子view再次调用getLocationInWindow方法获取自己的位置,并计算出偏移量,最后判断父view是否消费了距离,如果消费了,这个方法返回true,否则返回false。

        

        没有NestedScrolling机制,我们仍然可以通过自定义view来实现类似的滑动效果,但是NestedScrolling机制的出现,给了我们一个统一的标准,它使得我们代码之间的耦合性更低,而且更易于扩展。