RecyclerView完美实现拖拽,滑动删除,撤销删除

 · 11948 word(s) · 12 mins read

本文所讲的都是使用自带 API 实现,并不借用第三方控件。用于RecyclerView 中实现滑动删除拖拽排序,以及如何实现删除后撤销操作(类似于知乎中撤销删除操作)

初始化 RecyclerView,绑定 Adapter,LayoutManager等。

#效果图

效果图

#直接上代码

    //数据
    mUserBookShelfResponses = new ArrayList<>();
    mRecyclerView =(RecyclerView)findViewById(R.id.recyclerView);
    mSwipeRefreshLayout = (SwipeRefreshLayout)findViewById(R.id.swipe_refresh_widget);
    //给刷新控件添加颜色,最多4中颜色
    mSwipeRefreshLayout.setColorSchemeResources(R.color.recycler_color1,R.color.recycler_color2,R.color.recycler_color3, R.color.recycler_color4);
    //瀑布流
    mLayoutManager = new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL);
    mRecyclerView.setHasFixedSize(false);
    mRecyclerView.setLayoutManager(mLayoutManager);
    //创建Adapter
    mBookShelfAdapter = new UserBookShelfAdapter(mUserBookShelfResponses);
    //设置Item增加、移除动画
    mRecyclerView.setItemAnimator(new DefaultItemAnimator());
    final int space = DensityUtils.dp2px(getActivity(), 4);
    //自定义一个Decoration,用于recyclerView中每一个Item之间的间隔(后面将贴出代码【附1】)
    mRecyclerView.addItemDecoration(new StaggeredGridDecoration(space, space, space, space));
    mRecyclerView.setAdapter(mBookShelfAdapter);
    //绑定监听事件实现上拉加载更多【附2】
    mRecyclerView.addOnScrollListener(new RecyclerViewScrollDetector());
    //监听刷新
    mSwipeRefreshLayout.setOnRefreshListener(this);
  • 使用 ItemTouchHelper 工具类来处理 RecyclerView 中 item 的选中、滑动或(和)拖拽动作。 Google官方文档上是这么介绍的: This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView. 意思就是:这是一个支持 RecyclerView 滑动删除和拖拽的实用工具类

####看看它的构造函数:

 public ItemTouchHelper(Callback callback) {
        mCallback = callback;
    }

我们发现这里需要一个 Callback 参数,进入 ItemTouchHelper 我们会看到有一个 Callback 类,集成它需要实现一些方法

        //所有动作的标志
        @Override
        public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
            return 0;
        }

        //拖拽
        @Override
        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
            return false;
        }

        //滑动
        @Override
        public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {

        }

我们还会发现还有以下已经被封装好了的一个 SimpleCallback 类,Google 工程师已经为我们封装好了一些操作,让我们更简单的使用它。

ItemTouchHelper.Callback mCallback = new ItemTouchHelper.SimpleCallback(int dragDirs, int swipeDirs) {
    /**
     * @param recyclerView
     * @param viewHolder 拖动的ViewHolder
     * @param target 目标位置的ViewHolder
     * @return
     */
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        //...
        return false;
    }
    /**
     * @param viewHolder 滑动的ViewHolder
     * @param direction 滑动的方向
     */
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        //...
    }
};

这里来解释一下构造函数 SimpleCallback(int dragDirs, int swipeDirs)中的参数的含义,有一下这些可选项:

    /**
     * Up direction, used for swipe & drag control.
     */
    public static final int UP = 1;

    /**
     * Down direction, used for swipe & drag control.
     */
    public static final int DOWN = 1 << 1;

    /**
     * Left direction, used for swipe & drag control.
     */
    public static final int LEFT = 1 << 2;

    /**
     * Right direction, used for swipe & drag control.
     */
    public static final int RIGHT = 1 << 3;

即我们对哪些方向操作关心。如果我们关心用户向上拖动,可以将 dragDirs参数填充 UP | DOWN ,如果我们对左右滑动感兴趣,填充 swipeDirs参数为 LEFT | RIGHT 。0表示从不关心。

然后调用 attachToRecyclerView() 绑定动作

ItemTouchHelper touchHelper = new ItemTouchHelper(newSimpleItemTouchHelperCallback(0,ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT));
//attachToRecyclerView(将动作处理监听绑定到 RecyclerView 中)
touchHelper.attachToRecyclerView(mRecyclerView);

接下来我们看看 Callback 具体实现

class SimpleItemTouchHelperCallback extends ItemTouchHelper.SimpleCallback {
        //保存被删除item信息,用于撤销操作
        //这里使用队列数据结构,当连续滑动删除几个 item 时可能会保存多个 item 数据,并需要记录删除循序。
        BlockingQueue queue = new ArrayBlockingQueue(3);

        public SimpleItemTouchHelperCallback(int dragDirs, int swipeDirs) {
            super(dragDirs, swipeDirs);
        }

        @Override
        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
            //得到拖动 ViewHolder 的 position
            int fromPosition = viewHolder.getAdapterPosition();
            //得到目标 ViewHolder 的 position
            int toPosition = target.getAdapterPosition();
            if (fromPosition < toPosition) {
                //分别把中间所有的 item 的位置重新交换
                for (int i = fromPosition; i < toPosition; i++) {
                    Collections.swap(mUserBookShelfResponses, i, i + 1);
                }
            } else {
                for (int i = fromPosition; i > toPosition; i--) {
                    Collections.swap(mUserBookShelfResponses, i, i - 1);
                }
            }
            mBookShelfAdapter.notifyItemMoved(fromPosition, toPosition);
            //返回 true 表示执行拖动
            return true;
        }

        @Override
        public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
            //记录将要删除 item 的位置
            int position = viewHolder.getAdapterPosition();
            final UserBookShelfResponse bookShelfResponse = mUserBookShelfResponses.get(position);
            bookShelfResponse.setIndex(position);
            //将位置放到数据中,再保存到队列中方便操作
            queue.add(bookShelfResponse);
            //滑动删除,将该 item 数据从集合中移除,
            //被移除的数据可能还需要被撤销,已经被保存到队列中了
            mUserBookShelfResponses.remove(position);
            mBookShelfAdapter.notifyItemRemoved(position);
        }
        //处理动画
        @Override
        public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
            if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
                //滑动时改变 Item 的透明度,以实现滑动过程中实现渐变效果
                final float alpha = 1 - Math.abs(dX) / (float) viewHolder.itemView.getWidth();
                viewHolder.itemView.setAlpha(alpha);
                viewHolder.itemView.setTranslationX(dX);
            } else {
                super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
            }
        }

        //滑动事件完成时回调
        //在这里可以实现撤销操作
        @Override
        public void clearView(final RecyclerView recyclerView, final RecyclerView.ViewHolder viewHolder) {
            super.clearView(recyclerView, viewHolder);
            if (!queue.isEmpty()) {
                //如果队列中有数据,说明刚才有删掉一些 item
                Snackbar.make(((BaseActivity) getActivity()).getToolbar(), R.string.delete_bookshelf_success, Snackbar.LENGTH_LONG).setAction(R.string.repeal, new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                    //SnackBar 的撤销按钮被点击,队列中取出刚被删掉的数据,然后再添加到数据集合,实现数据被撤回的动作
                        final UserBookShelfResponse bookShelfResponse = (UserBookShelfResponse) queue.remove();
                    //通知 Adapter                        mBookShelfAdapter.notifyItemInserted(bookShelfResponse.getIndex());
                        mUserBookShelfResponses.add(bookShelfResponse.getIndex(), bookShelfResponse);
                        //实际开发中遇到一个 bug:删除第一个item再撤销出现的试图延迟
                        //手动将 recyclerView 滑到顶部可以解决这个bug
                        if (bookShelfResponse.getIndex() == 0) {
                            mRecyclerView.smoothScrollToPosition(0);
                        }
                    }
                }).setCallback(new Snackbar.Callback() {
                //不撤销将做正在的删除操作,监听 SnackBar 消失事件,
                //SnackBar 消失(非排挤式消失)出队、访问服务器删除数据。
                    @Override
                    public void onDismissed(Snackbar snackbar, int event) {
                        super.onDismissed(snackbar, event);
                        //event 为消失原因,详细介绍在下文【附3】
                        //排除一种情况就是联系删除多个 item SnackBar 挤掉前一个 SnackBar 导致的
                        //消失
                        if (event != DISMISS_EVENT_ACTION) {
                            final UserBookShelfResponse bookShelfResponse = (UserBookShelfResponse) queue.remove();
                            mLibraryPresenter.setBookShelf(BaseApplication.getUserId(), BaseApplication.getUserPassword(),
                                    bookShelfResponse.getName(), bookShelfResponse.getRemark(), bookShelfResponse.getID(), 1);
                        }
                    }
                }).show();
            }
        }

        //是否长按进行拖拽
        @Override
        public boolean isLongPressDragEnabled() {
            return true;
        }
    }

附1

/**
 * Author   :hymanme
 * Email    :[email protected]
 * Create at 2016/1/12
 * Description:
 */
public class StaggeredGridDecoration extends RecyclerView.ItemDecoration {
    private int left;
    private int top;
    private int right;
    private int bottom;

    public StaggeredGridDecoration(int left, int top, int right, int bottom) {
        this.left = left;
        this.top = top;
        this.right = right;
        this.bottom = bottom;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        outRect.bottom = bottom;
        if (parent.getChildAdapterPosition(view) < 2) {
            outRect.top = 2 * top;
        } else {
            outRect.top = top;
        }
        if (parent.getChildAdapterPosition(view) % 2 == 0) {
            outRect.left = 2 * left;
            outRect.right = right;
        } else {
            outRect.left = left;
            outRect.right = 2 * right;
        }
    }
}

附2

    class RecyclerViewScrollDetector extends RecyclerView.OnScrollListener {
        //如果你的 recyclerview 是列表只需要用一个int就可以,private int lastVisibleItem;
        //如果是 gridview 几列就需要几个数据的一个数组,
        //如3列 private int[] lastVisibleItem = {0, 0, 0};
        private int[] lastVisibleItem = {0, 0};
        private int mScrollThreshold = DensityUtils.dp2px(x.app(), 1);

        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            if (newState == RecyclerView.SCROLL_STATE_IDLE &&
                    (lastVisibleItem[0] + mLayoutManager.getSpanCount() >= mBookShelfAdapter.getItemCount())) {
                onLoadMore();
            }
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            boolean isSignificantDelta = Math.abs(dy) > mScrollThreshold;

            if (isSignificantDelta) {
                if (dy > 0) {
                    ((MainActivity) getActivity()).hideFloatingBar();
                } else {
                    ((MainActivity) getActivity()).showFloatingBar();
                }
            }
            lastVisibleItem = mLayoutManager.findLastCompletelyVisibleItemPositions(null);
        }
    }

附3

    /** Indicates that the Snackbar was dismissed via a swipe.*/
    public static final int DISMISS_EVENT_SWIPE = 0;
    /** Indicates that the Snackbar was dismissed via an action click.*/
    public static final int DISMISS_EVENT_ACTION = 1;
    /** Indicates that the Snackbar was dismissed via a timeout.*/
    public static final int DISMISS_EVENT_TIMEOUT = 2;
    /** Indicates that the Snackbar was dismissed via a call to {@link #dismiss()}.*/
    public static final int DISMISS_EVENT_MANUAL = 3;
    /** Indicates that the Snackbar was dismissed from a new Snackbar being shown.*/
    public static final int DISMISS_EVENT_CONSECUTIVE = 4;

附4

//你还可以复写以下方法定义选中item时动画逻辑
@Overridepublic void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
  super.onSelectedChanged(viewHolder, actionState);
  //当选中 Item 时候会调用该方法,重写此方法可以实现选中时候的一些动画逻辑
  Log.v("zxy","onSelectedChanged");
}

总结 RecyclerView 是 ListView 的很好的替代品,它不仅带给我们更加方便地实现方法,还优化了视图复用效率。原本用 listview 很难实现的功能用 RecyclerView 来实现,却变得很简单。吼吼。

如果本文对你有帮助,不妨给博主一点鼓励