NestedScrollingParent, NestedScrollingChild详解

    xiaoxiao2021-12-14  18

    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 public interface NestedScrollingChild {          /**       * 设置嵌套滑动是否可用       *       * @param enabled       */       public void setNestedScrollingEnabled(boolean enabled);          /**       * 嵌套滑动是否可用       *       * @return       */       public boolean isNestedScrollingEnabled();          /**       * 开始嵌套滑动,       *       * @param axes 表示方向 有一下两种值       *             ViewCompat.SCROLL_AXIS_HORIZONTAL 横向哈东       *             ViewCompat.SCROLL_AXIS_VERTICAL 纵向滑动       */       public boolean startNestedScroll(int axes);          /**       * 停止嵌套滑动       */       public void stopNestedScroll();          /**       * 是否有父View 支持 嵌套滑动,  会一层层的网上寻找父View       * @return       */       public boolean hasNestedScrollingParent();          /**       * 在处理滑动之后 调用       * @param dxConsumed x轴上 被消费的距离       * @param dyConsumed y轴上 被消费的距离       * @param dxUnconsumed x轴上 未被消费的距离       * @param dyUnconsumed y轴上 未被消费的距离       * @param offsetInWindow view 的移动距离       * @return       */       public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,                                           int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);          /**       * 一般在滑动之前调用, 在ontouch 中计算出滑动距离, 然后 调用改 方法, 就给支持的嵌套的父View 处理滑动事件       * @param dx x 轴上滑动的距离, 相对于上一次事件, 不是相对于 down事件的 那个距离       * @param dy y 轴上滑动的距离       * @param consumed 一个数组, 可以传 一个空的 数组,  表示 x 方向 或 y 方向的事件 是否有被消费       * @param offsetInWindow   支持嵌套滑动到额父View 消费 滑动事件后 导致 本 View 的移动距离       * @return 支持的嵌套的父View 是否处理了 滑动事件       */       public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);          /**       *       * @param velocityX x 轴上的滑动速度       * @param velocityY y 轴上的滑动速度       * @param consumed 是否被消费       * @return       */       public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);          /**       *       * @param velocityX x 轴上的滑动速度       * @param velocityY y 轴上的滑动速度       * @return       */       public boolean dispatchNestedPreFling(float velocityX, float velocityY);   }  

    去掉了 原来的注释, 加入点自己理解的注释  在看看  NestedScrollingParentHelper 这个类, 这个类是一个辅助类,  先来看看 子View 如何继承 NestedScrollingChild 

    [java]  view plain  copy public class Child extends LinearLayout implements android.support.v4.view.NestedScrollingChild {       public static final String TAG = "Child";          private NestedScrollingChildHelper mNestedScrollingChildHelper;          public Child(Context context, AttributeSet attrs) {           super(context, attrs);           mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);       }          @Override       public void setNestedScrollingEnabled(boolean enabled) {           mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);       }          @Override       public boolean isNestedScrollingEnabled() {           return mNestedScrollingChildHelper.isNestedScrollingEnabled();       }          @Override       public boolean startNestedScroll(int axes) {           return mNestedScrollingChildHelper.startNestedScroll(axes);       }          @Override       public void stopNestedScroll() {           mNestedScrollingChildHelper.stopNestedScroll();       }          @Override       public boolean hasNestedScrollingParent() {           return mNestedScrollingChildHelper.hasNestedScrollingParent();       }          @Override       public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {           return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);       }          @Override       public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {           return mNestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);       }          @Override       public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {           return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);       }          @Override       public boolean dispatchNestedPreFling(float velocityX, float velocityY) {           return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);       }   }  

    可以看到基本接口里面的每个方法 都只要调用 mNestedScrollingChildHelper 中相应的方法; 下面来看看  NestedScrollingParentHelper 源码:  [java]  view plain  copy public class NestedScrollingChildHelper {       /**       * 嵌套滑动的ziView       */       private final View mView;          /**       * 支持 嵌套滑动的 父View       */       private ViewParent mNestedScrollingParent;          /**       * 是否支持 嵌套滑动       */       private boolean mIsNestedScrollingEnabled;          /**       * 是否被消费的一个中变变量       */       private int[] mTempNestedScrollConsumed;          public NestedScrollingChildHelper(View view) {           mView = view;       }          public void setNestedScrollingEnabled(boolean enabled) {           if (mIsNestedScrollingEnabled) {               ViewCompat.stopNestedScroll(mView);           }           mIsNestedScrollingEnabled = enabled;       }          public boolean isNestedScrollingEnabled() {           return mIsNestedScrollingEnabled;       }          public boolean hasNestedScrollingParent() {           return mNestedScrollingParent != null;       }          /**       * 开始嵌套滑动       * @param axes 滑动方向       * @return 是否有父view 支持嵌套滑动       */       public boolean startNestedScroll(int axes) {           if (hasNestedScrollingParent()) {               // 如果已经找到 了嵌套滑动的父View               // Already in progress               return true;           }           if (isNestedScrollingEnabled()) {               ViewParent p = mView.getParent();               View child = mView;               // 递归向上寻找 支持 嵌套滑动的父View               while (p != null) {                   // 这里会调用 父View 的NestedScrollingParent.onStartNestedScroll 方法                   // 如果 父View 返回 false  则再次向上寻找父View , 直到找到支持的fuView                   if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {                       mNestedScrollingParent = p;                       // 这里回调 父View 的onNestedScrollAccepted 方法 表示开始接收 嵌套滑动                       ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);                       return true;                   }                   if (p instanceof View) {                       child = (View) p;                   }                   p = p.getParent();               }           }           // 没有找到 支持嵌套滑动的父View  则返回false           return false;       }          /**       * 停止 嵌套滑动, 一般 在 cancel up 事件中 调用       */       public void stopNestedScroll() {           if (mNestedScrollingParent != null) {               ViewParentCompat.onStopNestedScroll(mNestedScrollingParent, mView);               mNestedScrollingParent = null;           }       }          /**       *       * @param dxConsumed  x 上被消费的距离       * @param dyConsumed  y 上被消费的距离       * @param dxUnconsumed  x 上未被消费的距离       * @param dyUnconsumed  y 上未被消费的距离       * @param offsetInWindow  子View 位置的移动距离       * @return       */       public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,                                           int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {           if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {               if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {                   int startX = 0;                   int startY = 0;                   if (offsetInWindow != null) {                       mView.getLocationInWindow(offsetInWindow);                       startX = offsetInWindow[0];                       startY = offsetInWindow[1];                   }                      // 父View 回调 onNestedScroll 方法, 该放在 主要会处理  dxUnconsumed dyUnconsumed 数据                   ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed,                           dyConsumed, dxUnconsumed, dyUnconsumed);                      if (offsetInWindow != null) {                       // 计算 子View的移动距离                       mView.getLocationInWindow(offsetInWindow);                       offsetInWindow[0] -= startX;                       offsetInWindow[1] -= startY;                   }                   return true;               } else if (offsetInWindow != null) {                   // No motion, no dispatch. Keep offsetInWindow up to date.                   offsetInWindow[0] = 0;                   offsetInWindow[1] = 0;               }           }           return false;       }          /**       *       * consumed[0]  为0 时 表示 x 轴方向上事件 没有被消费       *              不为0 时 表示 x 轴方向上事件 被消费了, 值表示 被消费的滑动距离       * consumed[1]  为0 时 表示 y 轴方向上事件 没有被消费       *              不为0 时 表示 y 轴方向上事件 被消费了, 值表示 被消费的滑动距离       *       *       * @param dx       * @param dy       * @param consumed       * @param offsetInWindow       * @return       */       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;                   // 获取 当前View 初始位置                   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;                      // 这里回调 父View 的 onNestedPreScroll 方法,                   // 父View 或许会处理 相应的滑动事件,                   // 如果 处理了 则 consumed 会被赋予 相应的值                   ViewParentCompat.onNestedPreScroll(mNestedScrollingParent, mView, dx, dy, consumed);                      if (offsetInWindow != null) {                       // 父View 处理了相应的滑动,  很可能导致 子View 的位置的移动                       // 这里计算出  父view 消费 滑动事件后,  导致 子View 的移动距离                       mView.getLocationInWindow(offsetInWindow);                       // 这里 子View 的移动距离                       offsetInWindow[0] -= startX;                       offsetInWindow[1] -= startY;                   }                   // 如果  xy 方向 上 有不为0 的表示消费了 则返回true                   return consumed[0] != 0 || consumed[1] != 0;               } else if (offsetInWindow != null) {                   offsetInWindow[0] = 0;                   offsetInWindow[1] = 0;               }           }           return false;       }          public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {           if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {               return ViewParentCompat.onNestedFling(mNestedScrollingParent, mView, velocityX,                       velocityY, consumed);           }           return false;       }          public boolean dispatchNestedPreFling(float velocityX, float velocityY) {           if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {               return ViewParentCompat.onNestedPreFling(mNestedScrollingParent, mView, velocityX,                       velocityY);           }           return false;       }          public void onDetachedFromWindow() {           ViewCompat.stopNestedScroll(mView);       }          public void onStopNestedScroll(View child) {           ViewCompat.stopNestedScroll(mView);       }   }   从上面的注释 可以基本看到 嵌套滑动的基本逻辑: 下面来看看 父View  [java]  view plain  copy public class Parent extends LinearLayout implements NestedScrollingParent {       public static final String TAG = "Parent";          private NestedScrollingParentHelper mNestedScrollingParentHelper;          public Parent(Context context, AttributeSet attrs) {           super(context, attrs);           mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);       }          /**       * 回调开始滑动       * @param child 该父VIew 的子View       * @param target 支持嵌套滑动的 VIew       * @param nestedScrollAxes 滑动方向       * @return 是否支持 嵌套滑动       */       @Override       public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {           return true;       }          @Override       public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {           mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);       }          @Override       public void onStopNestedScroll(View target) {           mNestedScrollingParentHelper.onStopNestedScroll(target);       }          /**       * 这里 主要处理 dyUnconsumed dxUnconsumed 这两个值对应的数据       * @param target       * @param dxConsumed       * @param dyConsumed       * @param dxUnconsumed       * @param dyUnconsumed       */       @Override       public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {           LogUtil.d(TAG, "onNestedScroll target = " + target + " , dxConsumed = " + dxConsumed + " , dyConsumed = " + dyConsumed + " , dxUnconsumed = " + dxUnconsumed + " , dyUnconsumed = " + dyUnconsumed);       }          /**       * 这里 传来了 x y 方向上的滑动距离       * 并且 先与 子VIew  处理滑动,  并且 consumed  中可以设置相应的 除了的距离       * 然后 子View  需要更具这感觉, 来处理自己滑动       *       * @param target       * @param dx       * @param dy       * @param consumed       */       @Override       public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {              consumed[1] = dy;           LogUtil.d(TAG, "onNestedPreScroll dx = " + dx + " dy = " + dy);       }          @Override       public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {           return false;       }          @Override       public boolean onNestedPreFling(View target, float velocityX, float velocityY) {           return false;       }          @Override       public int getNestedScrollAxes() {           return mNestedScrollingParentHelper.getNestedScrollAxes();       }   }  

    总结一下  整个嵌套滑动的流程是: 子view父viewstartNestedScrollonStartNestedScroll、onNestedScrollAccepteddispatchNestedPreScrollonNestedPreScrolldispatchNestedScrollonNestedScrollstopNestedScrollonStopNestedScroll 通过以上的代码 注释 基本对 嵌套互动有了一个大致的基本了解
    转载请注明原文地址: https://ju.6miu.com/read-968052.html

    最新回复(0)