recyclerview item子view点击事件_RecyclerView的ItemTouchHelper学习

ItemTouchHelper的使用

ItemTouchHelper可以解释为:Item点击的帮助类,它可以判断帮助我们快速的实现侧滑Swiped长按拖动Move的效果。如下图的效果,用ItemTouchHelper就可以很简单的实现。644a056b8ca78534e80412f606de4801.gif

1.实现ItemTouchHelper.CallBack接口这个接口我们可以决定是否可以侧滑和拖动,以及方向,并知道侧滑、拖动的执行时的方法回调

class DefaultItemTouchHelper(private var itemTouchStatus: ItemTouchStatus) :
  ItemTouchHelper.Callback() {

  /**
   * dragFlags:拖动支持的方向
   * swipeFlags: 滑动支持的方向
   */
  override fun getMovementFlags(
      recyclerView: RecyclerView,
      viewHolder: RecyclerView.ViewHolder
  ): Int {
      val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
      val swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
      // 通过makeMovementFlags确定拖动、侧滑的方向
      return makeMovementFlags(dragFlags, swipeFlags)
  }

  // 长按拖动
  override fun onMove(
      recyclerView: RecyclerView,
      viewHolder: RecyclerView.ViewHolder,
      target: RecyclerView.ViewHolder
  ): Boolean {
      return itemTouchStatus.onItemMove(viewHolder.adapterPosition, target.adapterPosition)
  }

  // 侧滑
  override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
      itemTouchStatus.onItemRemove(viewHolder.adapterPosition)
  }
}

在拖动、侧滑对Adapter进行数据更新

public class RvAdapter extends RecyclerView.Adapter implements ItemTouchStatus {
  // ....省略Adapter的View创建和数据绑定
   // 侧滑删除
  @Override
  public void onItemRemove(int position) {
      data.remove(position);
      notifyItemRemoved(position);
  }
   //长按拖动数据交换
  @Override
  public boolean onItemMove(int position, int targetPosition) {
      Collections.swap(data, position, targetPosition);
      notifyItemMoved(position, targetPosition);return true;
  }
}
[Activity]将Rv绑到ItemTouchHelper中
val itemTouchHelper = ItemTouchHelper(DefaultItemTouchHelper(adapter!!))
itemTouchHelper.attachToRecyclerView(recyclerView)

实现此功能可以概括为三步

  1. 实现ItemTouchHelper.CallBack接口
  2. ItemTouchHelper实例化时将CallBack传给实例
  3. 通过ItemTouchHelper. attachToRecyclerView将RecyclerView绑定到ItemTouchHelper中。

ItemTouchHelper源码学习

带着问题看源码:

  1. 为什么RecyclerView的Item在侧滑的时候会调用ItemTouchHelper.CallBack.onSwiped方法?
  2. 为什么RecyclerView的Item在长按拖动的时候会调用ItemTouchHelper.CallBack.onMoved方法?
  3. 为什么只通过一行代码attachToRecyclerView绑定RecyclerView后就可以操作RecyclerView的监听事件?
ItemTouchHelper的绑定

先看看ItemTouchHelper的构造函数attachToRecyclerView  [ItemTouchHelper#attachToRecyclerView]

 // 接口实例
  public ItemTouchHelper(@NonNull Callback callback) {
      mCallback = callback;
  }

  public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
      if (mRecyclerView == recyclerView) {
          return; // nothing to do
      }
      // 有旧的RecyclerView,则取消原有监听
      if (mRecyclerView != null) {
          destroyCallbacks();
      }
      mRecyclerView = recyclerView;
      if (recyclerView != null) {
          final Resources resources = recyclerView.getResources();
          mSwipeEscapeVelocity = resources
                  .getDimension(R.dimen.item_touch_helper_swipe_escape_velocity);
          mMaxSwipeVelocity = resources
                  .getDimension(R.dimen.item_touch_helper_swipe_escape_max_velocity);
          // 设置监听
          setupCallbacks();
      }
  }

  private void setupCallbacks() {
      ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());
      mSlop = vc.getScaledTouchSlop();
      mRecyclerView.addItemDecoration(this);
      // 这个监听很重要,可以截取RecylerView的手势事件
      mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
      mRecyclerView.addOnChildAttachStateChangeListener(this);
      startGestureDetection();
  }

我们在使用ItemTouchHelper时,也就是用到这个类中的两个方法, 一构造函数必须传一个CallBack接口的实例。二是attachToRecyclerView方法会给传来的RecyclerView设置监听,其中设置了OnItemTouchListener监听可以拦截RecyclerView的手势事件,从而使ItemTouchHelper可以拦截RecylerView的事件进行处理。startGestureDetection()方法创建了GestureDetection用来识别长按手势。

RecyclerView的事件托管

onInterceptTouchEvent[RecyclerView.onInterceptTouchEvent]

   // 是否拦截事件
  @Override
  public boolean onInterceptTouchEvent(MotionEvent e) {
      if (mLayoutSuppressed) {
          // When layout is suppressed,  RV does not intercept the motion event.
          // A child view e.g. a button may still get the click.
          return false;
      }

      // 当前处理的OnItemTouchListener,重新设置为null
      mInterceptingOnItemTouchListener = null;
      // 先检查是否设置了OnItemTouchListener,那就事件先交给OnItemTouchListener处理
      if (findInterceptingOnItemTouchListener(e)) {
           // 取消滑动
          cancelScroll();
          return true;
      }
      // RecylerView自身处理拦截事件
      //....
   }

  private boolean findInterceptingOnItemTouchListener(MotionEvent e) {
      int action = e.getAction();
       // 遍历onItemTouchListener集合
      final int listenerCount = mOnItemTouchListeners.size();
      for (int i = 0; i           final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
          //执行监听实例的onInterceptTouchEvent方法,如果返回ture和手势不是取消事件,则设置当前处理的OnItemTouchListener和返回Ture拦截事件。
          if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
              mInterceptingOnItemTouchListener = listener;
              return true;
          }
      }
      return false;
  }

RecyclerView在执行拦截事件onInterceptTouchEvent会先遍历OnItemTouchListener集合,查找在OnItemTouchListener.onInterceptTouchEvent中返回true的OnItemTouchListener,并把它设置为当前处理的onItemToucheListener,返回Ture拦截事件且不执行RecyclerView本身处理的onInterceptTouchEvent代码。

onTouchEvent[RecyclerView.onToucheEvent]

  @Override
  public boolean onTouchEvent(MotionEvent e) {
      if (mLayoutSuppressed || mIgnoreMotionEventTillDown) {
          return false;
      }
      // 如果有当前处理的OnItemTouchListener
      if (dispatchToOnItemTouchListeners(e)) {
          cancelScroll();
          return true;
      }
    //....RecyclerView自身对onTouchEvent方法的处理
   }

  private boolean dispatchToOnItemTouchListeners(MotionEvent e) {
      
      if (mInterceptingOnItemTouchListener == null) {
          // 手势开始时,没有要处理的OnItemTouchListener则返回false
          if (e.getAction() == MotionEvent.ACTION_DOWN) {
              return false;
          }
          // 手势在其他事件时,在去询问一下是否拦截,防止中途变更未及时更新
          return findInterceptingOnItemTouchListener(e);
      } else {
          // 调用OnItemTouchListener的.onTouchEvent()方法
          mInterceptingOnItemTouchListener.onTouchEvent(this, e);
          final int action = e.getAction();
          // 一个手势结束后,当前的OnItemTouchListener置null
          if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
              mInterceptingOnItemTouchListener = null;
          }
          return true;
      }
  }

  // 遍历OnItemTouchListener集合的onInterceptTouchEvent是否拦截
  private boolean findInterceptingOnItemTouchListener(MotionEvent e) {
      int action = e.getAction();
       // 遍历onItemTouchListener集合
      final int listenerCount = mOnItemTouchListeners.size();
      for (int i = 0; i           final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
          //执行监听实例的onInterceptTouchEvent方法,如果返回ture和手势不是取消事件,则设置当前处理的OnItemTouchListener和返回Ture拦截事件。
          if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
              mInterceptingOnItemTouchListener = listener;
              return true;
          }
      }
      return false;
  }

RecyclerView.onTouchEvent也会先查询是否有当前处理的onItemTouchListener,如果有onItemTouchListener,则调用onItemTouchListener.onTouchEvent,并返回True。如果没有onItemTouchListener,则先判断手势是否为DOWN刚开始则返回false,不是则调用findInterceptingOnItemTouchListener继续监听onItemTouchListener. onInterceptTouchEvent是否有更新。

requestDisallowInterceptTouchEvent[RecyclerView.requestDisallowInterceptTouchEvent]

  @Override
  public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
      // 先遍历OnItemTouchListener集合,调用其onRequestDisallowInterceptTouchEvent是否拦截
      final int listenerCount = mOnItemTouchListeners.size();
      for (int i = 0; i           final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
          listener.onRequestDisallowInterceptTouchEvent(disallowIntercept);
      }
      super.requestDisallowInterceptTouchEvent(disallowIntercept);
  }

requestDisallowInterceptTouchEvent可以向父布局表明是否需求拦截事件。RecylerView.requestDisallowInterceptTouchEvent则先让OnItemTouchListener先决定是否拦截。

RecyclerView重写的三个拦截方法都让OnItemTouchListener托管了,所以我们要控制RecyclerView的拦截以及手势事件只要设置OnItemTouchListener接口监听就可以了。

public interface OnItemTouchListener {
 
      boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e);

      void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e);

      void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept);
  }

  public static class SimpleOnItemTouchListener implements RecyclerView.OnItemTouchListener {
      @Override
      public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
          return false;
      }

      @Override
      public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
      }

      @Override
      public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
      }
  }

不过如果你要控制onTouchEvent事件,要先让onInterceptTouchEvent方法返回true。所以ItemTouchHelper类就是同时给绑定的RecyclerView设置OnItemTouchListener监听来控制RecyclerView的手势事件。

接下来,我们看看ItemTouchHelper是怎样实例化OnItemTouchListener的三个接口方法。

ItemTouchHelper的事件拦截

[ItemTouchHelper.OnItemTouchListener]

  private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() {
   
      @Override
      public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
              @NonNull MotionEvent event) {
           // GestureDetector进行长按监听
          mGestureDetector.onTouchEvent(event);
          if (DEBUG) {
              Log.d(TAG, "intercept: x:" + event.getX() + ",y:" + event.getY() + ", " + event);
          }
          final int action = event.getActionMasked();
          // 手势开始的时候做一个变量的初始化
          if (action == MotionEvent.ACTION_DOWN) {
              mActivePointerId = event.getPointerId(0);
              mInitialTouchX = event.getX();
              mInitialTouchY = event.getY();
              obtainVelocityTracker();
              //mSelected表示当前选中的ViewHolder
              if (mSelected == null) {
                  final RecoverAnimation animation = findAnimation(event);
                  if (animation != null) {
                      mInitialTouchX -= animation.mX;
                      mInitialTouchY -= animation.mY;
                      endRecoverAnimation(animation.mViewHolder, true);
                      if (mPendingCleanup.remove(animation.mViewHolder.itemView)) {
                          mCallback.clearView(mRecyclerView, animation.mViewHolder);
                      }
                      // 设置mSelected的ViewHolder
                      select(animation.mViewHolder, animation.mActionState);
                      updateDxDy(event, mSelectedFlags, 0);
                  }
              }
          } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
              // 当手势离开或取消时,更新PointerId和设置mSelected为null
              mActivePointerId = ACTIVE_POINTER_ID_NONE;
              select(null, ACTION_STATE_IDLE);
          } else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) {  // 手势不是离开或取消或按下时
               // 按下位置
              final int index = event.findPointerIndex(mActivePointerId);
              if (DEBUG) {
                  Log.d(TAG, "pointer index " + index);
              }
              // 检查是否需要侧滑,并通过select()方法设置mSelected
              if (index >= 0) {
                  checkSelectForSwipe(action, event, index);
              }
          }
          // 计算加速度
          if (mVelocityTracker != null) {
              mVelocityTracker.addMovement(event);
          }
          // 是否拦截决定与是否有选中的ViewHolder
          return mSelected != null;
      }

      @Override
      public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent event) {
          // 监听长按事件
          mGestureDetector.onTouchEvent(event);
          if (DEBUG) {
              Log.d(TAG,
                      "on touch: x:" + mInitialTouchX + ",y:" + mInitialTouchY + ", :" + event);
          }
          // 计算加速度
          if (mVelocityTracker != null) {
              mVelocityTracker.addMovement(event);
          }
           //手势已经UP或CANCEL
          if (mActivePointerId == ACTIVE_POINTER_ID_NONE) { 
              return;
          }
          // 检查是否需要侧滑,并通过select()方法设置mSelected
          final int action = event.getActionMasked();
          final int activePointerIndex = event.findPointerIndex(mActivePointerId);
          if (activePointerIndex >= 0) {
              checkSelectForSwipe(action, event, activePointerIndex);
          }
          ViewHolder viewHolder = mSelected;
          if (viewHolder == null) {
              return;
          }
          switch (action) {
              case MotionEvent.ACTION_MOVE: {
                  // Find the index of the active pointer and fetch its position
                  if (activePointerIndex >= 0) {
                      updateDxDy(event, mSelectedFlags, activePointerIndex);
                      // 检查是否为长按拖动
                      moveIfNecessary(viewHolder);
                      mRecyclerView.removeCallbacks(mScrollRunnable);
                      mScrollRunnable.run();
                      mRecyclerView.invalidate();
                  }
                  break;
              }
              case MotionEvent.ACTION_CANCEL:
                  if (mVelocityTracker != null) {
                      mVelocityTracker.clear();
                  }
                  // fall through
              case MotionEvent.ACTION_UP:
                   // 设置mSelected为Null
                  select(null, ACTION_STATE_IDLE);
                  mActivePointerId = ACTIVE_POINTER_ID_NONE;
                  break;
              case MotionEvent.ACTION_POINTER_UP: {
                  // 多指中的一个抬起,更新相关变量id
                  final int pointerIndex = event.getActionIndex();
                  final int pointerId = event.getPointerId(pointerIndex);
                  if (pointerId == mActivePointerId) {
                      // This was our active pointer going up. Choose a new
                      // active pointer and adjust accordingly.
                      final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                      mActivePointerId = event.getPointerId(newPointerIndex);
                      updateDxDy(event, mSelectedFlags, pointerIndex);
                  }
                  break;
              }
          }
      }

      @Override
      public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
          if (!disallowIntercept) {
              return;
          }
          // 重新设置mSelected为Null
          select(null, ACTION_STATE_IDLE);
      }
  };

这里的mSelected代表选中的Item的ViewHolder,如果为null表示没有选中的Item。

onInterceptTouchEvent方法在DOWN时更新一些变量,并在mSelected == null时根据event的点击坐标找到对应Item的动画,如果动画不为null,则调用select方法设置mSelected。在CANCEL或UP时设置mSelected为null,并将手势id设置为ACTIVE_POINTER_ID_NONE 。最后在手势id不等于ACTIVE_POINTER_ID_NONE时调用checkSelectForSwipe()方法查看是否需要侧滑。最后根据选中的ViewHolder的mSelected变量是否为null来决定是否拦截。

onTouchEvent首先调用checkSelectForSwipe方法是否需要侧滑,然后在MOVE时调用moveIfNecessary()方法是否需要长按拖动。

onRequestDisallowInterceptTouchEvent方法则是在表明要拦截事件时,将mSelected设置为null。

**总结:**在onInterceptTouchEvent方法中在非CANCEL和UP手势时,调用checkSelectForSwipe查询是否为侧滑。在onTouchEvent有执行时就表示mSelected != null,先是调用checkSelectForSwipe方法查询是否为侧滑,然后在MOVE手势时调用moveIfNecessary方法查询是否为长按拖动。

接下来就看看checkSelectForSwipe和moveIfNecessary的两个方法。

侧滑和长按拖动的调用时机

SWIPE侧滑触发ItemTouchHelper在onInterceptTouchEvent和onTouchEvent在事件开始时调用checkSelectForSwipe()检查SWIP。

void checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) {
  // 已选中ViewHolder、手势不是MOVE、状态不是DRAG、接口定义不可以侧滑
  if (mSelected != null || action != MotionEvent.ACTION_MOVE
          || mActionState == ACTION_STATE_DRAG || !mCallback.isItemViewSwipeEnab
      return;
  }
 // recyclerView在滚动
  if (mRecyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) {
      return;
  }
  // 根据手势位置找到可侧滑的ViewHolder,手势侧滑的方向不可以与RecyclerView可滑动的方向相同
  final ViewHolder vh = findSwipedView(motionEvent);
  if (vh == null) {
      return;
  }
  // 询问接口返回可以侧滑和拖动的手势方向
  final int movementFlags = mCallback.getAbsoluteMovementFlags(mRecyclerView, vh)
  // 接口定义的侧滑手势的方向
  final int swipeFlags = (movementFlags & ACTION_MODE_SWIPE_MASK)
          >> (DIRECTION_FLAG_COUNT * ACTION_STATE_SWIPE);
  if (swipeFlags == 0) {
      return;
  }
 // 最后点位置
  final float x = motionEvent.getX(pointerIndex);
  final float y = motionEvent.getY(pointerIndex);
   // x,y方向滑动的距离
  final float dx = x - mInitialTouchX;
  final float dy = y - mInitialTouchY;
  final float absDx = Math.abs(dx);
  final float absDy = Math.abs(dy);
  if (absDx       return;
  }
  if (absDx > absDy) {
      // 手指向左滑
      if (dx           return;
      }
      // 手指向右滑
      if (dx > 0 && (swipeFlags & RIGHT) == 0) {
          return;
      }
  } else {
      // 手指向上滑
      if (dy           return;
      }
      // 手指向下滑
      if (dy > 0 && (swipeFlags & DOWN) == 0) {
          return;
      }
  }
  mDx = mDy = 0f;
  mActivePointerId = motionEvent.getPointerId(0);
  //设置mSelect,并把状态设置为SWIPE
  select(vh, ACTION_STATE_SWIPE);
}

checkSelectForSwipe方法就做两件事:1.mSelected为null或MOVE时才往下进行。2.检查手势的滑动跟RecyclerView的滑动方向CallBack定义的侧滑方向是否有冲突,都没有则调用select方法:设置mSelected,并且为SWIPE侧滑状态。

DRAG长按拖动触发ItemTouchEvent通过GestureDetector实例检测MotionEvent的长按操作,从而触发拖动操作。

  private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {

      private boolean mShouldReactToLongPress = true;

      ItemTouchHelperGestureListener() {
      }

      void doNotReactToLongPress() {
          mShouldReactToLongPress = false;
      }

      @Override
      public boolean onDown(MotionEvent e) {
          return true;
      }

      @Override
      public void onLongPress(MotionEvent e) {
          if (!mShouldReactToLongPress) {
              return;
          }
          // 手势坐标对应的View
          View child = findChildView(e);
          if (child != null) {
              // View的ViewHolder
              ViewHolder vh = mRecyclerView.getChildViewHolder(child);
              if (vh != null) {
                  // 检查CallBack是否有设置Drag方向
                  if (!mCallback.hasDragFlag(mRecyclerView, vh)) {
                      return;
                  }
                  int pointerId = e.getPointerId(0);
                  // Long press is deferred.
                  // Check w/ active pointer id to avoid selecting after motion
                  // event is canceled.
                  if (pointerId == mActivePointerId) {
                      final int index = e.findPointerIndex(mActivePointerId);
                      final float x = e.getX(index);
                      final float y = e.getY(index);
                      mInitialTouchX = x;
                      mInitialTouchY = y;
                      mDx = mDy = 0f;
                      if (DEBUG) {
                          Log.d(TAG,
                                  "onlong press: x:" + mInitialTouchX + ",y:" + mInitialTouchY);
                      }
                       // 接口是否支持Drag,默认支持
                      if (mCallback.isLongPressDragEnabled()) {
                          //设置mSelected,并且状态为DRAG
                          select(vh, ACTION_STATE_DRAG);
                      }
                  }
              }
          }
      }
  }

onLongPress长按回调方法中,先通过手势坐标找到ViewHolder,然后在检查CallBack是否设置了Drag方向是否能支持Drag

通过以上代码,我们知道了checkSelectForSwipeGestureDetector. onLongPress两个方法分别确定了侧滑SWIPE和长按拖动DRAG的触发时机。但这只是开胃菜,接下来看看select方法是怎样实现侧滑和长按拖动

侧滑和长按拖动的实现

在上面的分析得知,在侧滑和拖动触发的时候等会调用一个select(ViewHolder selected, int actionState)方法,我们看看这个方法究竟做了什么?

  void select(@Nullable ViewHolder selected, int actionState) {
      if (selected == mSelected && actionState == mActionState) {
          return;
      }
      mDragScrollStartTimeInMs = Long.MIN_VALUE;
      final int prevActionState = mActionState;
      // 新传来的ViewHolder防止重复动画
      endRecoverAnimation(selected, true);
      mActionState = actionState; // 赋值状态
      // 如果是拖动,在<21的版本要设置RecyclerView的ChildDrawingOrderCallback监听
      if (actionState == ACTION_STATE_DRAG) {
          if (selected == null) {
              throw new IllegalArgumentException("Must pass a ViewHolder when dragging");
          }

          mOverdrawChild = selected.itemView;  // 在上面绘制的View
          addChildDrawingOrderCallback();   // 设置
      }
      int actionStateMask = (1 <              - 1;
      boolean preventLayout = false;

      // 对于原先有ViewHolder的情况,则要对之前的ViewHolder进行结束处理。
      if (mSelected != null) {
          final ViewHolder prevSelected = mSelected;  // 原有之前的ViewHolder
          // ItemView还吸附在RecyclerView
          if (prevSelected.itemView.getParent() != null) {
              // 如果之前是拖动则为0,如果是之前状态是侧滑,则计算加速度是否大于阀值,大于则返回需要滑动的距离
              final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0
                      : swipeIfNecessary(prevSelected);
              releaseVelocityTracker();  // 释放VelocityTracker
              // find where we should animate to
              final float targetTranslateX, targetTranslateY;
              int animationType;
              // 根据加速度的方向,将ItemView滑到边界。
              switch (swipeDir) {
                  case LEFT:
                  case RIGHT:
                  case START:
                  case END:
                      targetTranslateY = 0;
                      targetTranslateX = Math.signum(mDx) * mRecyclerView.getWidth();
                      break;
                  case UP:
                  case DOWN:
                      targetTranslateX = 0;
                      targetTranslateY = Math.signum(mDy) * mRecyclerView.getHeight();
                      break;
                  default:
                      targetTranslateX = 0;
                      targetTranslateY = 0;
              }
              if (prevActionState == ACTION_STATE_DRAG) {
                  animationType = ANIMATION_TYPE_DRAG;
              } else if (swipeDir > 0) {
                  animationType = ANIMATION_TYPE_SWIPE_SUCCESS;  //滑动成功
              } else {
                  animationType = ANIMATION_TYPE_SWIPE_CANCEL; //滑动取消
              }
              // 调用动画结束之前ViewHolder的操作
              getSelectedDxDy(mTmpPosition);
              final float currentTranslateX = mTmpPosition[0];
              final float currentTranslateY = mTmpPosition[1];
              final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType,
                      prevActionState, currentTranslateX, currentTranslateY,
                      targetTranslateX, targetTranslateY) {
                  @Override
                  public void onAnimationEnd(Animator animation) {
                      super.onAnimationEnd(animation);
                      if (this.mOverridden) {
                          return;
                      }
                      if (swipeDir <= 0) {
                          // this is a drag or failed swipe. recover immediately
                          mCallback.clearView(mRecyclerView, prevSelected);
                          // full cleanup will happen on onDrawOver
                      } else {
                          // wait until remove animation is complete.
                          mPendingCleanup.add(prevSelected.itemView);
                          mIsPendingCleanup = true;
                          if (swipeDir > 0) {
                              // 动画结束,调用CallBack.onSwiped()
                              postDispatchSwipe(this, swipeDir);
                          }
                      }
                      // removed from the list after it is drawn for the last time
                      if (mOverdrawChild == prevSelected.itemView) {
                          removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
                      }
                  }
              };
              final long duration = mCallback.getAnimationDuration(mRecyclerView, animationType,
                      targetTranslateX - currentTranslateX, targetTranslateY - currentTranslateY);
              rv.setDuration(duration);
              mRecoverAnimations.add(rv);
              rv.start();  // 开始动画
              preventLayout = true;
          } else {
             // ItemView已经脱离了ReccyclerView,则将监听回收
              removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
              mCallback.clearView(mRecyclerView, prevSelected);
          }
          // mSelected则为null
          mSelected = null;  
      }
       // 设置新的ViewHolder
      if (selected != null) {
           // 赋值初始变量
          mSelectedFlags =
                  (mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) & actionStateMask)
                          >> (mActionState * DIRECTION_FLAG_COUNT);
          mSelectedStartX = selected.itemView.getLeft();
          mSelectedStartY = selected.itemView.getTop();
          mSelected = selected;

           若是触发DRAG,则给用户一个触觉反馈
          if (actionState == ACTION_STATE_DRAG) {
              mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
          }
      }
      // 根据ViewHolder是否为null设置requestDisallowInterceptTouchEvent
      final ViewParent rvParent = mRecyclerView.getParent();
      if (rvParent != null) {
          rvParent.requestDisallowInterceptTouchEvent(mSelected != null);
      }

      // 之前没有Viewholder, 使RecyclerView在下一次布局时运行SimpleAnimation
      if (!preventLayout) {
          mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout();
      }
       // 调用接口onSelectedChanged和重新绘制
      mCallback.onSelectedChanged(mSelected, mActionState);
      mRecyclerView.invalidate();
  }

通过把代码分析一遍发现,就是先把原先的ViewHolder如果侧滑的加速度大于设置的阀值就让它滚出界面,并进行一些现有已选ViewHolder变量赋值操作,并调用 invalidate进行重绘,看来这里也没有真正实现侧滑和拖动效果。

ItemTouchHelper是实现了RecyclerView.ItemDecoration,看看它的实现。

  public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
          RecyclerView.State state) {
      // 不设置边距
      outRect.setEmpty();
  }

  @Override
  public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
      // we don't know if RV changed something so we should invalidate this index.
      mOverdrawChildPosition = -1;
      // 赋值偏移量
      float dx = 0, dy = 0;
      if (mSelected != null) {
          getSelectedDxDy(mTmpPosition);
          dx = mTmpPosition[0];
          dy = mTmpPosition[1];
      }
      // 由Callback执行
      mCallback.onDraw(c, parent, mSelected,
              mRecoverAnimations, mActionState, dx, dy);
  }
  @Override
  public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
      // 计算偏移量
      float dx = 0, dy = 0;
      if (mSelected != null) {
          getSelectedDxDy(mTmpPosition);
          dx = mTmpPosition[0];
          dy = mTmpPosition[1];
      }
      //  由Callback执行
      mCallback.onDrawOver(c, parent, mSelected,
              mRecoverAnimations, mActionState, dx, dy);
  }

通过attachToRecyclerView方法时,ItemTouchHelper就作为ItemDecoration绑定到RecyclerView的ItemDecoration集合中。ItemTouchHelper并没有设置分割线的大小。onDraw、onDrawOver的具体实现也交给了Callback执行。

public abstract static class Callback {

  void onDraw(Canvas c, RecyclerView parent, ViewHolder selected,
          List recoverAnimationList,
          int actionState, float dX, float dY) {
      final int recoverAnimSize = recoverAnimationList.size();for (int i = 0; i           final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i);
          anim.update();
          final int count = c.save();
          onChildDraw(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState,false);
          c.restoreToCount(count);
      }if (selected != null) {
          final int count = c.save();
          onChildDraw(c, parent, selected, dX, dY, actionState, true);
          c.restoreToCount(count);
      }
  }
  void onDrawOver(Canvas c, RecyclerView parent, ViewHolder selected,
          List recoverAnimationList,
          int actionState, float dX, float dY) {
      final int recoverAnimSize = recoverAnimationList.size();for (int i = 0; i           final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i);
          final int count = c.save();
          onChildDrawOver(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState,false);
          c.restoreToCount(count);
      }if (selected != null) {
          final int count = c.save();
          onChildDrawOver(c, parent, selected, dX, dY, actionState, true);
          c.restoreToCount(count);
      }
      boolean hasRunningAnimation = false;for (int i = recoverAnimSize - 1; i >= 0; i--) {
          final RecoverAnimation anim = recoverAnimationList.get(i);if (anim.mEnded && !anim.mIsPendingCleanup) {
              recoverAnimationList.remove(i);
          } else if (!anim.mEnded) {
              hasRunningAnimation = true;
          }
      }if (hasRunningAnimation) {
          parent.invalidate();
      }
  }
}
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
      @NonNull ViewHolder viewHolder,float dX, float dY, int actionState, boolean isCurrentlyActive) {
  ItemTouchUIUtilImpl.INSTANCE.onDraw(c, recyclerView, viewHolder.itemView, dX, dY,
          actionState, isCurrentlyActive);
}
public void onChildDrawOver(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
      ViewHolder viewHolder,float dX, float dY, int actionState, boolean isCurrentlyActive) {
  ItemTouchUIUtilImpl.INSTANCE.onDrawOver(c, recyclerView, viewHolder.itemView, dX, dY,
          actionState, isCurrentlyActive);
}
class ItemTouchUIUtilImpl implements ItemTouchUIUtil {

  @Override
  public void onDraw(Canvas c, RecyclerView recyclerView, View view, float dX, float dY,
          int actionState, boolean isCurrentlyActive) {
      if (Build.VERSION.SDK_INT >= 21) {
          if (isCurrentlyActive) {
              Object originalElevation = view.getTag(R.id.item_touch_helper_previous_elevation);
              if (originalElevation == null) {
                  originalElevation = ViewCompat.getElevation(view);
                  float newElevation = 1f + findMaxElevation(recyclerView, view);
                  ViewCompat.setElevation(view, newElevation);
                  view.setTag(R.id.item_touch_helper_previous_elevation, originalElevation);
              }
          }
      }

      view.setTranslationX(dX);
      view.setTranslationY(dY);
  }

  @Override
  public void onDrawOver(Canvas c, RecyclerView recyclerView, View view, float dX, float dY,
          int actionState, boolean isCurrentlyActive) {
  }
}

从最终的方法知道,是通过不断的setTranslationX,setTranslationY和移动或拖动ItemView。

什么时候调用CallBack的onSwiped、onMove方法

onSwiped在select方法结束动画之后调用:

void select(@Nullable ViewHolder selected, int actionState) {
  if (mSelected != null) {
      final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType,
              prevActionState, currentTranslateX, currentTranslateY,
              targetTranslateX, targetTranslateY) {
          @Override
          public void onAnimationEnd(Animator animation) {
              super.onAnimationEnd(animation);
              if (this.mOverridden) {
                  return;
              }
              if (swipeDir <= 0) {
                  mCallback.clearView(mRecyclerView, prevSelected);
              } else {
                  if (swipeDir > 0) {
                       // 调用CallBack.onSwiped()
                      postDispatchSwipe(this, swipeDir);
                  }
              }
          }
      };
  }
}

  void postDispatchSwipe(final RecoverAnimation anim, final int swipeDir) {
      // wait until animations are complete.
      mRecyclerView.post(new Runnable() {
          @Override
          public void run() {
              if (mRecyclerView != null && mRecyclerView.isAttachedToWindow()
                      && !anim.mOverridden
                      && anim.mViewHolder.getAbsoluteAdapterPosition()
                      != RecyclerView.NO_POSITION) {
                  final RecyclerView.ItemAnimator animator = mRecyclerView.getItemAnimator();
                  // if animator is running or we have other active recover animations, we try
                  // not to call onSwiped because DefaultItemAnimator is not good at merging
                  // animations. Instead, we wait and batch.
                  if ((animator == null || !animator.isRunning(null))
                          && !hasRunningRecoverAnim()) {
                      mCallback.onSwiped(anim.mViewHolder, swipeDir);
                  } else {
                      mRecyclerView.post(this);
                  }
              }
          }
      });
  }

onMove在moveIfNecessary方法中调用:

   */
  @SuppressWarnings("WeakerAccess") /* synthetic access */
  void moveIfNecessary(ViewHolder viewHolder) {
      if (mRecyclerView.isLayoutRequested()) {
          return;
      }
      if (mActionState != ACTION_STATE_DRAG) {
          return;
      }

      final float threshold = mCallback.getMoveThreshold(viewHolder);
      final int x = (int) (mSelectedStartX + mDx);
      final int y = (int) (mSelectedStartY + mDy);
      if (Math.abs(y - viewHolder.itemView.getTop())               && Math.abs(x - viewHolder.itemView.getLeft())
                        return;
      }
      List swapTargets = findSwapTargets(viewHolder);if (swapTargets.size() == 0) {return;
      }
      // may swap.
      ViewHolder target = mCallback.chooseDropTarget(viewHolder, swapTargets, x, y);if (target == null) {
          mSwapTargets.clear();
          mDistances.clear();return;
      }
      final int toPosition = target.getAbsoluteAdapterPosition();
      final int fromPosition = viewHolder.getAbsoluteAdapterPosition();if (mCallback.onMove(mRecyclerView, viewHolder, target)) {
          // keep target visible
          mCallback.onMoved(mRecyclerView, viewHolder, fromPosition,
                  target, toPosition, x, y);
      }
  }
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值