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

解决嵌套滑动的三种方式

作者: 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