ItemTouchHelper(二)源碼簡(jiǎn)析

寫在前面的幾句話

<p>
上一篇文章已經(jīng)對(duì)ItemTouchHelper的簡(jiǎn)單使用有了介紹,那么這一篇我們對(duì)ItemTouchHelper進(jìn)行更加深入的了解,對(duì)源碼進(jìn)行簡(jiǎn)單分析

ItemTouchHelper.CallBack分析

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

//聲明不同狀態(tài)下可以移動(dòng)的方向(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);
}

//拖動(dòng)的項(xiàng)目從舊位置移動(dòng)到新位置時(shí)調(diào)用
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
    return false;
}

//滑動(dòng)到消失后的調(diào)用
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}


//是否可以把拖動(dòng)的ViewHolder拖動(dòng)到目標(biāo)ViewHolder之上
@Override
public boolean canDropOver(RecyclerView recyclerView, RecyclerView.ViewHolder current, RecyclerView.ViewHolder target) {
    return true;
}

//獲取拖動(dòng)
@Override
public RecyclerView.ViewHolder chooseDropTarget(RecyclerView.ViewHolder selected, List<RecyclerView.ViewHolder> dropTargets, int curX, int curY) {
    return dropTargets.get(0);
}

//調(diào)用時(shí)與元素的用戶交互已經(jīng)結(jié)束,也就是它也完成了它的動(dòng)畫時(shí)候
@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);
}


//設(shè)置手指離開后ViewHolder的動(dòng)畫時(shí)間
@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();
}


//返回值作為用戶視為拖動(dòng)的距離
@Override
public float getMoveThreshold(RecyclerView.ViewHolder viewHolder) {
    return super.getMoveThreshold(viewHolder);
}

//返回值滑動(dòng)消失的距離,滑動(dòng)小于這個(gè)值不消失,大于消失
@Override
public float getSwipeEscapeVelocity(float defaultValue) {
    return super.getSwipeEscapeVelocity(defaultValue);
}

//返回值滑動(dòng)消失的距離, 這里是相對(duì)于RecycleView的寬度,0.5f表示為RecycleView的寬度的一半,取值為0~1f之間
@Override
public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {
    return super.getSwipeThreshold(viewHolder);
}

//返回值作為滑動(dòng)的流程程度,越小越難滑動(dòng),越大越好滑動(dòng)
@Override
public float getSwipeVelocityThreshold(float defaultValue) {
    return 1f;
}


//當(dāng)用戶拖動(dòng)一個(gè)視圖出界的ItemTouchHelper調(diào)用
@Override
public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, int viewSizeOutOfBounds, int totalSize, long msSinceStartScroll) {
    return super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll);
}


//返回值決定是否有滑動(dòng)操作
@Override
public boolean isItemViewSwipeEnabled() {
    return super.isItemViewSwipeEnabled();
}

//返回值決定是否有拖動(dòng)操作
@Override
public boolean isLongPressDragEnabled() {
    return super.isLongPressDragEnabled();
}

//自定義拖動(dòng)與滑動(dòng)交互
@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);
}

//自定義拖動(dòng)與滑動(dòng)交互
@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);
}


//當(dāng)onMove return ture的時(shí)候調(diào)用
@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);
}

//當(dāng)拖動(dòng)或者滑動(dòng)的ViewHolder改變時(shí)調(diào)用
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
    super.onSelectedChanged(viewHolder, actionState);
}

結(jié)合這些分析注釋就明白了上面一篇文章里面CallBack為什么寫那些方法了,還有部分的方法還是沒有理解到底是干嘛的所有就沒有注釋了。

ItemTouchHelper相關(guān)分析

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

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

那么從這個(gè)將ItemTouchHelper與RecycleView建立的方法進(jìn)行分析

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();
    }
}

這部分的代碼其實(shí)沒有做太多的事情,無非是獲取一些默認(rèn)值,setupCallbacks()與destroyCallbacks()兩個(gè)方法,這兩個(gè)方法從名稱看就是相對(duì)立的,所以分析一個(gè)就好了

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();
}

這里的步驟有點(diǎn)多了

分布來說明

1.addItemDecoration(this)

這個(gè)方法其實(shí)是調(diào)用了ItemDecoration的接口.從ItemTouchHelper方法聲明部分也可以看到

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

在ItemTouchHelper中重寫了ItemDecoration接口的兩個(gè)方法如下

@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);
}

從方法可以看到這里其實(shí)沒有做什么特別的工作,只是回調(diào)了Callback的兩個(gè)回調(diào)方法onDrawOver()與onDraw()而這兩個(gè)方法是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();
    }
}

這里牽扯的東西比較多,暫時(shí)不分析,我們可以看到有兩個(gè)方法分別為onChildDrawOver()與onChildDraw()

調(diào)用了這兩個(gè)方法,接下來看下這兩個(gè)方法里面有什么?

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);
}

這里面其實(shí)是sUICallback的回調(diào)方法

這里的sUICallback是一個(gè)接口,根據(jù)不同的版本執(zhí)行不同的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()方法來實(shí)現(xiàn)自定義的拖動(dòng)與滑動(dòng)交互

2.addOnChildAttachStateChangeListener(this)

這里調(diào)用了OnChildAttachStateChangeListener這個(gè)接口,這個(gè)接口里有兩個(gè)方法,分別是在RecycleView添加一個(gè)View與刪除一個(gè)View的時(shí)候回調(diào)

那看看我們?cè)贗temTouchHelper中重寫的方法

@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);
        }
    }
}

因?yàn)镮temTouchHelper中只用考慮移除的情況,

這里面的方法暫時(shí)不介紹,可以看到回調(diào)了clearView()的方法,所以在元素的用戶交互已經(jīng)結(jié)束的時(shí)候,可以通過這個(gè)方法監(jiān)聽到

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調(diào)用了hasDragFlag這個(gè)方法,那我們看下這個(gè)方法

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));
}

經(jīng)過調(diào)用發(fā)現(xiàn)最后調(diào)用了getMovementFlags這個(gè)方法,所以我們重寫方法如果是沒有聲明的在onLongPress中就直接return了,不會(huì)觸發(fā)下面的方法了

再往下看,會(huì)調(diào)用Callback的isLongPressDragEnabled()方法,當(dāng)return為true的時(shí)候會(huì)執(zhí)行select()方法

4. addOnItemTouchListener

這里則是調(diào)用了RecycleView的addOnItemTouchListener方法,ItemTouchHelper重寫了OnItemTouchListener接口的方法,OnItemTouchListener有三個(gè)方法,我們一個(gè)個(gè)來進(jìn)行分析

(1).onInterceptTouchEvent

public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) {
  //給前面注冊(cè)的GestureDetector添加監(jiān)聽
  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) {
      //獲取這個(gè)事件對(duì)應(yīng)的pointerId,ViewDragerHelper中也有說明
      mActivePointerId = MotionEventCompat.getPointerId(event, 0);
      mInitialTouchX = event.getX();
      mInitialTouchY = event.getY();
      //初始化跟蹤觸摸屏類VelocityTracker
      obtainVelocityTracker();
      if (mSelected == null) {
          //根據(jù)當(dāng)前的MotionEvent查找RecoverAnimation對(duì)象
          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 表示最少有一個(gè)觸控點(diǎn)存在
      if (index >= 0) {
          checkSelectForSwipe(action, event, index);
      }
  }
  if (mVelocityTracker != null) {
      mVelocityTracker.addMovement(event);
  }
  return mSelected != null;
}

大部分注釋都已經(jīng)說明了,主要把他們調(diào)用的方法來進(jìn)行說明

首先來看下RecoverAnimation這個(gè)類,這個(gè)類中有ValueAnimatorCompat主要是根據(jù)起始點(diǎn)及ActionState等做動(dòng)畫的。

接下來就貼出這個(gè)類

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) {

    }
}

里面的成員變量大家看名稱應(yīng)該大部分就可以理解了,不做更多的說明了

那么來看看findAnimation()這個(gè)方法

//根據(jù)查找的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;
}

//根據(jù)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這個(gè)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);
}

//計(jì)算是否點(diǎn)擊在當(dāng)前View的區(qū)域內(nèi)
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方法,這個(gè)方法主要就是從mRecoverAnimations集合中刪除某個(gè)RecoverAnimation對(duì)象

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()方法,這個(gè)方法作用是開始拖動(dòng)或者滑動(dòng)指定的View

private void select(ViewHolder selected, int actionState) {
    //當(dāng)ViewHolder一致且State狀態(tài)一致時(shí)候直接返回,不做處理
    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;
    //當(dāng)mSelected不為null的時(shí)候,新建RecoverAnimation對(duì)象,并且start這個(gè)ValueAnimator
    if (mSelected != null) {
        final ViewHolder prevSelected = mSelected;
        if (prevSelected.itemView.getParent() != null) {
            //當(dāng)為DRAG狀態(tài)時(shí)候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;
                    }
                    //動(dòng)畫結(jié)束如果swipeDir<=0則drag與swipe失敗,Callback會(huì)調(diào)用clearView方法
                    //swipeDir >0則表示成功,會(huì)調(diào)用postDispatchSwipe方法
                    //當(dāng)為DRAG狀態(tài)時(shí)候因?yàn)閟wipeDir為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,我們可以通過重寫這個(gè)方法來設(shè)定動(dòng)畫的時(shí)間
            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;
    }
    //當(dāng)傳進(jìn)來的selected不為空的時(shí)候?qū)elected賦給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會(huì)帶來拖動(dòng)或者滑動(dòng)的ViewHolder改變,所以這里會(huì)調(diào)用onSelectedChanged方法,我們可以通過回調(diào)接受到這些信息
    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()) {
                    //當(dāng)滑動(dòng)結(jié)束會(huì)調(diào)用onSwiped()方法,我們可以在
                    mCallback.onSwiped(anim.mViewHolder, swipeDir);
                } else {
                    mRecyclerView.post(this);
                }
            }
        }
    });
}

通過代碼這部分代碼我們可以大致知道了select這個(gè)方法的作用了,它主要是處理當(dāng)手指拖動(dòng)或者滑動(dòng)結(jié)束后的動(dòng)畫,要通過兩次調(diào)用,第一次在我們選中的時(shí)候,作用是確定我們手指選擇的View,第二次在我們手指放開的時(shí)候,作用是給這個(gè)View設(shè)置動(dòng)畫,并且執(zhí)行。

接下來看看checkSelectForSwipe() 方法

private boolean checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) {
     //這里調(diào)用了Callback的isItemViewSwipeEnabled()方法,我們通過重寫這個(gè)方法可以控制是否可以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;
}

這個(gè)方法主要是確定當(dāng)前的我們選擇的View是否可以滑動(dòng),而前面說的select方法的第一次調(diào)用是在這里,這個(gè)主要是滑動(dòng)的select第一個(gè)方法,拖動(dòng)的第一個(gè)select方法則在GestureDetector的onLongPress中

到這里基本就介紹結(jié)束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,觸發(fā)動(dòng)畫
            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時(shí)候觸發(fā)的第二次select()則會(huì)執(zhí)行動(dòng)畫效果

而ACTION_MOVE則是處理隨著手指運(yùn)動(dòng)的效果,那我們看下里面的實(shí)現(xiàn)方法

主要有兩個(gè)一個(gè)是moveIfNecessary另外一個(gè)是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其實(shí)上面的注釋解釋的很清楚,它的作用是檢測(cè)我們滑動(dòng)是否到達(dá)RecycleView的邊緣區(qū)域,如果到達(dá)邊緣區(qū)域則將RecycleView移動(dòng)(scrollBy),這里也調(diào)用了callback的interpolateOutOfBoundsScroll方法,所以我們可以在這里監(jiān)聽到我們拖出視圖邊界的調(diào)用

接下來看一下moveIfNecessary()方法

private void moveIfNecessary(ViewHolder viewHolder) {
    if (mRecyclerView.isLayoutRequested()) {
        return;
    }
    if (mActionState != ACTION_STATE_DRAG) {
        return;
    }
   //獲取getMoveThreshold,可以通過重寫來自定義用戶視為拖動(dòng)的距離
    final float threshold = mCallback.getMoveThreshold(viewHolder);
    final int x = (int) (mSelectedStartX + mDx);
    final int y = (int) (mSelectedStartY + mDy);
   //當(dāng)移動(dòng)距離小于拖動(dòng)距離,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();
    //調(diào)用onMove,可以重寫來讓RecycleView改變,也可以讓callback的onMoved方法是否重調(diào)
    if (mCallback.onMove(mRecyclerView, viewHolder, target)) {
        // keep target visible
        mCallback.onMoved(mRecyclerView, viewHolder, fromPosition,
                target, toPosition, x, y);
    }
}

所以這里其實(shí)是用來判斷是否move的帶來RecycleView的變化

最后 調(diào)用了mRecyclerView.invalidate()方法,而這個(gè)方法呢則調(diào)用了前面所提到的ItemDecoration里的方法,而那里面的方法處理的是當(dāng)運(yùn)動(dòng)帶來的拖動(dòng)與滑動(dòng)的交互,前面有提到就不做過多的介紹了

所以總結(jié)一下,拖動(dòng)時(shí)通過mRecyclerView.invalidate讓onDraw不斷重繪帶來手指與點(diǎn)擊ViewHolder的變化,當(dāng)手指離開時(shí)候則通過select方法啟動(dòng)RecoverAnimation讓ViewHolder執(zhí)行后面的動(dòng)畫,基本的流程就是這樣,當(dāng)然中間有很多Callback方法帶來不同的變化,感覺整個(gè)流程我還是講的比較亂的,大家理解大致流程就好了。。到這里就基本分析結(jié)束了,當(dāng)然還有一些方法可能沒有介紹了,大家可以通過前面的Callback注釋的方法去理解大致的功能就好了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容