ItemTouchHelper(二)源码简析

写在前面的几句话

<p>
上一篇文章已经对ItemTouchHelper的简单使用有了介绍,那么这一篇我们对ItemTouchHelper进行更加深入的了解,对源码进行简单分析

ItemTouchHelper.CallBack分析

<p>
分析ItemTouchHelper之前,我们先看下CallBack的定义了那些方法

//声明不同状态下可以移动的方向(idle, swiping, dragging)
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
    final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
    final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
    return makeMovementFlags(dragFlags, swipeFlags);
}

//拖动的项目从旧位置移动到新位置时调用
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
    return false;
}

//滑动到消失后的调用
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}


//是否可以把拖动的ViewHolder拖动到目标ViewHolder之上
@Override
public boolean canDropOver(RecyclerView recyclerView, RecyclerView.ViewHolder current, RecyclerView.ViewHolder target) {
    return true;
}

//获取拖动
@Override
public RecyclerView.ViewHolder chooseDropTarget(RecyclerView.ViewHolder selected, List<RecyclerView.ViewHolder> dropTargets, int curX, int curY) {
    return dropTargets.get(0);
}

//调用时与元素的用户交互已经结束,也就是它也完成了它的动画时候
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
    super.clearView(recyclerView, viewHolder);
}

@Override
public int convertToAbsoluteDirection(int flags, int layoutDirection) {
    return super.convertToAbsoluteDirection(flags, layoutDirection);
}


//设置手指离开后ViewHolder的动画时间
@Override
public long getAnimationDuration(RecyclerView recyclerView, int animationType, float animateDx, float animateDy) {
    return super.getAnimationDuration(recyclerView, animationType, animateDx, animateDy);
}


@Override
public int getBoundingBoxMargin() {
    return super.getBoundingBoxMargin();
}


//返回值作为用户视为拖动的距离
@Override
public float getMoveThreshold(RecyclerView.ViewHolder viewHolder) {
    return super.getMoveThreshold(viewHolder);
}

//返回值滑动消失的距离,滑动小于这个值不消失,大于消失
@Override
public float getSwipeEscapeVelocity(float defaultValue) {
    return super.getSwipeEscapeVelocity(defaultValue);
}

//返回值滑动消失的距离, 这里是相对于RecycleView的宽度,0.5f表示为RecycleView的宽度的一半,取值为0~1f之间
@Override
public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {
    return super.getSwipeThreshold(viewHolder);
}

//返回值作为滑动的流程程度,越小越难滑动,越大越好滑动
@Override
public float getSwipeVelocityThreshold(float defaultValue) {
    return 1f;
}


//当用户拖动一个视图出界的ItemTouchHelper调用
@Override
public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, int viewSizeOutOfBounds, int totalSize, long msSinceStartScroll) {
    return super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll);
}


//返回值决定是否有滑动操作
@Override
public boolean isItemViewSwipeEnabled() {
    return super.isItemViewSwipeEnabled();
}

//返回值决定是否有拖动操作
@Override
public boolean isLongPressDragEnabled() {
    return super.isLongPressDragEnabled();
}

//自定义拖动与滑动交互
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
    super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}

//自定义拖动与滑动交互
@Override
public void onChildDrawOver(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
    super.onChildDrawOver(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}


//当onMove return ture的时候调用
@Override
public void onMoved(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, int fromPos, RecyclerView.ViewHolder target, int toPos, int x, int y) {
    super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y);
}

//当拖动或者滑动的ViewHolder改变时调用
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
    super.onSelectedChanged(viewHolder, actionState);
}

结合这些分析注释就明白了上面一篇文章里面CallBack为什么写那些方法了,还有部分的方法还是没有理解到底是干嘛的所有就没有注释了。

ItemTouchHelper相关分析

<p>
上一篇文章中把ItemTouchHelper与RecycleView以及CallBack建立连接的方法如下

ItemTouchHelper mItemTouchHelper = new ItemTouchHelper(new ItemTouchHelperCallback());
mItemTouchHelper.attachToRecyclerView(mRecyclerView);

那么从这个将ItemTouchHelper与RecycleView建立的方法进行分析

public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
    if (mRecyclerView == recyclerView) {
        return; // nothing to do
    }
    if (mRecyclerView != null) {
        destroyCallbacks();
    }
    mRecyclerView = recyclerView;
    if (mRecyclerView != 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();
    }
}

这部分的代码其实没有做太多的事情,无非是获取一些默认值,setupCallbacks()与destroyCallbacks()两个方法,这两个方法从名称看就是相对立的,所以分析一个就好了

destroyCallbacks()

private void destroyCallbacks() {
    mRecyclerView.removeItemDecoration(this);
    mRecyclerView.removeOnItemTouchListener(mOnItemTouchListener);
    mRecyclerView.removeOnChildAttachStateChangeListener(this);
    // clean all attached
    final int recoverAnimSize = mRecoverAnimations.size();
    for (int i = recoverAnimSize - 1; i >= 0; i--) {
        final RecoverAnimation recoverAnimation = mRecoverAnimations.get(0);
        mCallback.clearView(mRecyclerView, recoverAnimation.mViewHolder);
    }
    mRecoverAnimations.clear();
    mOverdrawChild = null;
    mOverdrawChildPosition = -1;
    releaseVelocityTracker();
}

setupCallbacks()

private void setupCallbacks() {
    ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());
    mSlop = vc.getScaledTouchSlop();
    mRecyclerView.addItemDecoration(this);
    mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
    mRecyclerView.addOnChildAttachStateChangeListener(this);
    initGestureDetector();
}

这里的步骤有点多了

分布来说明

1.addItemDecoration(this)

这个方法其实是调用了ItemDecoration的接口.从ItemTouchHelper方法声明部分也可以看到

public class ItemTouchHelper extends RecyclerView.ItemDecoration
        implements RecyclerView.OnChildAttachStateChangeListener {}

在ItemTouchHelper中重写了ItemDecoration接口的两个方法如下

@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];
    }
    mCallback.onDrawOver(c, parent, mSelected,
            mRecoverAnimations, mActionState, dx, dy);
}

@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];
    }
    mCallback.onDraw(c, parent, mSelected,
            mRecoverAnimations, mActionState, dx, dy);
}

从方法可以看到这里其实没有做什么特别的工作,只是回调了Callback的两个回调方法onDrawOver()与onDraw()而这两个方法是Callback的private方法如下

private void onDraw(Canvas c, RecyclerView parent, ViewHolder selected,
        List<ItemTouchHelper.RecoverAnimation> recoverAnimationList,
        int actionState, float dX, float dY) {
    final int recoverAnimSize = recoverAnimationList.size();
    for (int i = 0; i < recoverAnimSize; 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);
    }
}

private void onDrawOver(Canvas c, RecyclerView parent, ViewHolder selected,
        List<ItemTouchHelper.RecoverAnimation> recoverAnimationList,
        int actionState, float dX, float dY) {
    final int recoverAnimSize = recoverAnimationList.size();
    for (int i = 0; i < recoverAnimSize; 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();
    }
}

这里牵扯的东西比较多,暂时不分析,我们可以看到有两个方法分别为onChildDrawOver()与onChildDraw()

调用了这两个方法,接下来看下这两个方法里面有什么?

public void onChildDraw(Canvas c, RecyclerView recyclerView,
        ViewHolder viewHolder,
        float dX, float dY, int actionState, boolean isCurrentlyActive) {
    sUICallback.onDraw(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
            isCurrentlyActive);
}

public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
        ViewHolder viewHolder,
        float dX, float dY, int actionState, boolean isCurrentlyActive) {
    sUICallback.onDrawOver(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
            isCurrentlyActive);
}

这里面其实是sUICallback的回调方法

这里的sUICallback是一个接口,根据不同的版本执行不同的onDraw与onDrawOver方法

static {
    if (Build.VERSION.SDK_INT >= 21) {
        sUICallback = new ItemTouchUIUtilImpl.Lollipop();
    } else if (Build.VERSION.SDK_INT >= 11) {
        sUICallback = new ItemTouchUIUtilImpl.Honeycomb();
    } else {
        sUICallback = new ItemTouchUIUtilImpl.Gingerbread();
    }
}

所以在我们自定义的CallBack中可以取重写onChildDraw()onChildDrawOver()方法来实现自定义的拖动与滑动交互

2.addOnChildAttachStateChangeListener(this)

这里调用了OnChildAttachStateChangeListener这个接口,这个接口里有两个方法,分别是在RecycleView添加一个View与删除一个View的时候回调

那看看我们在ItemTouchHelper中重写的方法

@Override
public void onChildViewAttachedToWindow(View view) {
}

@Override
public void onChildViewDetachedFromWindow(View view) {
    removeChildDrawingOrderCallbackIfNecessary(view);
    final ViewHolder holder = mRecyclerView.getChildViewHolder(view);
    if (holder == null) {
        return;
    }
    if (mSelected != null && holder == mSelected) {
        select(null, ACTION_STATE_IDLE);
    } else {
        endRecoverAnimation(holder, false); // this may push it into pending cleanup list.
        if (mPendingCleanup.remove(holder.itemView)) {
            mCallback.clearView(mRecyclerView, holder);
        }
    }
}

因为ItemTouchHelper中只用考虑移除的情况,

这里面的方法暂时不介绍,可以看到回调了clearView()的方法,所以在元素的用户交互已经结束的时候,可以通过这个方法监听到

3.initGestureDetector()

这里主要是初始化GestureDetector

private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {

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

    @Override
    public void onLongPress(MotionEvent e) {
        View child = findChildView(e);
        if (child != null) {
            ViewHolder vh = mRecyclerView.getChildViewHolder(child);
            if (vh != null) {
                if (!mCallback.hasDragFlag(mRecyclerView, vh)) {
                    return;
                }
                int pointerId = MotionEventCompat.getPointerId(e, 0);
                // Long press is deferred.
                // Check w/ active pointer id to avoid selecting after motion
                // event is canceled.
                if (pointerId == mActivePointerId) {
                    final int index = MotionEventCompat
                            .findPointerIndex(e, mActivePointerId);
                    final float x = MotionEventCompat.getX(e, index);
                    final float y = MotionEventCompat.getY(e, index);
                    mInitialTouchX = x;
                    mInitialTouchY = y;
                    mDx = mDy = 0f;
                    if (DEBUG) {
                        Log.d(TAG,
                                "onlong press: x:" + mInitialTouchX + ",y:" + mInitialTouchY);
                    }
                    if (mCallback.isLongPressDragEnabled()) {
                        select(vh, ACTION_STATE_DRAG);
                    }
                }
            }
        }
    }
}

首先看下Callback调用了hasDragFlag这个方法,那我们看下这个方法

private boolean hasDragFlag(RecyclerView recyclerView, ViewHolder viewHolder) {
    final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
    return (flags & ACTION_MODE_DRAG_MASK) != 0;
}

final int getAbsoluteMovementFlags(RecyclerView recyclerView,
        ViewHolder viewHolder) {
    final int flags = getMovementFlags(recyclerView, viewHolder);
    return convertToAbsoluteDirection(flags, ViewCompat.getLayoutDirection(recyclerView));
}

经过调用发现最后调用了getMovementFlags这个方法,所以我们重写方法如果是没有声明的在onLongPress中就直接return了,不会触发下面的方法了

再往下看,会调用Callback的isLongPressDragEnabled()方法,当return为true的时候会执行select()方法

4. addOnItemTouchListener

这里则是调用了RecycleView的addOnItemTouchListener方法,ItemTouchHelper重写了OnItemTouchListener接口的方法,OnItemTouchListener有三个方法,我们一个个来进行分析

(1).onInterceptTouchEvent

public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) {
  //给前面注册的GestureDetector添加监听
  mGestureDetector.onTouchEvent(event);
  if (DEBUG) {
      Log.d(TAG, "intercept: x:" + event.getX() + ",y:" + event.getY() + ", " + event);
  }
  final int action = MotionEventCompat.getActionMasked(event);
  if (action == MotionEvent.ACTION_DOWN) {
      //获取这个事件对应的pointerId,ViewDragerHelper中也有说明
      mActivePointerId = MotionEventCompat.getPointerId(event, 0);
      mInitialTouchX = event.getX();
      mInitialTouchY = event.getY();
      //初始化跟踪触摸屏类VelocityTracker
      obtainVelocityTracker();
      if (mSelected == null) {
          //根据当前的MotionEvent查找RecoverAnimation对象
          final RecoverAnimation animation = findAnimation(event);
          //如果animation存在则更新animation
          if (animation != null) {
              mInitialTouchX -= animation.mX;
              mInitialTouchY -= animation.mY;
              //删除RecoverAnimation
              endRecoverAnimation(animation.mViewHolder, true);
              if (mPendingCleanup.remove(animation.mViewHolder.itemView)) {
                  mCallback.clearView(mRecyclerView, animation.mViewHolder);
              }
              //select方法
              select(animation.mViewHolder, animation.mActionState);
              //更新Dx与Dy
              updateDxDy(event, mSelectedFlags, 0);
          }
      }
  } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
      mActivePointerId = ACTIVE_POINTER_ID_NONE;
      //select方法
      select(null, ACTION_STATE_IDLE);
  } else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) {
      // in a non scroll orientation, if distance change is above threshold, we
      // can select the item
      final int index = MotionEventCompat.findPointerIndex(event, mActivePointerId);
      if (DEBUG) {
          Log.d(TAG, "pointer index " + index);
      }
      //index >= 0 表示最少有一个触控点存在
      if (index >= 0) {
          checkSelectForSwipe(action, event, index);
      }
  }
  if (mVelocityTracker != null) {
      mVelocityTracker.addMovement(event);
  }
  return mSelected != null;
}

大部分注释都已经说明了,主要把他们调用的方法来进行说明

首先来看下RecoverAnimation这个类,这个类中有ValueAnimatorCompat主要是根据起始点及ActionState等做动画的。

接下来就贴出这个类

private class RecoverAnimation implements AnimatorListenerCompat {

    final float mStartDx;

    final float mStartDy;

    final float mTargetX;

    final float mTargetY;

    final ViewHolder mViewHolder;

    final int mActionState;

    private final ValueAnimatorCompat mValueAnimator;

    private final int mAnimationType;

    public boolean mIsPendingCleanup;

    float mX;

    float mY;

    // if user starts touching a recovering view, we put it into interaction mode again,
    // instantly.
    boolean mOverridden = false;

    private boolean mEnded = false;

    private float mFraction;

    public RecoverAnimation(ViewHolder viewHolder, int animationType,
            int actionState, float startDx, float startDy, float targetX, float targetY) {
        mActionState = actionState;
        mAnimationType = animationType;
        mViewHolder = viewHolder;
        mStartDx = startDx;
        mStartDy = startDy;
        mTargetX = targetX;
        mTargetY = targetY;
        mValueAnimator = AnimatorCompatHelper.emptyValueAnimator();
        mValueAnimator.addUpdateListener(
                new AnimatorUpdateListenerCompat() {
                    @Override
                    public void onAnimationUpdate(ValueAnimatorCompat animation) {
                        setFraction(animation.getAnimatedFraction());
                    }
                });
        mValueAnimator.setTarget(viewHolder.itemView);
        mValueAnimator.addListener(this);
        setFraction(0f);
    }

    public void setDuration(long duration) {
        mValueAnimator.setDuration(duration);
    }

    public void start() {
        mViewHolder.setIsRecyclable(false);
        mValueAnimator.start();
    }

    public void cancel() {
        mValueAnimator.cancel();
    }

    public void setFraction(float fraction) {
        mFraction = fraction;
    }

    /**
     * We run updates on onDraw method but use the fraction from animator callback.
     * This way, we can sync translate x/y values w/ the animators to avoid one-off frames.
     */
    public void update() {
        if (mStartDx == mTargetX) {
            mX = ViewCompat.getTranslationX(mViewHolder.itemView);
        } else {
            mX = mStartDx + mFraction * (mTargetX - mStartDx);
        }
        if (mStartDy == mTargetY) {
            mY = ViewCompat.getTranslationY(mViewHolder.itemView);
        } else {
            mY = mStartDy + mFraction * (mTargetY - mStartDy);
        }
    }

    @Override
    public void onAnimationStart(ValueAnimatorCompat animation) {

    }

    @Override
    public void onAnimationEnd(ValueAnimatorCompat animation) {
        if (!mEnded) {
            mViewHolder.setIsRecyclable(true);
        }
        mEnded = true;
    }

    @Override
    public void onAnimationCancel(ValueAnimatorCompat animation) {
        setFraction(1f); //make sure we recover the view's state.
    }

    @Override
    public void onAnimationRepeat(ValueAnimatorCompat animation) {

    }
}

里面的成员变量大家看名称应该大部分就可以理解了,不做更多的说明了

那么来看看findAnimation()这个方法

//根据查找的View从mRecoverAnimations集合中查找相同View的RecoverAnimation
private RecoverAnimation findAnimation(MotionEvent event) {
    if (mRecoverAnimations.isEmpty()) {
        return null;
    }
    View target = findChildView(event);
    for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
        final RecoverAnimation anim = mRecoverAnimations.get(i);
        if (anim.mViewHolder.itemView == target) {
            return anim;
        }
    }
    return null;
}

//根据event查找View
private View findChildView(MotionEvent event) {
    // first check elevated views, if none, then call RV
    final float x = event.getX();
    final float y = event.getY();
    //mSelected不为空则拿mSelected.itemView
    if (mSelected != null) {
        final View selectedView = mSelected.itemView;
        if (hitTest(selectedView, x, y, mSelectedStartX + mDx, mSelectedStartY + mDy)) {
            return selectedView;
        }
    }
    //从mRecoverAnimations这个RecoverAnimation类集合中查找是否存在View
    for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
        final RecoverAnimation anim = mRecoverAnimations.get(i);
        final View view = anim.mViewHolder.itemView;
        if (hitTest(view, x, y, anim.mX, anim.mY)) {
            return view;
        }
    }
    //通过RecyclerView的findChildViewUnder方法查找View
    return mRecyclerView.findChildViewUnder(x, y);
}

//计算是否点击在当前View的区域内
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();
}

往下看就可以看到endRecoverAnimation方法,这个方法主要就是从mRecoverAnimations集合中删除某个RecoverAnimation对象

private int endRecoverAnimation(ViewHolder viewHolder, boolean override) {
    final int recoverAnimSize = mRecoverAnimations.size();
    for (int i = recoverAnimSize - 1; i >= 0; i--) {
        final RecoverAnimation anim = mRecoverAnimations.get(i);
        if (anim.mViewHolder == viewHolder) {
            anim.mOverridden |= override;
            if (!anim.mEnded) {
                anim.cancel();
            }
            mRecoverAnimations.remove(i);
            return anim.mAnimationType;
        }
    }
    return 0;
}

接下来看下select()方法,这个方法作用是开始拖动或者滑动指定的View

private void select(ViewHolder selected, int actionState) {
    //当ViewHolder一致且State状态一致时候直接返回,不做处理
    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) {
        // 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;
    //当mSelected不为null的时候,新建RecoverAnimation对象,并且start这个ValueAnimator
    if (mSelected != null) {
        final ViewHolder prevSelected = mSelected;
        if (prevSelected.itemView.getParent() != null) {
            //当为DRAG状态时候swipeDir为0
            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(ValueAnimatorCompat animation) {
                    super.onAnimationEnd(animation);
                    if (this.mOverridden) {
                        return;
                    }
                    //动画结束如果swipeDir<=0则drag与swipe失败,Callback会调用clearView方法
                    //swipeDir >0则表示成功,会调用postDispatchSwipe方法
                    //当为DRAG状态时候因为swipeDir为0,所以只走clearView方法
                    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);
                    }
                }
            };
            //获取AnimationDuration,我们可以通过重写这个方法来设定动画的时间
            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;
    }
    //当传进来的selected不为空的时候将selected赋给mSelected
    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();
    }
    //每次select会带来拖动或者滑动的ViewHolder改变,所以这里会调用onSelectedChanged方法,我们可以通过回调接受到这些信息
    mCallback.onSelectedChanged(mSelected, mActionState);
    mRecyclerView.invalidate();
}
private 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.getAdapterPosition() != 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()) {
                    //当滑动结束会调用onSwiped()方法,我们可以在
                    mCallback.onSwiped(anim.mViewHolder, swipeDir);
                } else {
                    mRecyclerView.post(this);
                }
            }
        }
    });
}

通过代码这部分代码我们可以大致知道了select这个方法的作用了,它主要是处理当手指拖动或者滑动结束后的动画,要通过两次调用,第一次在我们选中的时候,作用是确定我们手指选择的View,第二次在我们手指放开的时候,作用是给这个View设置动画,并且执行。

接下来看看checkSelectForSwipe() 方法

private boolean checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) {
     //这里调用了Callback的isItemViewSwipeEnabled()方法,我们通过重写这个方法可以控制是否可以Swipe
    if (mSelected != null || action != MotionEvent.ACTION_MOVE
            || mActionState == ACTION_STATE_DRAG || !mCallback.isItemViewSwipeEnabled()) {
        return false;
    }
    if (mRecyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) {
        return false;
    }
    final ViewHolder vh = findSwipedView(motionEvent);
    if (vh == null) {
        return false;
    }
    //这里的getAbsoluteMovementFlags()前面有介绍过,
    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 false;
    }

    // mDx and mDy are only set in allowed directions. We use custom x/y here instead of
    // updateDxDy to avoid swiping if user moves more in the other direction
    final float x = MotionEventCompat.getX(motionEvent, pointerIndex);
    final float y = MotionEventCompat.getY(motionEvent, pointerIndex);

    // Calculate the distance moved
    final float dx = x - mInitialTouchX;
    final float dy = y - mInitialTouchY;
    // swipe target is chose w/o applying flags so it does not really check if swiping in that
    // direction is allowed. This why here, we use mDx mDy to check slope value again.
    final float absDx = Math.abs(dx);
    final float absDy = Math.abs(dy);

    if (absDx < mSlop && absDy < mSlop) {
        return false;
    }
    if (absDx > absDy) {
        if (dx < 0 && (swipeFlags & LEFT) == 0) {
            return false;
        }
        if (dx > 0 && (swipeFlags & RIGHT) == 0) {
            return false;
        }
    } else {
        if (dy < 0 && (swipeFlags & UP) == 0) {
            return false;
        }
        if (dy > 0 && (swipeFlags & DOWN) == 0) {
            return false;
        }
    }
    mDx = mDy = 0f;
    mActivePointerId = MotionEventCompat.getPointerId(motionEvent, 0);
    //select方法
    select(vh, ACTION_STATE_SWIPE);
    return true;
}

这个方法主要是确定当前的我们选择的View是否可以滑动,而前面说的select方法的第一次调用是在这里,这个主要是滑动的select第一个方法,拖动的第一个select方法则在GestureDetector的onLongPress中

到这里基本就介绍结束onInterceptTouchEvent里面做的东西了

(2).onTouchEvent

public void onTouchEvent(RecyclerView recyclerView, 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 = MotionEventCompat.getActionMasked(event);
    final int activePointerIndex = MotionEventCompat
            .findPointerIndex(event, 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:
            // 第二次select,触发动画
            select(null, ACTION_STATE_IDLE);
            mActivePointerId = ACTIVE_POINTER_ID_NONE;
            break;
        case MotionEvent.ACTION_POINTER_UP: {
            final int pointerIndex = MotionEventCompat.getActionIndex(event);
            final int pointerId = MotionEventCompat.getPointerId(event, 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 = MotionEventCompat.getPointerId(event, newPointerIndex);
                updateDxDy(event, mSelectedFlags, pointerIndex);
            }
            break;
        }
    }
}

在ACTION_UP时候触发的第二次select()则会执行动画效果

而ACTION_MOVE则是处理随着手指运动的效果,那我们看下里面的实现方法

主要有两个一个是moveIfNecessary另外一个是mScrollRunnable

我们看下mScrollRunnable

private final Runnable mScrollRunnable = new Runnable() {
    @Override
    public void run() {
        if (mSelected != null && scrollIfNecessary()) {
            if (mSelected != null) { //it might be lost during scrolling
                moveIfNecessary(mSelected);
            }
            mRecyclerView.removeCallbacks(mScrollRunnable);
            ViewCompat.postOnAnimation(mRecyclerView, this);
        }
    }
};

那我们先来分析下scrollIfNecessary()然后再分析moveIfNecessary()方法

scrollIfNecessary其实上面的注释解释的很清楚,它的作用是检测我们滑动是否到达RecycleView的边缘区域,如果到达边缘区域则将RecycleView移动(scrollBy),这里也调用了callback的interpolateOutOfBoundsScroll方法,所以我们可以在这里监听到我们拖出视图边界的调用

接下来看一下moveIfNecessary()方法

private void moveIfNecessary(ViewHolder viewHolder) {
    if (mRecyclerView.isLayoutRequested()) {
        return;
    }
    if (mActionState != ACTION_STATE_DRAG) {
        return;
    }
   //获取getMoveThreshold,可以通过重写来自定义用户视为拖动的距离
    final float threshold = mCallback.getMoveThreshold(viewHolder);
    final int x = (int) (mSelectedStartX + mDx);
    final int y = (int) (mSelectedStartY + mDy);
   //当移动距离小于拖动距离,return掉
    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;
    }
    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();
    //调用onMove,可以重写来让RecycleView改变,也可以让callback的onMoved方法是否重调
    if (mCallback.onMove(mRecyclerView, viewHolder, target)) {
        // keep target visible
        mCallback.onMoved(mRecyclerView, viewHolder, fromPosition,
                target, toPosition, x, y);
    }
}

所以这里其实是用来判断是否move的带来RecycleView的变化

最后 调用了mRecyclerView.invalidate()方法,而这个方法呢则调用了前面所提到的ItemDecoration里的方法,而那里面的方法处理的是当运动带来的拖动与滑动的交互,前面有提到就不做过多的介绍了

所以总结一下,拖动时通过mRecyclerView.invalidate让onDraw不断重绘带来手指与点击ViewHolder的变化,当手指离开时候则通过select方法启动RecoverAnimation让ViewHolder执行后面的动画,基本的流程就是这样,当然中间有很多Callback方法带来不同的变化,感觉整个流程我还是讲的比较乱的,大家理解大致流程就好了。。到这里就基本分析结束了,当然还有一些方法可能没有介绍了,大家可以通过前面的Callback注释的方法去理解大致的功能就好了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335

推荐阅读更多精彩内容