NestedScrollingParent, NestedScrollingChild 详解

NestedScrollingParent
NestedScrollingChild
这是两个接口,  Android 就是通过这两个接口, 来实现 子View 与父View 之间的嵌套滑动

这样的嵌套滑动机制是在 Android 发布 Lollipop 之后提供的 
不过同样在Support v7 中同样支持了 

同时 RecycleView  以及 Android 5.0 以上的系统原声View 大部分都已经支持 嵌套滑动了 

ok 了解个大概  下面来看看 具体的嵌套滑动 是怎样的:
想要理解 嵌套滑动
必须, 需要理解一下几个类(接口):
NestedScrollingChild
NestedScrollingParent
NestedScrollingChildHelper
NestedScrollingParentHelper


先来看  NestedScrollingChild 接口,  顾名思义, 这个是子View 应该实现 的接口:

先看源码 

[java]  view plain  copy
  1. public interface NestedScrollingChild {  
  2.   
  3.     /** 
  4.      * 设置嵌套滑动是否可用 
  5.      * 
  6.      * @param enabled 
  7.      */  
  8.     public void setNestedScrollingEnabled(boolean enabled);  
  9.   
  10.     /** 
  11.      * 嵌套滑动是否可用 
  12.      * 
  13.      * @return 
  14.      */  
  15.     public boolean isNestedScrollingEnabled();  
  16.   
  17.     /** 
  18.      * 开始嵌套滑动, 
  19.      * 
  20.      * @param axes 表示方向 有一下两种值 
  21.      *             ViewCompat.SCROLL_AXIS_HORIZONTAL 横向哈东 
  22.      *             ViewCompat.SCROLL_AXIS_VERTICAL 纵向滑动 
  23.      */  
  24.     public boolean startNestedScroll(int axes);  
  25.   
  26.     /** 
  27.      * 停止嵌套滑动 
  28.      */  
  29.     public void stopNestedScroll();  
  30.   
  31.     /** 
  32.      * 是否有父View 支持 嵌套滑动,  会一层层的网上寻找父View 
  33.      * @return 
  34.      */  
  35.     public boolean hasNestedScrollingParent();  
  36.   
  37.     /** 
  38.      * 在处理滑动之后 调用 
  39.      * @param dxConsumed x轴上 被消费的距离 
  40.      * @param dyConsumed y轴上 被消费的距离 
  41.      * @param dxUnconsumed x轴上 未被消费的距离 
  42.      * @param dyUnconsumed y轴上 未被消费的距离 
  43.      * @param offsetInWindow view 的移动距离 
  44.      * @return 
  45.      */  
  46.     public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,  
  47.                                         int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);  
  48.   
  49.     /** 
  50.      * 一般在滑动之前调用, 在ontouch 中计算出滑动距离, 然后 调用改 方法, 就给支持的嵌套的父View 处理滑动事件 
  51.      * @param dx x 轴上滑动的距离, 相对于上一次事件, 不是相对于 down事件的 那个距离 
  52.      * @param dy y 轴上滑动的距离 
  53.      * @param consumed 一个数组, 可以传 一个空的 数组,  表示 x 方向 或 y 方向的事件 是否有被消费 
  54.      * @param offsetInWindow   支持嵌套滑动到额父View 消费 滑动事件后 导致 本 View 的移动距离 
  55.      * @return 支持的嵌套的父View 是否处理了 滑动事件 
  56.      */  
  57.     public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);  
  58.   
  59.     /** 
  60.      * 
  61.      * @param velocityX x 轴上的滑动速度 
  62.      * @param velocityY y 轴上的滑动速度 
  63.      * @param consumed 是否被消费 
  64.      * @return 
  65.      */  
  66.     public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);  
  67.   
  68.     /** 
  69.      * 
  70.      * @param velocityX x 轴上的滑动速度 
  71.      * @param velocityY y 轴上的滑动速度 
  72.      * @return 
  73.      */  
  74.     public boolean dispatchNestedPreFling(float velocityX, float velocityY);  
  75. }  

去掉了 原来的注释, 加入点自己理解的注释 

在看看  NestedScrollingParentHelper 这个类,
这个类是一个辅助类,  先来看看 子View 如何继承 NestedScrollingChild 

[java]  view plain  copy
  1. public class Child extends LinearLayout implements android.support.v4.view.NestedScrollingChild {  
  2.     public static final String TAG = "Child";  
  3.   
  4.     private NestedScrollingChildHelper mNestedScrollingChildHelper;  
  5.   
  6.     public Child(Context context, AttributeSet attrs) {  
  7.         super(context, attrs);  
  8.         mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);  
  9.     }  
  10.   
  11.     @Override  
  12.     public void setNestedScrollingEnabled(boolean enabled) {  
  13.         mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);  
  14.     }  
  15.   
  16.     @Override  
  17.     public boolean isNestedScrollingEnabled() {  
  18.         return mNestedScrollingChildHelper.isNestedScrollingEnabled();  
  19.     }  
  20.   
  21.     @Override  
  22.     public boolean startNestedScroll(int axes) {  
  23.         return mNestedScrollingChildHelper.startNestedScroll(axes);  
  24.     }  
  25.   
  26.     @Override  
  27.     public void stopNestedScroll() {  
  28.         mNestedScrollingChildHelper.stopNestedScroll();  
  29.     }  
  30.   
  31.     @Override  
  32.     public boolean hasNestedScrollingParent() {  
  33.         return mNestedScrollingChildHelper.hasNestedScrollingParent();  
  34.     }  
  35.   
  36.     @Override  
  37.     public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {  
  38.         return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);  
  39.     }  
  40.   
  41.     @Override  
  42.     public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {  
  43.         return mNestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);  
  44.     }  
  45.   
  46.     @Override  
  47.     public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {  
  48.         return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);  
  49.     }  
  50.   
  51.     @Override  
  52.     public boolean dispatchNestedPreFling(float velocityX, float velocityY) {  
  53.         return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);  
  54.     }  
  55. }  

可以看到基本接口里面的每个方法 都只要调用 mNestedScrollingChildHelper 中相应的方法;
下面来看看  NestedScrollingParentHelper 源码: 
[java]  view plain  copy
  1. public class NestedScrollingChildHelper {  
  2.     /** 
  3.      * 嵌套滑动的ziView 
  4.      */  
  5.     private final View mView;  
  6.   
  7.     /** 
  8.      * 支持 嵌套滑动的 父View 
  9.      */  
  10.     private ViewParent mNestedScrollingParent;  
  11.   
  12.     /** 
  13.      * 是否支持 嵌套滑动 
  14.      */  
  15.     private boolean mIsNestedScrollingEnabled;  
  16.   
  17.     /** 
  18.      * 是否被消费的一个中变变量 
  19.      */  
  20.     private int[] mTempNestedScrollConsumed;  
  21.   
  22.     public NestedScrollingChildHelper(View view) {  
  23.         mView = view;  
  24.     }  
  25.   
  26.     public void setNestedScrollingEnabled(boolean enabled) {  
  27.         if (mIsNestedScrollingEnabled) {  
  28.             ViewCompat.stopNestedScroll(mView);  
  29.         }  
  30.         mIsNestedScrollingEnabled = enabled;  
  31.     }  
  32.   
  33.     public boolean isNestedScrollingEnabled() {  
  34.         return mIsNestedScrollingEnabled;  
  35.     }  
  36.   
  37.     public boolean hasNestedScrollingParent() {  
  38.         return mNestedScrollingParent != null;  
  39.     }  
  40.   
  41.     /** 
  42.      * 开始嵌套滑动 
  43.      * @param axes 滑动方向 
  44.      * @return 是否有父view 支持嵌套滑动 
  45.      */  
  46.     public boolean startNestedScroll(int axes) {  
  47.         if (hasNestedScrollingParent()) {  
  48.             // 如果已经找到 了嵌套滑动的父View  
  49.             // Already in progress  
  50.             return true;  
  51.         }  
  52.         if (isNestedScrollingEnabled()) {  
  53.             ViewParent p = mView.getParent();  
  54.             View child = mView;  
  55.             // 递归向上寻找 支持 嵌套滑动的父View  
  56.             while (p != null) {  
  57.                 // 这里会调用 父View 的NestedScrollingParent.onStartNestedScroll 方法  
  58.                 // 如果 父View 返回 false  则再次向上寻找父View , 直到找到支持的fuView  
  59.                 if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {  
  60.                     mNestedScrollingParent = p;  
  61.                     // 这里回调 父View 的onNestedScrollAccepted 方法 表示开始接收 嵌套滑动  
  62.                     ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);  
  63.                     return true;  
  64.                 }  
  65.                 if (p instanceof View) {  
  66.                     child = (View) p;  
  67.                 }  
  68.                 p = p.getParent();  
  69.             }  
  70.         }  
  71.         // 没有找到 支持嵌套滑动的父View  则返回false  
  72.         return false;  
  73.     }  
  74.   
  75.     /** 
  76.      * 停止 嵌套滑动, 一般 在 cancel up 事件中 调用 
  77.      */  
  78.     public void stopNestedScroll() {  
  79.         if (mNestedScrollingParent != null) {  
  80.             ViewParentCompat.onStopNestedScroll(mNestedScrollingParent, mView);  
  81.             mNestedScrollingParent = null;  
  82.         }  
  83.     }  
  84.   
  85.     /** 
  86.      * 
  87.      * @param dxConsumed  x 上被消费的距离 
  88.      * @param dyConsumed  y 上被消费的距离 
  89.      * @param dxUnconsumed  x 上未被消费的距离 
  90.      * @param dyUnconsumed  y 上未被消费的距离 
  91.      * @param offsetInWindow  子View 位置的移动距离 
  92.      * @return 
  93.      */  
  94.     public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,  
  95.                                         int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {  
  96.         if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {  
  97.             if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {  
  98.                 int startX = 0;  
  99.                 int startY = 0;  
  100.                 if (offsetInWindow != null) {  
  101.                     mView.getLocationInWindow(offsetInWindow);  
  102.                     startX = offsetInWindow[0];  
  103.                     startY = offsetInWindow[1];  
  104.                 }  
  105.   
  106.                 // 父View 回调 onNestedScroll 方法, 该放在 主要会处理  dxUnconsumed dyUnconsumed 数据  
  107.                 ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed,  
  108.                         dyConsumed, dxUnconsumed, dyUnconsumed);  
  109.   
  110.                 if (offsetInWindow != null) {  
  111.                     // 计算 子View的移动距离  
  112.                     mView.getLocationInWindow(offsetInWindow);  
  113.                     offsetInWindow[0] -= startX;  
  114.                     offsetInWindow[1] -= startY;  
  115.                 }  
  116.                 return true;  
  117.             } else if (offsetInWindow != null) {  
  118.                 // No motion, no dispatch. Keep offsetInWindow up to date.  
  119.                 offsetInWindow[0] = 0;  
  120.                 offsetInWindow[1] = 0;  
  121.             }  
  122.         }  
  123.         return false;  
  124.     }  
  125.   
  126.     /** 
  127.      * 
  128.      * consumed[0]  为0 时 表示 x 轴方向上事件 没有被消费 
  129.      *              不为0 时 表示 x 轴方向上事件 被消费了, 值表示 被消费的滑动距离 
  130.      * consumed[1]  为0 时 表示 y 轴方向上事件 没有被消费 
  131.      *              不为0 时 表示 y 轴方向上事件 被消费了, 值表示 被消费的滑动距离 
  132.      * 
  133.      * 
  134.      * @param dx 
  135.      * @param dy 
  136.      * @param consumed 
  137.      * @param offsetInWindow 
  138.      * @return 
  139.      */  
  140.     public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {  
  141.         if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {  
  142.             if (dx != 0 || dy != 0) {  
  143.                 int startX = 0;  
  144.                 int startY = 0;  
  145.                 // 获取 当前View 初始位置  
  146.                 if (offsetInWindow != null) {  
  147.                     mView.getLocationInWindow(offsetInWindow);  
  148.                     startX = offsetInWindow[0];  
  149.                     startY = offsetInWindow[1];  
  150.                 }  
  151.   
  152.                 // 初始化是否被消费数据  
  153.                 if (consumed == null) {  
  154.                     if (mTempNestedScrollConsumed == null) {  
  155.                         mTempNestedScrollConsumed = new int[2];  
  156.                     }  
  157.                     consumed = mTempNestedScrollConsumed;  
  158.                 }  
  159.                 consumed[0] = 0;  
  160.                 consumed[1] = 0;  
  161.   
  162.                 // 这里回调 父View 的 onNestedPreScroll 方法,  
  163.                 // 父View 或许会处理 相应的滑动事件,  
  164.                 // 如果 处理了 则 consumed 会被赋予 相应的值  
  165.                 ViewParentCompat.onNestedPreScroll(mNestedScrollingParent, mView, dx, dy, consumed);  
  166.   
  167.                 if (offsetInWindow != null) {  
  168.                     // 父View 处理了相应的滑动,  很可能导致 子View 的位置的移动  
  169.                     // 这里计算出  父view 消费 滑动事件后,  导致 子View 的移动距离  
  170.                     mView.getLocationInWindow(offsetInWindow);  
  171.                     // 这里 子View 的移动距离  
  172.                     offsetInWindow[0] -= startX;  
  173.                     offsetInWindow[1] -= startY;  
  174.                 }  
  175.                 // 如果  xy 方向 上 有不为0 的表示消费了 则返回true  
  176.                 return consumed[0] != 0 || consumed[1] != 0;  
  177.             } else if (offsetInWindow != null) {  
  178.                 offsetInWindow[0] = 0;  
  179.                 offsetInWindow[1] = 0;  
  180.             }  
  181.         }  
  182.         return false;  
  183.     }  
  184.   
  185.     public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {  
  186.         if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {  
  187.             return ViewParentCompat.onNestedFling(mNestedScrollingParent, mView, velocityX,  
  188.                     velocityY, consumed);  
  189.         }  
  190.         return false;  
  191.     }  
  192.   
  193.     public boolean dispatchNestedPreFling(float velocityX, float velocityY) {  
  194.         if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {  
  195.             return ViewParentCompat.onNestedPreFling(mNestedScrollingParent, mView, velocityX,  
  196.                     velocityY);  
  197.         }  
  198.         return false;  
  199.     }  
  200.   
  201.     public void onDetachedFromWindow() {  
  202.         ViewCompat.stopNestedScroll(mView);  
  203.     }  
  204.   
  205.     public void onStopNestedScroll(View child) {  
  206.         ViewCompat.stopNestedScroll(mView);  
  207.     }  
  208. }  

从上面的注释 可以基本看到 嵌套滑动的基本逻辑:

下面来看看 父View 
[java]  view plain  copy
  1. public class Parent extends LinearLayout implements NestedScrollingParent {  
  2.     public static final String TAG = "Parent";  
  3.   
  4.     private NestedScrollingParentHelper mNestedScrollingParentHelper;  
  5.   
  6.     public Parent(Context context, AttributeSet attrs) {  
  7.         super(context, attrs);  
  8.         mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);  
  9.     }  
  10.   
  11.     /** 
  12.      * 回调开始滑动 
  13.      * @param child 该父VIew 的子View 
  14.      * @param target 支持嵌套滑动的 VIew 
  15.      * @param nestedScrollAxes 滑动方向 
  16.      * @return 是否支持 嵌套滑动 
  17.      */  
  18.     @Override  
  19.     public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {  
  20.         return true;  
  21.     }  
  22.   
  23.     @Override  
  24.     public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {  
  25.         mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);  
  26.     }  
  27.   
  28.     @Override  
  29.     public void onStopNestedScroll(View target) {  
  30.         mNestedScrollingParentHelper.onStopNestedScroll(target);  
  31.     }  
  32.   
  33.     /** 
  34.      * 这里 主要处理 dyUnconsumed dxUnconsumed 这两个值对应的数据 
  35.      * @param target 
  36.      * @param dxConsumed 
  37.      * @param dyConsumed 
  38.      * @param dxUnconsumed 
  39.      * @param dyUnconsumed 
  40.      */  
  41.     @Override  
  42.     public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {  
  43.         LogUtil.d(TAG, "onNestedScroll target = " + target + " , dxConsumed = " + dxConsumed + " , dyConsumed = " + dyConsumed + " , dxUnconsumed = " + dxUnconsumed + " , dyUnconsumed = " + dyUnconsumed);  
  44.     }  
  45.   
  46.     /** 
  47.      * 这里 传来了 x y 方向上的滑动距离 
  48.      * 并且 先与 子VIew  处理滑动,  并且 consumed  中可以设置相应的 除了的距离 
  49.      * 然后 子View  需要更具这感觉, 来处理自己滑动 
  50.      * 
  51.      * @param target 
  52.      * @param dx 
  53.      * @param dy 
  54.      * @param consumed 
  55.      */  
  56.     @Override  
  57.     public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {  
  58.   
  59.         consumed[1] = dy;  
  60.         LogUtil.d(TAG, "onNestedPreScroll dx = " + dx + " dy = " + dy);  
  61.     }  
  62.   
  63.     @Override  
  64.     public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {  
  65.         return false;  
  66.     }  
  67.   
  68.     @Override  
  69.     public boolean onNestedPreFling(View target, float velocityX, float velocityY) {  
  70.         return false;  
  71.     }  
  72.   
  73.     @Override  
  74.     public int getNestedScrollAxes() {  
  75.         return mNestedScrollingParentHelper.getNestedScrollAxes();  
  76.     }  
  77. }  


总结一下  整个嵌套滑动的流程是:

子view父view
startNestedScrollonStartNestedScroll、onNestedScrollAccepted
dispatchNestedPreScrollonNestedPreScroll
dispatchNestedScrollonNestedScroll
stopNestedScrollonStopNestedScroll

通过以上的代码 注释 基本对 嵌套互动有了一个大致的基本了解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值