前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android--利用ItemTouchHelper实现RecyclerView的侧滑删除

Android--利用ItemTouchHelper实现RecyclerView的侧滑删除

作者头像
aruba
发布2020-07-02 15:28:40
3.2K0
发布2020-07-02 15:28:40
举报
文章被收录于专栏:android技术android技术
上次分析源码,我们知道,ItemTouchHelper对被选中的ViewHodler进行动画操作都是通过ItemTouchUIUtilImpl这个类,我们想要实现侧滑删除,必定需要对ViewHodler进行平移操作,ItemTouchHelper.Callback通过onChildDraw方法调用了ItemTouchUIUtilImpl中的方法,所以我们改写onChildDraw方法
代码语言:javascript
复制
    @Override
    public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
        if (dY != 0 && dX == 0) {//因为我们只关注侧滑,而侧滑条件是dX!=0&&dY ==0,所以其他的情况调用ItemTouchUIUtilImpl的方法
            super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
        }

        if (iMoveAndSwipeCallback != null) {
            iMoveAndSwipeCallback.onChildDraw(viewHolder, dX, dY);
        }
    }
并实现iMoveAndSwipeCallback的onChildDraw方法
代码语言:javascript
复制
    @Override
    public void onChildDraw(RecyclerView.ViewHolder viewHolder, float dX, float dY) {
        MyAdapter.MyViewHolder myViewHolder = (MyAdapter.MyViewHolder) viewHolder;
        //最大偏移不超过删除布局宽度
        if (dX < -myViewHolder.ll_remove.getWidth()) {
            dX = -myViewHolder.ll_remove.getWidth();
        }
        myViewHolder.tv.setTranslationX(dX);
    }
我们RecyclerView的item的布局文件是这样的
代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="70dp"
    android:orientation="horizontal">


    <LinearLayout
        android:id="@+id/ll_remove"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="right"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/tv_remove"
            android:layout_width="100dp"
            android:layout_height="match_parent"
            android:background="@android:color/holo_red_light"
            android:gravity="center"
            android:text="remove"
            android:textColor="@android:color/white" />

        <TextView
            android:id="@+id/tv_cancel"
            android:layout_width="100dp"
            android:layout_height="match_parent"
            android:background="@android:color/holo_blue_bright"
            android:gravity="center"
            android:text="cancel"
            android:textColor="@android:color/white" />

    </LinearLayout>
    
    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorPrimary"
        android:gravity="center"
        android:textColor="@android:color/white" />

</FrameLayout>

item.png

其中删除和取消的布局在下层,好了,我们来看下效果

recyclerview.gif

但是,我们的item并不能获取点击事件,因为ItemTouchHelper并没有把事件传递给子控件,解决方法:把ItemTouchHelper复制到自己的项目中!我们只需要改OnItemTouchListener这个对象就可以了,修改后如下:
代码语言:javascript
复制
    private final RecyclerView.OnItemTouchListener mOnItemTouchListener = new RecyclerView.OnItemTouchListener() {
        private boolean onClick;

        @Override
        public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) {
            mGestureDetector.onTouchEvent(event);
            if (DEBUG) {
                Log.d(TAG, "intercept: x:" + event.getX() + ",y:" + event.getY() + ", " + event);
            }
            final int action = event.getActionMasked();
            if (action == MotionEvent.ACTION_DOWN) {
                mActivePointerId = event.getPointerId(0);
                mInitialTouchX = event.getX();
                mInitialTouchY = event.getY();
                //记录ACTION_DOWN的位置
                mDownX = event.getX();
                mDownY = event.getY();
                onClick = true;

                obtainVelocityTracker();
                if (mSelected == null) {
                    final RecoverAnimation animation = findAnimation(event);
                    if (animation != null) {
                        mInitialTouchX -= animation.mX;
                        mInitialTouchY -= animation.mY;
                        endRecoverAnimation(animation.mViewHolder, true);
                        if (mPendingCleanup.remove(animation.mViewHolder.itemView)) {
                            mCallback.clearView(mRecyclerView, animation.mViewHolder);
                        }
                        select(animation.mViewHolder, animation.mActionState);
                        updateDxDy(event, mSelectedFlags, 0);
                    }
                }
            } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
                mActivePointerId = ACTIVE_POINTER_ID_NONE;
                if (action == MotionEvent.ACTION_UP) {
                    //点击事件
                    click(event);
                }
                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 = event.findPointerIndex(mActivePointerId);
                if (DEBUG) {
                    Log.d(TAG, "pointer index " + index);
                }
                if (index >= 0) {
                    checkSelectForSwipe(action, event, index);
                }
            }

            //处理下move事件
            if (action == MotionEvent.ACTION_MOVE) {
                //移动了,就解除点击
                final float dx = event.getX(mActivePointerId) - mDownX;
                final float dy = event.getY(mActivePointerId) - mDownY;
                final float absDx = Math.abs(dx);
                final float absDy = Math.abs(dy);

                if (absDx > mSlop || absDy > mSlop) {
                    onClick = false;
                }
            }


            if (mVelocityTracker != null) {
                mVelocityTracker.addMovement(event);
            }
            return mSelected != null;
        }

        @Override
        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 = event.getActionMasked();
            final int activePointerIndex = event.findPointerIndex(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:
                    //点击事件
                    click(event);
                    select(null, ACTION_STATE_IDLE);
                    mActivePointerId = ACTIVE_POINTER_ID_NONE;
                    break;
                case MotionEvent.ACTION_POINTER_UP: {
                    onClick = false;
                    final int pointerIndex = event.getActionIndex();
                    final int pointerId = event.getPointerId(pointerIndex);
                    if (pointerId == mActivePointerId) {
                        // This was our active pointer going up. Choose a new
                        // active pointer and adjust accordingly.
                        final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                        mActivePointerId = event.getPointerId(newPointerIndex);
                        updateDxDy(event, mSelectedFlags, pointerIndex);
                    }
                    break;
                }

                default:
                    onClick = false;
                    break;
            }
        }

        /**
         * 点击事件处理
         * @param event
         */
        private void click(MotionEvent event) {
            if (onClick && mSelected != null) {//抬起时,调用点击
                View view = mSelected.itemView;
                if (view instanceof ViewGroup) {
                    view = findView((ViewGroup) mSelected.itemView, event.getRawX(), event.getRawY());
                }

                if (view != null) {
                    view.performClick();
                }
            }
            onClick = false;
        }

        /**
         * 找到被点击的View
         *
         * @param parent
         * @param x
         * @param y
         * @return
         */
        private View findView(ViewGroup parent, float x, float y) {
            int count = parent.getChildCount();
            for (int i = 0; i < count; i++) {
                View view = parent.getChildAt(i);
                //如果是组件,并可见的话
                if (view instanceof ViewGroup && view.getVisibility() == View.VISIBLE) {
                    //递归调用
                    View child = findView((ViewGroup) view, x, y);
                    if (child != null) {
                        return child;
                    }
                }

                //如果在可点击区域内,又是控件的话
                if (isInRegion(view, x, y) && view.getVisibility() == View.VISIBLE) {
                    return view;
                }
            }

            //没有child的情况,判断下有没有在可点击区域内
            if (isInRegion(parent, x, y) && parent.getVisibility() == View.VISIBLE) {
                return parent;
            }

            return null;
        }

        /**
         * 判断点击位置是否在区域内
         * @param child
         * @param x
         * @param y
         * @return
         */
        private boolean isInRegion(View child, float x, float y) {
            Rect rect = new Rect();
            //获取在屏幕全局的区域
            child.getGlobalVisibleRect(rect);
            if (rect.contains((int) x, (int) y) && ViewCompat.hasOnClickListeners(child) &&
                    child.getVisibility() == View.VISIBLE) {
                return true;
            }
            return false;
        }


        @Override
        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
            if (!disallowIntercept) {
                return;
            }
            select(null, ACTION_STATE_IDLE);
        }
    };
我们自己处理并找到被点击的控件,并调用performClick方法,这时我们可以删除了

recyclerview.gif

我们还需要解决的问题是上下滑动或者选中其他ViewHodler时的时候,把ViewHodler复原
在select方法中,记录上一个ViewHodler
代码语言:javascript
复制
    /**
     * 之前侧滑的ViewHolder
     */
    ViewHolder mPreSelected = null;

                final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType,
                        prevActionState, currentTranslateX, currentTranslateY,
                        targetTranslateX, targetTranslateY) {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        if (this.mOverridden) {
                            return;
                        }
                        if (swipeDir <= 0) {
                            // this is a drag or failed swipe. recover immediately
                            mCallback.clearView(mRecyclerView, prevSelected);
                            // full cleanup will happen on onDrawOver
                        } else {
                            // wait until remove animation is complete.
                            mPendingCleanup.add(prevSelected.itemView);
                            //记录
                            mPreSelected = prevSelected;
                            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);
                        }
                    }
                };
定义恢复动画
代码语言:javascript
复制
    /**
     * 关闭动画
     */
    private void closeOpenedPreItem() {
        final View view = mCallback.getItemFrontView(mPreSelected);
        if (mPreSelected == null || view == null) return;
        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "translationX", view.getTranslationX(), 0f);
        objectAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                if (mPreSelected != null) mCallback.clearView(mRecyclerView, mPreSelected);
                if (mPreSelected != null) mPendingCleanup.remove(mPreSelected.itemView);
                endRecoverAnimation(mPreSelected, true);
                mPreSelected = mSelected;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
            }
        });
        objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        objectAnimator.start();
    }
在recyclerView滑动和ViewHodler改变时调用
代码语言:javascript
复制
    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);
            //添加滑动监听
            mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                    super.onScrollStateChanged(recyclerView, newState);
                    if (newState == RecyclerView.SCROLL_STATE_DRAGGING && mPreSelected != null) {
                        closeOpenedPreItem();
                    }
                }
            });
            setupCallbacks();
        }
    }

    /**
     * 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);
        if (vh == null) {
            return false;
        }
        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 = 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);

        if (mPreSelected != null && mPreSelected != vh && mPreSelected != null) {
            closeOpenedPreItem();
        }
        return true;
    }
最终效果:

recyclerview.gif

项目地址:https://gitee.com/aruba/ItemTouchHelperApplication.git
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 上次分析源码,我们知道,ItemTouchHelper对被选中的ViewHodler进行动画操作都是通过ItemTouchUIUtilImpl这个类,我们想要实现侧滑删除,必定需要对ViewHodler进行平移操作,ItemTouchHelper.Callback通过onChildDraw方法调用了ItemTouchUIUtilImpl中的方法,所以我们改写onChildDraw方法
  • 并实现iMoveAndSwipeCallback的onChildDraw方法
  • 我们RecyclerView的item的布局文件是这样的
  • 其中删除和取消的布局在下层,好了,我们来看下效果
  • 但是,我们的item并不能获取点击事件,因为ItemTouchHelper并没有把事件传递给子控件,解决方法:把ItemTouchHelper复制到自己的项目中!我们只需要改OnItemTouchListener这个对象就可以了,修改后如下:
  • 我们自己处理并找到被点击的控件,并调用performClick方法,这时我们可以删除了
  • 我们还需要解决的问题是上下滑动或者选中其他ViewHodler时的时候,把ViewHodler复原
  • 在select方法中,记录上一个ViewHodler
  • 定义恢复动画
  • 在recyclerView滑动和ViewHodler改变时调用
  • 最终效果:
  • 项目地址:https://gitee.com/aruba/ItemTouchHelperApplication.git
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档