美文网首页
解决嵌套滑动的三种方式

解决嵌套滑动的三种方式

作者: isLJli | 来源:发表于2020-09-06 01:32 被阅读0次

嵌套滑动冲突的原因:

嵌套滑动:一个可滑动的父View包裹了可滑动的子View,由上篇的事件分发原理分析我们得知:
父View会执行dispatchTouchEvent()决定是否拦截?如果拦截,则不传递给子类事件,如果不拦截,则遍历子类查看是否拦截。而嵌套滑动的父子View都需要拦截事件,但默认给父View拦截了,子View就滑动不了。
那么解决的方法就是,只能根据滑动的临界条件,动态的给父子View分配事件。

大致三种解决方案:

  1. 使用有NestedScrolling机制滑动控件:根据接口实现,动态分配事件。
  2. 外部拦截法:父View滑动控件,动态决定是否拦截。
  3. 内部拦截法:子View滑动控件,动态决定是否拦截

外部拦截法

原理:控制父View的onInterceptTouchEvent()方法,决定在什么时候拦截。
拦截时机:先判断手势的上下,然后根据滑动的子View是否已经在顶部或底部来决定是否拦截。

public class MyScrollerView extends ScrollView {

  private int mLastY = 0;
  、、、省略构造函数

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
      //默认不拦截
      boolean intercepted = false;
      int y = (int) ev.getY();
      switch (ev.getActionMasked()) {
          case MotionEvent.ACTION_DOWN:
              intercepted = false;
              super.onInterceptTouchEvent(ev);
              break;
          case MotionEvent.ACTION_MOVE:
              int detY = y - mLastY;
              // 还要自己找子View
              View childView = findViewById(R.id.child);
              if (childView == null) {
                  return true; //拦截
              }
              //根据手势判断子scrollView是否在顶部或底部
              boolean isChildScrolledTop = detY > 0 && !childView.canScrollVertically(-1);
              boolean isChildScrolledBottom = detY < 0 && !childView.canScrollVertically(1);
              if (isChildScrolledTop || isChildScrolledBottom) {
                  intercepted = true;
              } else {
                  intercepted = false;
              }
              break;
          case MotionEvent.ACTION_UP:
              intercepted = false;
              break;
      }
      mLastY = y;
      return intercepted;
  }
}


内部拦截法

原理:由子滑动View调用requestDisallowInterceptTouchEvent()决定父View是否可拦截

public class MyScrollerView extends ScrollView {

  private int mLastY = 0;

  public MyScrollerView(Context context) {
      super(context);
  }

  public MyScrollerView(Context context, AttributeSet attrs) {
      super(context, attrs);
  }

  public MyScrollerView(Context context, AttributeSet attrs, int defStyleAttr) {
      super(context, attrs, defStyleAttr);
  }

  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
      int y = (int) ev.getY();
      switch (ev.getActionMasked()) {
          case MotionEvent.ACTION_DOWN:
              //不让父View拦截
              getParent().requestDisallowInterceptTouchEvent(true);
              break;
          case MotionEvent.ACTION_MOVE:
              int detY = y - mLastY;
              boolean isScrolledTop = detY > 0 && !canScrollVertically(-1);
              boolean isScrolledBottom = detY < 0 && !canScrollVertically(1);
              //根据自身是否滑动到顶部或者顶部来判断让父View拦截触摸事件
              if (isScrolledTop || isScrolledBottom) {
                  //让父View拦截
                  getParent().requestDisallowInterceptTouchEvent(false);
              }
              mLastY = y;
      }
      return super.dispatchTouchEvent(ev);
  }

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
      if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
          super.onInterceptTouchEvent(ev);
          return false;
      }
      return true;
  }
}

NestedScrolling机制

实现了NestedScrolling的常用控件有:RecyclerView、NestedScrollView、CoordinatorLayout

NestedScrolling介绍:

  • NestedScrolling的分为child和parent两种,它解决嵌套滑动的主要思路是让child在onTouchEvent中总是拦截事件,然后通过实现NestedScrollingChild和NestedScrollingParent接口来让两者相互的通信。子View总会先让父View先消耗事件,然后再自己消耗。
  • 系统提供了NestedScrollingParentHelper和NestedScrollingChildHelper两个类中写好了parent和child的接口实例类,我们只要实列化他们,就可以调用他们已经写好的通信方式。
  • 但实例类并不是万能,在MOVE手势时的dispatchNestedPreScroll和dispatchNestedScroll,onNestedPreScroll和onNestedScrollUP手势时的dispatchNestedPreFling和dispatchNestedFling,onNestedPreFling和onNestFling,实现不同的需求逻辑。

NestedScrollingChild接口类API:

public interface NestedScrollingChild {

  void setNestedScrollingEnabled(boolean enabled); //开启或关闭嵌套滑动

  boolean isNestedScrollingEnabled(); //返回是否开启嵌套滑动

  boolean startNestedScroll(@ScrollAxis int axes); //axes为滑动方向, 返回是否找到NestedScrollingParent配合滑动

  void stopNestedScroll(); //停止嵌套滑动

  boolean hasNestedScrollingParent(); //返回是否有配合滑动NestedScrollingParent

  boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
          int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow); //滑动完成后,将已经消费、剩余的滑动值分发给NestedScrollingParent

  boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
          @Nullable int[] offsetInWindow); //在滑动之前,将滑动值分发给NestedScrollingParent

  boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);//将惯性滑动的速度和NestedScrollingChild自身是否需要消费此惯性滑动分发给NestedScrollingParent

  boolean dispatchNestedPreFling(float velocityX, float velocityY); //在惯性滑动之前,将惯性滑动值分发给NestedScrollingParent
}

NestedScrollingParent接口类API:

public interface NestedScrollingParent {

 /**
   * 对NestedScrollingChild发起嵌套滑动作出应答
   * @param child 布局中包含下面target的直接父View
   * @param target 发起嵌套滑动的NestedScrollingChild的View
   * @param axes 滑动方向
   * @return 返回NestedScrollingParent是否配合处理嵌套滑动
   */
  boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes); //对NestedScrollingChild发起嵌套滑动作出应答

 /**
   * NestedScrollingParent配合处理嵌套滑动回调此方法
   * @param child 同上
   * @param target 同上
   * @param axes 同上
   */
  void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes);//NestedScrollingParent配合处理嵌套滑动回调此方法

   /**
   * 嵌套滑动结束
   * @param target 同上
   */
  void onStopNestedScroll(@NonNull View target);

/**
   * NestedScrollingChild滑动完成后将滑动值分发给NestedScrollingParent回调此方法
   * @param target 同上
   * @param dxConsumed 水平方向消费的距离
   * @param dyConsumed 垂直方向消费的距离
   * @param dxUnconsumed 水平方向剩余的距离
   * @param dyUnconsumed 垂直方向剩余的距离
   */
  void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
          int dxUnconsumed, int dyUnconsumed);

  /**
   * NestedScrollingChild滑动完之前将滑动值分发给NestedScrollingParent回调此方法
   * @param target 同上
   * @param dx 水平方向的距离
   * @param dy 水平方向的距离
   * @param consumed 返回NestedScrollingParent是否消费部分或全部滑动值
   */
  void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed);

 /**
   * NestedScrollingChild在惯性滑动之前,将惯性滑动的速度和NestedScrollingChild自身是否需要消费此惯性滑动分
   * 发给NestedScrollingParent回调此方法
   * @param target 同上
   * @param velocityX 水平方向的速度
   * @param velocityY 垂直方向的速度
   * @param consumed NestedScrollingChild自身是否需要消费此惯性滑动
   * @return 返回NestedScrollingParent是否消费全部惯性滑动
   */
  boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed);

  /**
   * NestedScrollingChild在惯性滑动之前,将惯性滑动的速度分发给NestedScrollingParent
   * @param target 同上
   * @param velocityX 同上
   * @param velocityY 同上
   * @return 返回NestedScrollingParent是否消费全部惯性滑动
   */
  boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY);

  @ScrollAxis
  int getNestedScrollAxes(); //返回当前嵌套滑动的方向
}

NestedScrollView实例分析
NestedScrollView是一个实现了Parent、Child的NestedScrolling的接口,接下看看它是如何通过实例帮助类,实现嵌套滑动的。

DOWN手势

  1. 子View滑动之前询问父View是否配合滑动
  • 子:startNestedScroll
  • 父:onStartNestedScroll,onNestedScrollAccepted

NestedScrollView:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
  case MotionEvent.ACTION_DOWN: {
       // ....
      startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
      break;
  }
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
  case MotionEvent.ACTION_DOWN: {
      // ....
      startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
      break;
  }
}

  @Override
  public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes,
          int type) {
      //如果是垂直方向就接受
      return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
  }

  @Override
  public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes,
          int type) {
      mParentHelper.onNestedScrollAccepted(child, target, axes, type);
      startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, type);
  }

NestedScrollingChildHelper:

public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) {
      //如果有可配合的Parent则返回true
      if (hasNestedScrollingParent(type)) {
          // Already in progress
          return true;
      }
      //自己是否可滑动
      if (isNestedScrollingEnabled()) {
          ViewParent p = mView.getParent();
          View child = mView;
          while (p != null) {
               //遍历父View的onStartNestedScroll()询问是否配合滑动
              if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) {
                  //保存配合的父View
                  setNestedScrollingParentForType(type, p);
                   //执行父View的onNestedScrollAccepted方法
                  ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type);
                  //返回ture
                  return true;
              }
              if (p instanceof View) {
                  child = (View) p;
              }
              p = p.getParent();
          }
      }
      //没有父View配合滑动则返回false
      return false;
  }

MOVE手势

  1. 如果有父View配合。则子View在滑动之前会把滑动值先交给父View消费,父View也将消费的值存起来返回给子View,子View则可以根据父View消耗的值来计算自己的滑动距离。子View消耗后将剩余的交给父View进行处理
  • 子:dispatchNestedPreScroll,dispatchNestedScroll
  • 父:onNestedPreScroll,onNestedScroll

NestedScrollView:

  @Override
  public boolean onTouchEvent(MotionEvent ev) {
          // ....
          case MotionEvent.ACTION_MOVE:
              // ....
              //滑动的距离
              int deltaY = mLastMotionY - y;
              // ....
              if (mIsBeingDragged) {
                  // Start with nested pre scrolling
                  //滑动之前先给父View消耗此次的滑动距离
                  if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset,
                          ViewCompat.TYPE_TOUCH)) {
                      //父View消耗后剩余的距离
                      deltaY -= mScrollConsumed[1];
                      mNestedYOffset += mScrollOffset[1];
                  }

                 // ....
                //调用overScrollByCompat将调用onOverScrolled,//如果适用,则调用onScrollChanged。
                  if (overScrollByCompat(0, deltaY, 0, getScrollY(), 0, range, 0,
                          0, true) && !hasNestedScrollingParent(ViewCompat.TYPE_TOUCH)) {
                      // Break our velocity if we hit a scroll barrier.
                      mVelocityTracker.clear();
                  }

                  final int scrolledDeltaY = getScrollY() - oldY;
                  final int unconsumedY = deltaY - scrolledDeltaY;

                  mScrollConsumed[1] = 0;
                   //子View消耗后将剩余的距离给父View处理
                  dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset,
                          ViewCompat.TYPE_TOUCH, mScrollConsumed);
                 // ....
              break;
        
      return true;
  }

  // 消费子View滑动之前的距离
  @Override
  public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,
          int type) {
      dispatchNestedPreScroll(dx, dy, consumed, null, type);
  }

  // 消费子View滑动之后的距离
  @Override
  public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
          int dxUnconsumed, int dyUnconsumed, int type) {
      onNestedScrollInternal(dyUnconsumed, type, null);
  }

NestedScrollingChildHelper:

public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
          @Nullable int[] offsetInWindow, @NestedScrollType int type) {
      if (isNestedScrollingEnabled()) {
          //拿到配合滑动的父View
          final ViewParent parent = getNestedScrollingParentForType(type);
          if (parent == null) {
              return false;
          }

          if (dx != 0 || dy != 0) {
              int startX = 0;
              int startY = 0;
              if (offsetInWindow != null) {
                  //滑动子VIew当前的坐上坐标
                  mView.getLocationInWindow(offsetInWindow);
                  startX = offsetInWindow[0];
                  startY = offsetInWindow[1];
              }
              //给父View消耗的数组
              if (consumed == null) {
                  consumed = getTempNestedScrollConsumed();
              }
              consumed[0] = 0;
              consumed[1] = 0;
              //调用父View的onNestedPreScroll()
              ViewParentCompat.onNestedPreScroll(parent, mView, dx, dy, consumed, type);

              if (offsetInWindow != null) {
                  //计算子view滑动的距离
                  mView.getLocationInWindow(offsetInWindow);
                  offsetInWindow[0] -= startX;
                  offsetInWindow[1] -= startY;
              }
              //返回父View是否消耗了距离
              return consumed[0] != 0 || consumed[1] != 0;
          } else if (offsetInWindow != null) {
              offsetInWindow[0] = 0;
              offsetInWindow[1] = 0;
          }
      }
      return false;
  }

public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
      int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) {
  return dispatchNestedScrollInternal(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
          offsetInWindow, TYPE_TOUCH, null);
}

  private boolean dispatchNestedScrollInternal(int dxConsumed, int dyConsumed,
          int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow,
          @NestedScrollType int type, @Nullable int[] consumed) {
      //找到配合滑动的父View
      if (isNestedScrollingEnabled()) {
          final ViewParent parent = getNestedScrollingParentForType(type);
          if (parent == null) {
              return false;
          }

          if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {
              int startX = 0;
              int startY = 0;
              //子View开始的左上位置
              if (offsetInWindow != null) {
                  mView.getLocationInWindow(offsetInWindow);
                  startX = offsetInWindow[0];
                  startY = offsetInWindow[1];
              }

              if (consumed == null) {
                  consumed = getTempNestedScrollConsumed();
                  consumed[0] = 0;
                  consumed[1] = 0;
              }
              //执行父View的onNestedScroll()进行剩余事件消耗
              ViewParentCompat.onNestedScroll(parent, mView,
                      dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed);
              // 父view消耗后子View的滑动距离
              if (offsetInWindow != null) {
                  mView.getLocationInWindow(offsetInWindow);
                  offsetInWindow[0] -= startX;
                  offsetInWindow[1] -= startY;
              }
               //返回true
              return true;
          } else if (offsetInWindow != null) {
              // No motion, no dispatch. Keep offsetInWindow up to date.
              offsetInWindow[0] = 0;
              offsetInWindow[1] = 0;
          }
      }
      return false;
  }

UP手势

  1. 当手指抬起时,子View则将配合的父View置空,父View也将点击类型转化为NONE。
    如果手指抬起时有fling值,则先将Fling值给父View消耗,然后子View计算父View消耗的距离再决定自己的消耗。等子View消耗完之后,则再讲剩余的给父View消耗。
  • 子:stopNestScroll(),dispatchNestedPreFling(),dispatchNestedFling()
  • 父:onStopNestedScroll(),onNestedPreFling(),onNestedFling()

NestedScrollView:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
  case MotionEvent.ACTION_UP:
      // ....
      stopNestedScroll(ViewCompat.TYPE_TOUCH);
      break;
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
  case MotionEvent.ACTION_UP:
      final VelocityTracker velocityTracker = mVelocityTracker;
      velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
      int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
      if ((Math.abs(initialVelocity) >= mMinimumVelocity)) {
          if (!dispatchNestedPreFling(0, -initialVelocity)) {
              dispatchNestedFling(0, -initialVelocity, true);
              fling(-initialVelocity);
          }
      } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
              getScrollRange())) {
          ViewCompat.postInvalidateOnAnimation(this);
      }
      mActivePointerId = INVALID_POINTER;
      endDrag();
      break;
  case MotionEvent.ACTION_CANCEL:
      if (mIsBeingDragged && getChildCount() > 0) {
          if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
                  getScrollRange())) {
              ViewCompat.postInvalidateOnAnimation(this);
          }
      }
      mActivePointerId = INVALID_POINTER;
      endDrag();
      break;
}

private void endDrag() {
   // ....
   stopNestedScroll(ViewCompat.TYPE_TOUCH); 
  // ....
}

@Override
public void onStopNestedScroll(@NonNull View target, int type) {
  mParentHelper.onStopNestedScroll(target, type);
  stopNestedScroll(type);
}

@Override
public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) {
  return dispatchNestedPreFling(velocityX, velocityY);
}

@Override
public boolean onNestedFling(
      @NonNull View target, float velocityX, float velocityY, boolean consumed) {
  if (!consumed) {
      dispatchNestedFling(0, velocityY, true);
      fling((int) velocityY);
      return true;
  }
  return false;
}

NestedScrollingChildHelper:-----------------------
public void stopNestedScroll(@NestedScrollType int type) {
      ViewParent parent = getNestedScrollingParentForType(type);
      if (parent != null) {
          //调用父View的onStopNestedScroll()方法
          ViewParentCompat.onStopNestedScroll(parent, mView, type);
          //设置父View为null
          setNestedScrollingParentForType(type, null);
      }
  }

NestedScrollingParentHelper:-----------------------------
public void onStopNestedScroll(@NonNull View target, @NestedScrollType int type) {
      if (type == ViewCompat.TYPE_NON_TOUCH) {
          mNestedScrollAxesNonTouch = ViewGroup.SCROLL_AXIS_NONE;
      } else {
          mNestedScrollAxesTouch = ViewGroup.SCROLL_AXIS_NONE;
      }
  }

NestedScrollingChildHelper:-----------------------
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
      if (isNestedScrollingEnabled()) {
          ViewParent parent = getNestedScrollingParentForType(TYPE_TOUCH);
          if (parent != null) {
              //调用父View的onNestedPreFling()方法
              return ViewParentCompat.onNestedPreFling(parent, mView, velocityX,
                      velocityY);
          }
      }
      return false;
  }

public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
      if (isNestedScrollingEnabled()) {
          ViewParent parent = getNestedScrollingParentForType(TYPE_TOUCH);
          if (parent != null) {
              return ViewParentCompat.onNestedFling(parent, mView, velocityX,
                      velocityY, consumed);
          }
      }
      return false;
  }

流程图:


image.png

自定义简易版NestedScrollingParent类

public class NestedScrollLayout extends NestedScrollView {
  private View topView;
  private ViewGroup contentView;
  private static final String TAG = "NestedScrollLayout";

  public NestedScrollLayout(Context context) {
      this(context, null);
      init();
  }

  public NestedScrollLayout(Context context, @Nullable AttributeSet attrs) {
      this(context, attrs, 0);
      init();
  }

  public NestedScrollLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
      this(context, attrs, defStyleAttr, 0);
      init();
  }

  public NestedScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
      super(context, attrs, defStyleAttr);
      init();
  }

  private FlingHelper mFlingHelper;

  int totalDy = 0;
  /**
   * 用于判断RecyclerView是否在fling
   */
  boolean isStartFling = false;
  /**
   * 记录当前滑动的y轴加速度
   */
  private int velocityY = 0;

  private void init() {
      mFlingHelper = new FlingHelper(getContext());
      setOnScrollChangeListener(new View.OnScrollChangeListener() {
          @Override
          public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
              if (isStartFling) {
                  totalDy = 0;
                  isStartFling = false;
              }
              if (scrollY == 0) {
                  Log.i(TAG, "TOP SCROLL");
                 // refreshLayout.setEnabled(true);
              }
              if (scrollY == (getChildAt(0).getMeasuredHeight() - v.getMeasuredHeight())) {
                  Log.i(TAG, "BOTTOM SCROLL");
                  dispatchChildFling();
              }
              //在RecyclerView fling情况下,记录当前RecyclerView在y轴的偏移
              totalDy += scrollY - oldScrollY;
          }
      });
  }


  @Override
  protected void onFinishInflate() {
      super.onFinishInflate();
      topView = ((ViewGroup) getChildAt(0)).getChildAt(0);
      contentView = (ViewGroup) ((ViewGroup) getChildAt(0)).getChildAt(1);
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      // 调整contentView的高度为父容器高度,使之填充布局,避免父容器滚动后出现空白
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      ViewGroup.LayoutParams lp = contentView.getLayoutParams();
      lp.height = getMeasuredHeight();
      contentView.setLayoutParams(lp);
  }

  @Override
  public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
      Log.i("NestedScrollLayout", getScrollY()+"::onNestedPreScroll::"+topView.getMeasuredHeight());
      // 向上滑动。若当前topview可见,需要将topview滑动至不可见
      boolean hideTop = dy > 0 && getScrollY() < topView.getMeasuredHeight();
      if (hideTop) {
          scrollBy(0, dy);
          consumed[1] = dy;
      }
  }

  private void dispatchChildFling() {
      if (velocityY != 0) {
          Double splineFlingDistance = mFlingHelper.getSplineFlingDistance(velocityY);
          if (splineFlingDistance > totalDy) {
              childFling(mFlingHelper.getVelocityByDistance(splineFlingDistance - Double.valueOf(totalDy)));
          }
      }
      totalDy = 0;
      velocityY = 0;
  }

  private void childFling(int velY) {
      RecyclerView childRecyclerView = getChildRecyclerView(contentView);
      if (childRecyclerView != null) {
          childRecyclerView.fling(0, velY);
      }
  }

  @Override
  public void fling(int velocityY) {
      super.fling(velocityY);
      //记录速度
      if (velocityY <= 0) {
          this.velocityY = 0;
      } else {
          isStartFling = true;
          this.velocityY = velocityY;
      }
  }

  private RecyclerView getChildRecyclerView(ViewGroup viewGroup) {
      for (int i = 0; i < viewGroup.getChildCount(); i++) {
          View view = viewGroup.getChildAt(i);
          if (view instanceof RecyclerView && view.getClass() == NestedLogRecyclerView.class) {
              return (RecyclerView) viewGroup.getChildAt(i);
          } else if (viewGroup.getChildAt(i) instanceof ViewGroup) {
              ViewGroup childRecyclerView = getChildRecyclerView((ViewGroup) viewGroup.getChildAt(i));
              if (childRecyclerView instanceof RecyclerView) {
                  return (RecyclerView) childRecyclerView;
              }
          }
          continue;
      }
      return null;
  }
}

相关文章

网友评论

      本文标题:解决嵌套滑动的三种方式

      本文链接:https://www.haomeiwen.com/subject/kfrrqktx.html