源码分析 ItemTouchHelper手势的入口 (OnInterceptTouchEvent onLongPress等)

来源:互联网 发布:windows控制mac 编辑:程序博客网 时间:2024/06/05 05:29

ItemTouchHelper是v7包RecyclerView的ItemDecoration接口的一个实现,其前身是v4包的ViewDragHelper(可在任意ViewGroup中使用)在DrawerLayout、SlidingPaneLayout源码中都有应用。

两者使用方法也很类似,都是实现各自的Callback

ItemTouchHelper手势的实现都在匿名内部类mOnItemTouchListener中实现,

private final OnItemTouchListener mOnItemTouchListener            = new OnItemTouchListener()

与RecyclerView的绑定,则在attachToRecyclerView方法中的setupCallbacks()中

add到RV中

    /**     * Attaches the ItemTouchHelper to the provided RecyclerView. If TouchHelper is already     * attached to a RecyclerView, it will first detach from the previous one. You can call this     * method with {@code null} to detach it from the current RecyclerView.     *     * @param recyclerView The RecyclerView instance to which you want to add this helper or     *                     {@code null} if you want to remove ItemTouchHelper from the current     *                     RecyclerView.     */    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(); //初始化 setup        }    }    private void setupCallbacks() {        ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());        mSlop = vc.getScaledTouchSlop(); //初始化TouchSlop        mRecyclerView.addItemDecoration(this);  //绑定Callback.OnDraw供实现onChildDraw        mRecyclerView.addOnItemTouchListener(mOnItemTouchListener); //绑定手势监听        mRecyclerView.addOnChildAttachStateChangeListener(this);         initGestureDetector();    }    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();    }

进入正题

严格的说,ItemTouchHelper的手势正式接管方法是select()方法,而通过手势开始(达到某种条件)而进入select的入口总共3个

1、长按

2、与rv垂直方向的swipe移动(超过touchSlop的Move)

3、按下一个正在做恢复动画的vh(对RecoverAnimation没做完的vh触发Down事件)

既然是手势接管,毫无疑问,这些入口的入口一定来自mOnItemTouchListener的OnInterceptTouchEvent方法

        @Override        public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) {            mGestureDetector.onTouchEvent(event);//主要实现onLongPress的监听  
这里就是入口1,下有对于ItemTouchHelperGestureListener的onLongPress具体分析

    //如果是个长按 那么拦截短路掉RV自己的滑动    //并判定Down选中的vh ItemTouchHelper正式接管             if (DEBUG) {                Log.d(TAG, "intercept: x:" + event.getX() + ",y:" + event.getY() + ", " + event);            }            final int action = MotionEventCompat.getActionMasked(event);            if (action == MotionEvent.ACTION_DOWN) {                mActivePointerId = event.getPointerId(0);
下面记录down的位置,注意这里是DOWN的时候无条件记录!!!
因为接管手势是未来的事,之后的位移比对等等都来自这个初始记录值
长按的时候这个初始值还会被替换,(因为长按的起始是长按600ms而不是DOWN)
如果这里代码没有被执行 startDrag startSwipe等方法在外部调用是无法被正确工作的! 
startDrag等方法工作前提的说明见我的另一篇点击打开链接
                mInitialTouchX = event.getX();                 mInitialTouchY = event.getY();                 obtainVelocityTracker();                if (mSelected == null) {                    final RecoverAnimation animation = findAnimation(event);
下面是入口3,一个并不常见的场景
                    if (animation != null) { //特别注意!!! 这里并不是一个主流程的手势接管入口                    //该段代码仅处理DOWN按下一个正在做恢复动画的vh的场景!                    //千万不要误解,通常DOWN的时候是没有恢复动画的,所以mSelected不会被赋值                    //intercept也就不会成功,后面的手势OnItemTouchListener会正常执行onIntercepter方法                    //若都返回false 也会传递到vh的itemView的DispatchTouchEvent里                        mInitialTouchX -= animation.mX;                         mInitialTouchY -= animation.mY;                        endRecoverAnimation(animation.mViewHolder, true); //肯定把正在进行的恢复动画先干掉咯                        if (mPendingCleanup.remove(animation.mViewHolder.itemView)) { //mPendingCleanup 存储动画之后就应该detach并被cleanup的Views                            mCallback.clearView(mRecyclerView, animation.mViewHolder); //如果正在被移除 恢复vh                        }                        select(animation.mViewHolder, animation.mActionState); //调用select 接管!                        updateDxDy(event, mSelectedFlags, 0);                    }                }            }

处理cancel与抬起

 else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {                mActivePointerId = ACTIVE_POINTER_ID_NONE; //如果是cancel或up 那么select(null)                select(null, ACTION_STATE_IDLE);            }
再下面就是Swipe的入口了 入口2
 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 = event.findPointerIndex(mActivePointerId);                if (DEBUG) {                    Log.d(TAG, "pointer index " + index);                }                if (index >= 0) { //检验PointerId合法性 不是ACTIVE_POINTER_ID_NONE -1                    checkSelectForSwipe(action, event, index); //Swipe的入口                    //前提:(没有接管过mSelect为空、必须是Move事件、支持swipe、拖动超过Slop且与Rv方向垂直)                }            }            if (mVelocityTracker != null) {                mVelocityTracker.addMovement(event);            }            return mSelected != null;  //特别特别注意: mSelected是否为空决定是否接管!!!(select方法是否调用)        }

onIntercepterTouchEvent 到此结束 注意它的返回值


下面来看一下入口1和入口2的具体实现

长按手势的入口ItemTouchHelperGestureListener, 也就是GestureDetector的回调实现。

    private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {        ItemTouchHelperGestureListener() {        }        @Override        public boolean onDown(MotionEvent e) {            return true;        }        @Override        public void onLongPress(MotionEvent e) {            View child = findChildView(e);//先找点了谁(vh) 点到花花草草(divider分割线之类)可不作数哦            if (child != null) {                ViewHolder vh = mRecyclerView.getChildViewHolder(child);                if (vh != null) {//注意 这个hasDragFlag方法和hasSwipeFlag一样调用public可重写的getAbsoluteMovementFlags方法//入参有vh 也就是说可以像checkSelectForSwipe定制swipe方向一样//定制各个vh自己的move方向                    if (!mCallback.hasDragFlag(mRecyclerView, vh)) {                        return;                    }                    int pointerId = e.getPointerId(0);                    // Long press is deferred.                    // Check w/ active pointer id to avoid selecting after motion                    // event is canceled.                    if (pointerId == mActivePointerId) {                        final int index = e.findPointerIndex(mActivePointerId);                        final float x = e.getX(index);                        final float y = e.getY(index);                        mInitialTouchX = x; //如果长按了 那么长按才是手势的正式入口                        mInitialTouchY = y;                        mDx = mDy = 0f;                        if (DEBUG) {                            Log.d(TAG,                                    "onlong press: x:" + mInitialTouchX + ",y:" + mInitialTouchY);                        }                        if (mCallback.isLongPressDragEnabled()) {//最关键的校验 如果你通过重写这方法                        //禁用了LongPressDragEnabled HOHO 那它肯定也就不拖动了                            select(vh, ACTION_STATE_DRAG);                        }                    }                }            }        }    }
接下来看Move事件Swipe的入口
    /**     * Checks whether we should select a View for swiping.     */    boolean checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) {        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);//检验手势目前是否符合Swipe 下有分析        if (vh == null) {            return false;        }//之前校验了一堆 如果已经有mSelected 那么不用再校验了//校验只检验Move事件 且itemTouchHelper必须是拖动状态//Callback必须支持Swipe isItemViewSwipeEnabled没有被重写flase//如果RecyclerView已经处理了拖动状态————对不起也不行//如果点击的地方没有viewHoler不行(比如点到了divider?)        final int movementFlags = mCallback.getAbsoluteMovementFlags(mRecyclerView, vh);//获取支持的swipe方向,注意!这里getAbsoluteMovementFlags入参有vh,也就是说你//可以定制每一个vh的swipe或者move方向(见OnLongPress)        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 = motionEvent.getX(pointerIndex);        final float y = motionEvent.getY(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 = motionEvent.getPointerId(0);        select(vh, ACTION_STATE_SWIPE);        return true;    }
检验这个手势是否是swipe一个ViewHolder

    private ViewHolder findSwipedView(MotionEvent motionEvent) {        final RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();        if (mActivePointerId == ACTIVE_POINTER_ID_NONE) { //再次校验pointerId是否有效            return null;        }        final int pointerIndex = motionEvent.findPointerIndex(mActivePointerId);        final float dx = motionEvent.getX(pointerIndex) - mInitialTouchX;        final float dy = motionEvent.getY(pointerIndex) - mInitialTouchY;        final float absDx = Math.abs(dx);        final float absDy = Math.abs(dy);        if (absDx < mSlop && absDy < mSlop) { //判断是否超过TouchSlop            return null;        }        if (absDx > absDy && lm.canScrollHorizontally()) { //如果RecyclerView滑动的同一个方向        //的位移更大,那么这应该交给RV处理,这里不是个swipe            return null;        } else if (absDy > absDx && lm.canScrollVertically()) {            return null;        }        View child = findChildView(motionEvent);        if (child == null) {            return null;        }        return mRecyclerView.getChildViewHolder(child);    }
下面分析一下最重要的接管方法select()

    /**     * Starts dragging or swiping the given View. Call with null if you want to clear it.     *     * @param selected    The ViewHolder to drag or swipe. Can be null if you want to cancel the     *                    current action     * @param actionState The type of action     */    void select(ViewHolder selected, int actionState) {        if (selected == mSelected && actionState == mActionState) {            return;  //如果状态没有改变 不需要重复处理        }        mDragScrollStartTimeInMs = Long.MIN_VALUE;        final int prevActionState = mActionState;        // prevent duplicate animations        endRecoverAnimation(selected, true);        mActionState = actionState;        if (actionState == ACTION_STATE_DRAG) {            // we remove after animation is complete. this means we only elevate the last drag            // child but that should perform good enough as it is very hard to start dragging a            // new child before the previous one settles.            mOverdrawChild = selected.itemView;            addChildDrawingOrderCallback();        }        int actionStateMask = (1 << (DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT * actionState))                - 1;        boolean preventLayout = false;        if (mSelected != null) { //如果原来有接管目标vh的 那么那个vh要寿终正寝 完成后事动画            final ViewHolder prevSelected = mSelected;            if (prevSelected.itemView.getParent() != null) {                final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0                        : swipeIfNecessary(prevSelected);                releaseVelocityTracker();                // find where we should animate to                final float targetTranslateX, targetTranslateY;                int animationType;                switch (swipeDir) {                    case LEFT:                    case RIGHT:                    case START:                    case END:                        targetTranslateY = 0;                        targetTranslateX = Math.signum(mDx) * mRecyclerView.getWidth();                        break;                    case UP:                    case DOWN:                        targetTranslateX = 0;                        targetTranslateY = Math.signum(mDy) * mRecyclerView.getHeight();                        break;                    default:                        targetTranslateX = 0;                        targetTranslateY = 0;                }                if (prevActionState == ACTION_STATE_DRAG) { //如果原来有接管对象vh的                    animationType = ANIMATION_TYPE_DRAG; //拖动的变状态为拖动动画                } else if (swipeDir > 0) {                    animationType = ANIMATION_TYPE_SWIPE_SUCCESS; //swipe的变状态为swipe动画                } 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;                        }                        if (swipeDir <= 0) {                            // this is a drag or failed swipe. recover immediately                            //如果是个拖拉(onMove)或者没有过阈值的swipe 是没有动画的                            //立即恢复                            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.                                //动画可能在布局过程中由其他animators完成。//所以这里用递归判断isRunning!!!,以避免在布局期间编辑适配器。//保证onSwipe的回调(特别注意:动画结束不意味着mRecoverAnimations里//对应的vh被清除! 依赖于onSwipe里移除对应数据而移除vh之后执行//onChildViewDetachedFromWindow的回调里endRecoverAnimation()移除)                                postDispatchSwipe(this, swipeDir);                             }                        }                        // removed from the list after it is drawn for the last time                        if (mOverdrawChild == prevSelected.itemView) {                            removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);                        }                    }                };                final long duration = mCallback.getAnimationDuration(mRecyclerView, animationType,                        targetTranslateX - currentTranslateX, targetTranslateY - currentTranslateY);                rv.setDuration(duration); //注意!!! 这个CallBack的getAnimationDuration方法可以重写                //自定义动画时间                mRecoverAnimations.add(rv);                rv.start();                preventLayout = true;            } else {                removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);                mCallback.clearView(mRecyclerView, prevSelected);            }            mSelected = null; //旧接管目标清空        }        if (selected != null) { //新接管目标            mSelectedFlags =                    (mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) & actionStateMask)                            >> (mActionState * DIRECTION_FLAG_COUNT);            mSelectedStartX = selected.itemView.getLeft();            mSelectedStartY = selected.itemView.getTop();            mSelected = selected; //赋值            if (actionState == ACTION_STATE_DRAG) {                mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);            }        }        final ViewParent rvParent = mRecyclerView.getParent();        if (rvParent != null) { //如果select有值 那么Disallow拦截 这个号理解            rvParent.requestDisallowInterceptTouchEvent(mSelected != null);        }        if (!preventLayout) {            mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout();        }        mCallback.onSelectedChanged(mSelected, mActionState); //注意 这里接管和放开接管都有CallBack回调        //可以重写的!!!        mRecyclerView.invalidate();    }




原创粉丝点击