NestedScrolling解析

1.什么是NestedScrolling?

NestedScrolling(嵌套滑动机制)是Google在Android 5.0版本之后提供的一种新的滑动特性, 是一种嵌套view之间嵌套滑动的互动机制, 更方便于实现复杂的嵌套滑动效果。

2.为什么会有NestedScrolling?

view的touch事件分发机制是自上而下的, 主要是通过这几个函数来处理:dispatchTouchEvent一一传递,onInterceptTouchEvent一一拦截、onTouchEvent一一消费
这种机制有个痛点:

touch事件 要么被 父View 处理,要么被 子View 处理,很难在两者之间进行交互处理。

但Google提供的NestedScrolling机制能够很好地解决这个问题。

3.NestedScrolling的实现分析

NestedScrolling实现主要依赖以下四个类:
** NestedScrollingChild **
** NestedScrollingParent **
** NestedScrollingChildHelper**
** NestedScrollingParentHelper**
NestedScrollingChild 和 NestedScrollingParent 是接口类, NestedScrollingParent 和 NestedScrollingParentHelper则是嵌套父view和子view嵌套滑动交互的辅助类, 主要是实现了嵌套父view与子view之间的交互。所以想要实现NestedScrolling,
嵌套子view需实现NestedScrollingChild 接口, 将相关事件委托给NestedScrollingChildHelper处理
嵌套父view需实现NestedScrollingParent 接口, 将滑动事件委托给NestedScrollingParentHelper处理。
NestedScrollingChild

    //设置是否支持嵌套滑动
    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
        super.setNestedScrollingEnabled(enabled);
        mChildHelper.setNestedScrollingEnabled(enabled);
    }

    //嵌套滑动是否可用
    @Override
    public boolean isNestedScrollingEnabled() {
        return mChildHelper.isNestedScrollingEnabled();
    }

   //设置嵌套滑动方向
    @Override
    public boolean startNestedScroll(int axes) {
        return mChildHelper.startNestedScroll(axes);
    }
    
   //停止嵌套滑动
    @Override
    public void stopNestedScroll() {
        mChildHelper.stopNestedScroll();
    }

    //父View是否支持嵌套滑动
    @Override
    public boolean hasNestedScrollingParent() {
        return mChildHelper.hasNestedScrollingParent();
    }

   //处理滑动事件
    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) 
        return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
    }

    //处理滑动事件前的准备动作
    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
    }

     //处理滑行事件
    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
    }

    //滑行前的准备动作
    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
    }

NestedScrollingParent

   
    //是否允许嵌套滑动
    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        return super.onStartNestedScroll(child, target, nestedScrollAxes);
    }

    //onStartNestedScroll()方法返回true会调用该函数
    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
        super.onNestedScrollAccepted(child, target, axes);
    }

    //嵌套滑动停止
    @Override
    public void onStopNestedScroll(View target) {
        super.onStopNestedScroll(target);
    }

    //子View的嵌套滑动事件
    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    }

   //子View嵌套滑动前的准备动作
    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(target, dx, dy, consumed);
    }

    //嵌套滑动子View的滑行情况
    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        return super.onNestedFling(target, velocityX, velocityY, consumed);
    }

    //嵌套滑动子View滑行前的准备动作
    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        return super.onNestedPreFling(target, velocityX, velocityY);
    }

    //嵌套滑动方向
    @Override
    public int getNestedScrollAxes() {
        return super.getNestedScrollAxes();
    }

4.NestedScrolling工作流程

在NestedScrolling(嵌套滑动机制)中, 嵌套子View是事件发起者, 嵌套父View则对事件进行响应处理和结果回调。工作流程如下:

4.1 startNestedScroll

子View调用startNestedScroll方法开启嵌套滑动, 寻找支持NestedScrolling的父View, 通知父View开始嵌套滑动,这时父View的onStartNestedScroll和onNestedScrollAccepted会被调用。
具体实现我们看一下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;
 }

如果父View的onStartNestedScroll返回true,表示接受此次的嵌套滑动, 会继续执行onNestedScrollAccepted方法;
如果返回false则表示不想处理此次的滑动, 那么子View就会继续向上寻找支持滑动的父View。

4.2 dispatchNestedPreScroll

滑动开始前, 子View会调用dispatchNestedPreScroll方法回调父View的onNestedPreScroll方法, 询问父View是否需要进行滑动处理, 如果父View进行了滑动处理, 会将消费的距离信息通过consumed回传给子View, 子View根据父View的消费情况来决定自己的滑动处理。 如果父View不需要滑动处理,则子View自己进行滑动处理。

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;
 }

4.3 dispatchNestedScroll

子view滑动后,可调用dispatchNestedScroll将自己的滑动情况告诉父View, 父View的onNestedScroll会被回调,如果父view接受了它的滚动参数,进行了部分消费,则这个函数返回true,否则为false
ps: dispatchNestedFling 与dispatchNestedScroll类似

4.4 stopNestedScroll

子View调用stopNestedScroll结束整个滑动流程, 父view的onStopNestedScroll方法被回调结束滑动

子view 父view
startNestedScroll onStartNestedScroll、onNestedScrollAccepted
dispatchNestedPreScroll onNestedPreScroll
dispatchNestedScroll onNestedScroll
dispatchNestedFling onNestedFling
stopNestedScroll onStopNestedScroll

你可能感兴趣的:(NestedScrolling解析)