二、ItemTouchHelper 涉及到的本博客相关源码
public class ItemTouchHelper extends RecyclerView.ItemDecoration implements RecyclerView.OnChildAttachStateChangeListener { /** * Currently selected view holder */ @SuppressWarnings("WeakerAccess") /* synthetic access */ ViewHolder mSelected = null; /** * The diff between the last event and initial touch. * 最后的触摸事件和初始触摸事件之间的坐标差异 , 偏移值 . */ float mDx; float mDy; private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() { // 拦截触摸事件 , 处理拦截机制 @Override public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent event) { mGestureDetector.onTouchEvent(event); if (DEBUG) { Log.d(TAG, "intercept: x:" + event.getX() + ",y:" + event.getY() + ", " + event); } final int action = event.getActionMasked(); // 注意此处拦截的动作 , 只拦截 DOWN / UP / CANCEL 三种动作 , MOVE 动作不拦截 // 取消操作很少遇到 // 因此 , 拦截机制中 , 只负责拦截手指按下 和 抬起 操作 // 在 ItemTouchHelper 的业务逻辑中 , 不需要处理移动事件 if (action == MotionEvent.ACTION_DOWN) { // 按下操作 , 得到初始 XY 坐标位置 mActivePointerId = event.getPointerId(0); mInitialTouchX = event.getX(); mInitialTouchY = event.getY(); // 滑动速度检测 obtainVelocityTracker(); // mSelected 是当前正在点击的条目的 ViewHolder if (mSelected == null) { // 恢复动画 , 查找手指按下的 View 子组件 , 该子组件时 RecyclerView 中的一个条目 // 用户按下 RecyclerView 中的某个条目 // findAnimation 方法用于找到按下的条目 View , 并设置给 RecoverAnimation 恢复动画 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); } // 为动画选择 item 项 select(animation.mViewHolder, animation.mActionState); // 计算当前移动的位置 , 初始位置 与 最后一次事件的位置 偏移值 . updateDxDy(event, mSelectedFlags, 0); } } } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { mActivePointerId = ACTIVE_POINTER_ID_NONE; // 抬起 / 取消 时 , 选择项 置空 . select(null, ACTION_STATE_IDLE); } else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) { // 该分支表示滑动操作完成的分支 // ACTIVE_POINTER_ID_NONE 表示是否完成了滑动 // 如果滑动完成 , 触发了侧滑事件 , 才会进入该分支 // 如果滑动没有完成 , 滑到半路 , 松开手 , 条目组件缩回去了 , 则不会进入该分支 // in a non scroll orientation, if distance change is above threshold, we // can select the item // 滑动完成后 , 记录当前的触摸指针索引 final int index = event.findPointerIndex(mActivePointerId); if (DEBUG) { Log.d(TAG, "pointer index " + index); } if (index >= 0) { // 检查是否完成了滑动操作 checkSelectForSwipe(action, event, index); } } if (mVelocityTracker != null) { mVelocityTracker.addMovement(event); } return mSelected != null; } // 处理最终的事件消费 // 只处理手指滑动操作 MotionEvent.ACTION_MOVE @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); } if (mActivePointerId == ACTIVE_POINTER_ID_NONE) { return; } final int action = event.getActionMasked(); final int activePointerIndex = event.findPointerIndex(mActivePointerId); if (activePointerIndex >= 0) { checkSelectForSwipe(action, event, activePointerIndex); } // 按下第一次后 , mSelected 便进行赋值 // 有了 mSelected 值后 , 开始在滑动中处理 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: // 抬起操作 select(null, ACTION_STATE_IDLE); mActivePointerId = ACTIVE_POINTER_ID_NONE; break; case MotionEvent.ACTION_POINTER_UP: { 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; } select(null, ACTION_STATE_IDLE); } }; // 该方法作用 : // 用户按下 RecyclerView 中的某个条目 // 该方法用于找到按下的条目 View , 并设置给 RecoverAnimation 恢复动画 @SuppressWarnings("WeakerAccess") /* synthetic access */ RecoverAnimation findAnimation(MotionEvent event) { if (mRecoverAnimations.isEmpty()) { return null; } // 找到手指按下所在位置的条目的 View 组件 // 查找手指按下的 View 子组件 , 该子组件时 RecyclerView 中的一个条目 View target = findChildView(event); // 遍历恢复动画 // 动画中有 mViewHolder 成员 , mViewHolder 中有 itemView 成员 // 设置 anim.mViewHolder.itemView 为手指按下的子组件 // 即设置该动画作用于 RecyclerView 的哪个条目上 ; for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) { final RecoverAnimation anim = mRecoverAnimations.get(i); if (anim.mViewHolder.itemView == target) { return anim; } } return null; } @SuppressWarnings("WeakerAccess") /* synthetic access */ View findChildView(MotionEvent event) { // first check elevated views, if none, then call RV // 根据按下的 X, Y 坐标 , 查找对应的条目 final float x = event.getX(); final float y = event.getY(); // 如果 mSelected 成员不为空 , 则直接使用 , 分支中直接返回了 if (mSelected != null) { final View selectedView = mSelected.itemView; if (hitTest(selectedView, x, y, mSelectedStartX + mDx, mSelectedStartY + mDy)) { return selectedView; } } // 如果 mSelected 为空 , 则开始遍历进行检测 for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) { final RecoverAnimation anim = mRecoverAnimations.get(i); final View view = anim.mViewHolder.itemView; // 根据当前按下的坐标 , 找到列表条目对应的 View 组件 if (hitTest(view, x, y, anim.mX, anim.mY)) { return view; } } return mRecyclerView.findChildViewUnder(x, y); } // 判断删上下左右边距 是否在对应子组件范围内 private static boolean hitTest(View child, float x, float y, float left, float top) { return x >= left && x <= left + child.getWidth() && y >= top && y <= top + child.getHeight(); } /** * Starts dragging or swiping the given View. Call with null if you want to clear it. * 开始拖动/滑动给定的 View 组件. 如果想要清除传入 null. * 为动画选择 item 项 * 该方法中进行一系列的计算 * * @param selected The ViewHolder to drag or swipe. Can be null if you want to cancel the * current action, but may not be null if actionState is ACTION_STATE_DRAG. * @param actionState The type of action */ @SuppressWarnings("WeakerAccess") /* synthetic access */ void select(@Nullable ViewHolder selected, int actionState) { if (selected == mSelected && actionState == mActionState) { return; } mDragScrollStartTimeInMs = Long.MIN_VALUE; final int prevActionState = mActionState; // prevent duplicate animations endRecoverAnimation(selected, true); mActionState = actionState; if (actionState == ACTION_STATE_DRAG) { if (selected == null) { throw new IllegalArgumentException("Must pass a ViewHolder when dragging"); } // we remove after animation is complete. this means we only elevate the last drag // child but that should perform good enough as it is very hard to start dragging a // new child before the previous one settles. mOverdrawChild = selected.itemView; addChildDrawingOrderCallback(); } int actionStateMask = (1 << (DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT * actionState)) - 1; boolean preventLayout = false; if (mSelected != null) { final ViewHolder prevSelected = mSelected; if (prevSelected.itemView.getParent() != null) { final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0 : swipeIfNecessary(prevSelected); releaseVelocityTracker(); // find where we should animate to final float targetTranslateX, targetTranslateY; int animationType; 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; } 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) { // Animation might be ended by other animators during a layout. // We defer callback to avoid editing adapter during a layout. 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 { removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView); mCallback.clearView(mRecyclerView, prevSelected); } mSelected = null; } if (selected != null) { mSelectedFlags = (mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) & actionStateMask) >> (mActionState * DIRECTION_FLAG_COUNT); mSelectedStartX = selected.itemView.getLeft(); mSelectedStartY = selected.itemView.getTop(); mSelected = selected; if (actionState == ACTION_STATE_DRAG) { mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } } final ViewParent rvParent = mRecyclerView.getParent(); if (rvParent != null) { rvParent.requestDisallowInterceptTouchEvent(mSelected != null); } if (!preventLayout) { mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout(); } mCallback.onSelectedChanged(mSelected, mActionState); mRecyclerView.invalidate(); } // 计算当前移动的位置 @SuppressWarnings("WeakerAccess") /* synthetic access */ void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) { final float x = ev.getX(pointerIndex); final float y = ev.getY(pointerIndex); // Calculate the distance moved mDx = x - mInitialTouchX; mDy = y - mInitialTouchY; if ((directionFlags & LEFT) == 0) { mDx = Math.max(0, mDx); } if ((directionFlags & RIGHT) == 0) { mDx = Math.min(0, mDx); } if ((directionFlags & UP) == 0) { mDy = Math.max(0, mDy); } if ((directionFlags & DOWN) == 0) { mDy = Math.min(0, mDy); } } /** * Checks if we should swap w/ another view holder. * 拖动事件判定 , 一般是拖动交换条目组件 */ @SuppressWarnings("WeakerAccess") /* synthetic access */ void moveIfNecessary(ViewHolder viewHolder) { if (mRecyclerView.isLayoutRequested()) { return; } if (mActionState != ACTION_STATE_DRAG) { return; } // 该方法就是 开发者 自定义 Callback 中的 // public float getMoveThreshold(@NonNull RecyclerView.ViewHolder viewHolder) // 方法的作用是设置 拖动幅度 // 组件在宽度 / 高度 上移动超过该比例 , 就认为拖动触发, 执行拖动相关操作 // 拖动多少系数 , 才算完成 拖动操作 final float threshold = mCallback.getMoveThreshold(viewHolder); final int x = (int) (mSelectedStartX + mDx); final int y = (int) (mSelectedStartY + mDy); // 在该判断中 , 使用了 threshold 系数 乘以 水平 / 垂直 方向上的条目组件宽度 ; // 如果拖动比例超过在 水平 / 垂直 方向上的条目组件 宽度 / 高度 乘以 threshold 的值 // 则拖动判定成功 , 执行响应的方法 // 如果拖动比例没有超过该值 , 说明没有触发拖动操作 , 直接返回 if (Math.abs(y - viewHolder.itemView.getTop()) < viewHolder.itemView.getHeight() * threshold && Math.abs(x - viewHolder.itemView.getLeft()) < viewHolder.itemView.getWidth() * threshold) { return; } // 执行到此处说明拖动判定成功 List<ViewHolder> 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.getAdapterPosition(); // 获取被拖动的起始位置 final int fromPosition = viewHolder.getAdapterPosition(); // 拖动判定成功 , 调用开发者实现的 Callback 的 // public boolean onMove(@NonNull RecyclerView recyclerView, // @NonNull RecyclerView.ViewHolder viewHolder, // @NonNull RecyclerView.ViewHolder target) 方法 // 一般是拖动交换数据 if (mCallback.onMove(mRecyclerView, viewHolder, target)) { // keep target visible mCallback.onMoved(mRecyclerView, viewHolder, fromPosition, target, toPosition, x, y); } } }