美文网首页RV
ItemTouchHelper(二)源码简析

ItemTouchHelper(二)源码简析

作者: 前世小书童 | 来源:发表于2016-05-09 19:52 被阅读3031次

    写在前面的几句话

    <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注释的方法去理解大致的功能就好了。

    相关文章

      网友评论

        本文标题:ItemTouchHelper(二)源码简析

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