NestedScrollingParent, NestedScrollingChild 详解

来源:互联网 发布:java date sethour 编辑:程序博客网 时间:2024/05/01 05:07
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父viewstartNestedScrollonStartNestedScroll、onNestedScrollAccepteddispatchNestedPreScrollonNestedPreScrolldispatchNestedScrollonNestedScrollstopNestedScrollonStopNestedScroll

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

0 0