美文网首页RecyclerView系列Android-recyclerview
RecyclerView的onTouchEvent源码浅析

RecyclerView的onTouchEvent源码浅析

作者: isLJli | 来源:发表于2020-11-25 11:35 被阅读0次

    从哪里找RecyclerView滑动事件处理?

    我们都知道RecyclerView是一个可以滑动的ViewGroup,而且还是一个实现NestedScrollingChild2、3可以嵌套滑动ViewGroup。那么我们的手指在手机点击滑动时,RecyclerView究竟是怎么做的呢?

    由如下图的ViewGroup事件分发机制可以得出,ViewGroup手势事件只能在三个方法中:1.在分发事件的方法dispatchTouchEvent;2.是在拦截事件:onInterceprTouchEvent;3.在onTouchEvent方法中。第一个dispatchTouchEventRecyclerView并没有重写,而是复用了ViewGroup本省的dispatchTouchEvent方法,所以并没有在dispatchTouchEvent处理手势事件。第二个onInterceprTouchEvent拦截方法也只是在DownUp做一些嵌套滑动开始结束查询操作,在MOVE事件并没有什么事情。所以第三个onTouchEvent就是RecyclerView手势滑动事件的方法。

    ViewGroup的事件分发

    onTouchEvent方法处理

      @Override
      public boolean onTouchEvent(MotionEvent e) {
          if (mLayoutSuppressed || mIgnoreMotionEventTillDown) {
              return false;
          }
          // RecyclerView提供了一个OnItemTouchListener接口,可以让你自己重写onInterceptTouchEvent,onTouchEvent,
          //onRequestDisallowInterceptTouchEvent方法,那么这时就会调用你自定义接口onTouchEvent方法
          if (dispatchToOnItemTouchListeners(e)) {
              cancelScroll();
              return true;
          }
    
          if (mLayout == null) {
              return false;
          }
    
          //向LayoutManager询问是哪边可滑动
          final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
          final boolean canScrollVertically = mLayout.canScrollVertically();
    
          // 这个根据fing力度算可滑动距离
          if (mVelocityTracker == null) {
              mVelocityTracker = VelocityTracker.obtain();
          }
          boolean eventAddedToVelocityTracker = false;
    
          final int action = e.getActionMasked();
          final int actionIndex = e.getActionIndex();
    
          if (action == MotionEvent.ACTION_DOWN) {
              mNestedOffsets[0] = mNestedOffsets[1] = 0;
          }
          final MotionEvent vtev = MotionEvent.obtain(e);
          vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]);
    
          switch (action) {
               //手指点击屏幕时
              case MotionEvent.ACTION_DOWN: {
                  //跟新 id和开始点击位置
                  mScrollPointerId = e.getPointerId(0);
                  mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
                  mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
    
                  // 设置滑动方向的变量,查询是否有配合滑动的父View
                  int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
                  if (canScrollHorizontally) {
                      nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
                  }
                  if (canScrollVertically) {
                      nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
                  }
                  startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
              }
              break;
    
              // 多指按下
              case MotionEvent.ACTION_POINTER_DOWN: {
                  // 跟新最后手指的 id和初始位置
                  mScrollPointerId = e.getPointerId(actionIndex);
                  mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);
                  mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);
              }
              break;
    
              // 手指在屏幕滑动
              case MotionEvent.ACTION_MOVE: {
                  final int index = e.findPointerIndex(mScrollPointerId);
                  if (index < 0) {
                      Log.e(TAG, "Error processing scroll; pointer index for id "
                              + mScrollPointerId + " not found. Did any MotionEvents get skipped?");
                      return false;
                  }
    
                  // 算出手指滑动的距离
                  final int x = (int) (e.getX(index) + 0.5f);
                  final int y = (int) (e.getY(index) + 0.5f);
                  int dx = mLastTouchX - x;
                  int dy = mLastTouchY - y;
    
                  // 手指滑动距离是否比系统默认的最小滑动距离大,设置是否可滑动
                  if (mScrollState != SCROLL_STATE_DRAGGING) {
                      boolean startScroll = false;
                      if (canScrollHorizontally) {
                          if (dx > 0) {
                              dx = Math.max(0, dx - mTouchSlop);
                          } else {
                              dx = Math.min(0, dx + mTouchSlop);
                          }
                          if (dx != 0) {
                              startScroll = true;
                          }
                      }
                      if (canScrollVertically) {
                          if (dy > 0) {
                              dy = Math.max(0, dy - mTouchSlop);
                          } else {
                              dy = Math.min(0, dy + mTouchSlop);
                          }
                          if (dy != 0) {
                              startScroll = true;
                          }
                      }
                      if (startScroll) {
                          setScrollState(SCROLL_STATE_DRAGGING);
                      }
                  }
    
                  if (mScrollState == SCROLL_STATE_DRAGGING) {
                      mReusableIntPair[0] = 0;
                      mReusableIntPair[1] = 0;
                      // 先给配合嵌套父view滑动
                      if (dispatchNestedPreScroll(
                              canScrollHorizontally ? dx : 0,
                              canScrollVertically ? dy : 0,
                              mReusableIntPair, mScrollOffset, TYPE_TOUCH
                      )) {
                          // 手指滑动的距离减去父View消耗的距离
                          dx -= mReusableIntPair[0];
                          dy -= mReusableIntPair[1];
                          // Updated the nested offsets
                          mNestedOffsets[0] += mScrollOffset[0];
                          mNestedOffsets[1] += mScrollOffset[1];
                          // Scroll has initiated, prevent parents from intercepting
                          getParent().requestDisallowInterceptTouchEvent(true);
                      }
    
                      mLastTouchX = x - mScrollOffset[0];
                      mLastTouchY = y - mScrollOffset[1];
                     // recylcerView将手指滑动的距离自己进行消耗滑动
                      if (scrollByInternal(
                              canScrollHorizontally ? dx : 0,
                              canScrollVertically ? dy : 0,
                              e, TYPE_TOUCH)) {
                          getParent().requestDisallowInterceptTouchEvent(true);
                      }
                      if (mGapWorker != null && (dx != 0 || dy != 0)) {
                          // 尝试拿到ViewHolder
                          mGapWorker.postFromTraversal(this, dx, dy);
                      }
                  }
              }
              break;
    
              case MotionEvent.ACTION_POINTER_UP: {
                 // 跟新id和初始位置
                  onPointerUp(e);
              }
              break;
    
              case MotionEvent.ACTION_UP: {
                 
                  mVelocityTracker.addMovement(vtev);
                  eventAddedToVelocityTracker = true;
                  mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
                   // 根据速度算出距离
                  final float xvel = canScrollHorizontally
                          ? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;
                  final float yvel = canScrollVertically
                          ? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;
                  // 开始fling距离
                  if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
                      setScrollState(SCROLL_STATE_IDLE);
                  }
                  resetScroll();
              }
              break;
    
              case MotionEvent.ACTION_CANCEL: {
                  cancelScroll();
              }
              break;
          }
    
          if (!eventAddedToVelocityTracker) {
              mVelocityTracker.addMovement(vtev);
          }
          vtev.recycle();
    
          return true;
      }
    

    在研究onTouchEvent()方法执行过程中,先不深究每个方法的具体实现,而是整个流程的大致做了什么。

    1. 手指按下: 更新触摸id和初始位置,与查询是否有父View配合嵌套滑动。
    2. 多指按下:根据最后的手指跟新触摸id和初始位置。
    3. 手指滑动: 得到手指滑动的距离,然后判断这个距离是否达到滑动大小。将手指滑动距离先交给父View消耗dispatchNestedPreScroll,如果父View消耗了,则手指滑动的距离减去父view消耗的距离,再将滑动的距离交给RV滑动消耗scrollByInternal,RV消耗完,再询问父View是否要消耗。并且尝试拿到ViewHoldermGapWorker.postFromTraversal
    4. 多指离开:跟新触摸id和初始位置。
    5. 单指离开: 根据Fling的力度算出可滑动的距离,然后调用fling方法进行滑动。

    这里有三个比较重要方法:分别是MOVE时的RV滑动方法scrollByInternal,获取ViewHolder的mGapWorker.postFromTraversal。最后就是执行UP时的fling方法。

    scrollByInternal

      boolean scrollByInternal(int x, int y, MotionEvent ev, int type) {
          int unconsumedX = 0;
          int unconsumedY = 0;
          int consumedX = 0;
          int consumedY = 0;
    
          consumePendingUpdateOperations();
          if (mAdapter != null) {
              mReusableIntPair[0] = 0;
              mReusableIntPair[1] = 0;
              // rv滑动的方法
              scrollStep(x, y, mReusableIntPair);
              consumedX = mReusableIntPair[0];
              consumedY = mReusableIntPair[1];
              unconsumedX = x - consumedX;
              unconsumedY = y - consumedY;
          }
          if (!mItemDecorations.isEmpty()) {
              invalidate();
          }
    
          mReusableIntPair[0] = 0;
          mReusableIntPair[1] = 0;
          //询问父view是否要消耗
          dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
                  type, mReusableIntPair);
          unconsumedX -= mReusableIntPair[0];
          unconsumedY -= mReusableIntPair[1];
           // 父View是否有消耗
          boolean consumedNestedScroll = mReusableIntPair[0] != 0 || mReusableIntPair[1] != 0;
    
          // Update the last touch co-ords, taking any scroll offset into account
          mLastTouchX -= mScrollOffset[0];
          mLastTouchY -= mScrollOffset[1];
          mNestedOffsets[0] += mScrollOffset[0];
          mNestedOffsets[1] += mScrollOffset[1];
    
          if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
              if (ev != null && !MotionEventCompat.isFromSource(ev, InputDevice.SOURCE_MOUSE)) {
                  pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);
              }
              considerReleasingGlowsOnScroll(x, y);
          }
          if (consumedX != 0 || consumedY != 0) {
              dispatchOnScrolled(consumedX, consumedY);
          }
          if (!awakenScrollBars()) {
              invalidate();
          }
          return consumedNestedScroll || consumedX != 0 || consumedY != 0;
      }
    
        void scrollStep(int dx, int dy, @Nullable int[] consumed) {
            startInterceptRequestLayout();
            onEnterLayoutOrScroll();
    
            TraceCompat.beginSection(TRACE_SCROLL_TAG);
            fillRemainingScrollValues(mState);
    
            int consumedX = 0;
            int consumedY = 0;
            // 调用LayoutManager的scrollHorizontallyBy方法来给RV滑动
            if (dx != 0) {
                consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
            }
            if (dy != 0) {
                consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
            }
    
            TraceCompat.endSection();
            repositionShadowingViews();
    
            onExitLayoutOrScroll();
            stopInterceptRequestLayout(false);
    
            if (consumed != null) {
                consumed[0] = consumedX;
                consumed[1] = consumedY;
            }
        }
    

    scrollByInternal 方法先是调用LayoutManager让RV滑动消耗距离,然后再将剩余的距离询问父View是否需要消耗。

    相关文章

      网友评论

        本文标题:RecyclerView的onTouchEvent源码浅析

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