RecyclerView实现条目Item长按拖拽排序与滑动删除

效果演示

<img src="http://upload-images.jianshu.io/upload_images/2918620-eb4210464ce2c4b4.gif?imageMogr2/auto-orient/strip"/>

需求和技术分析

  1. RecyclerView Item拖拽排序:长按RecyclerView的Item或者触摸Item的某个按钮。
  2. RecyclerView Item滑动删除:RecyclerView Item滑动删除:RecyclerView的Item滑动删除。

实现方案与技术

利用ItemTouchHelper绑定RecyclerView、ItemTouchHelper.Callback来实现UI更新,并且实现动态控制是否开启拖拽功能和滑动删除功能。

实现步骤

  1. 继承抽象类ItemTouchHelper,并在构造方法传入实现的ItemTouchHelper.Callback。
  2. recyclerView绑定ItemTouchHelper:itemTouchHelper.attachToRecyclerView(recyclerView)。
  3. 自定义ItemTouchHelper.Callback的实现接口OnItemTouchCallbackListener,由外部更新RecyclerView的Item。

几个主要的布局

实现ItemTouchHelperCallback并继承自ItemTouchHelper.Callback

public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {

    private final ItemTouchHelperAdapter mAdapter;
    /**
     * 是否可以拖拽
     */
    private boolean isCanDrag = false;
    /**
     * 是否可以被滑动
     */
    private boolean isCanSwipe = false;

    public ItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
        mAdapter = adapter;
    }

    public ItemTouchHelperCallback(ItemTouchHelperAdapter adapter,boolean canDrag,boolean canSwipe) {
        mAdapter = adapter;
        this.isCanDrag = canDrag;
        this.isCanSwipe = canSwipe;
    }

    /**
     * 设置是否可以被拖拽
     *
     * @param canDrag 是true,否false
     */
    public void setDragEnable(boolean canDrag) {
        isCanDrag = canDrag;
    }

    /**
     * 设置是否可以被滑动
     *
     * @param canSwipe 是true,否false
     */
    public void setSwipeEnable(boolean canSwipe) {
        isCanSwipe = canSwipe;
    }

    /**
     * 当Item被长按的时候是否可以被拖拽
     * @return
     */
    @Override
    public boolean isLongPressDragEnabled() {
        return isCanDrag;
    }

    /**
     * Item是否可以被滑动(H:左右滑动,V:上下滑动)
     * @return
     */
    @Override
    public boolean isItemViewSwipeEnabled() {
        return isCanSwipe;
    }

    /**
     * 当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向
     * @param recyclerView
     * @param viewHolder
     * @return
     */
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {// GridLayoutManager
            // flag如果值是0,相当于这个功能被关闭
            int dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT | ItemTouchHelper.UP | ItemTouchHelper.DOWN;
            int swipeFlag = 0;
            // create make
            return makeMovementFlags(dragFlag, swipeFlag);
        } else if (layoutManager instanceof LinearLayoutManager) {// linearLayoutManager
            LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
            int orientation = linearLayoutManager.getOrientation();

            int dragFlag = 0;
            int swipeFlag = 0;

            // 为了方便理解,相当于分为横着的ListView和竖着的ListView
            if (orientation == LinearLayoutManager.HORIZONTAL) {// 如果是横向的布局
                swipeFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
                dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
            } else if (orientation == LinearLayoutManager.VERTICAL) {// 如果是竖向的布局,相当于ListView
                dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
                swipeFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
            }
            return makeMovementFlags(dragFlag, swipeFlag);
        }
        return 0;
    }

    /**
     * 当Item被拖拽的时候被回调
     * @param recyclerView
     * @param viewHolder
     * @param target
     * @return
     */
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        /**
         * 回调
         */
        mAdapter.onMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
        return true;
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) {
        /**
         * 回调
         */
        mAdapter.onSwipe(viewHolder.getAdapterPosition());
    }
}

上面主要就五个方法比较重要

/**
 * 是否可以长按拖拽排序。
 */
@Override
public boolean isLongPressDragEnabled() {}
/**
 * Item是否可以被滑动(H:左右滑动,V:上下滑动)
 */
@Override
public boolean isItemViewSwipeEnabled() {}
/**
 * 当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向
 */
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {}
/**
 * 当Item被拖拽的时候被回调
 */
@Override
public boolean onMove(RecyclerView r, ViewHolder rholer, ViewHolder tholder) {}

/**
 * 当View被滑动删除的时候
 */
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {}

isItemViewSwipeEnabled()返回值是否可以拖拽排序,true可以,false不可以,isItemViewSwipeEnabled()是否可以滑动删除,true可以,false不可以;这两个方法都是配置是否可以操作的。我们上面的代码中返回了一个成员变量值,并且这个值通过外部可以修改,所以提供了外部控制的方法。

onMove()当Item被拖拽排序移动到另一个Item的位置的时候被回调,onSwiped()当Item被滑动删除到不见;这两个方法是当用户操作了,来回调我们,我们就该去更新UI了。这里我们提供了一个Listener去通知外部,并且返回出去了必要的值,来降低代码耦合度。

getMovementFlags()说明一:是当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向,那我们又知道支持拖拽和滑动删除的无非就是LinearLayoutManagerGridLayoutManager了,相当于我们老早的时候用的ListViewGridView了。所以我们根据布局管理器的不同做了响应的区分。

getMovementFlags()说明二:其他都好理解,就是这里的return makeMovementFlags(dragFlag, swipeFlag);这句话是最终的返回值,也就是它决定了我们的拖拽或者滑动的方法。第一个参数是拖拽flag,第二个是滑动的flag。

实现ItemTouchHelperAdapter接口

用于监听长按拖拽或滑动删除时的事件

public interface ItemTouchHelperAdapter {

    /**
     * @param fromPosition 起始位置
     * @param toPosition 移动的位置
     */
    void onMove(int fromPosition, int toPosition);
    void onSwipe(int position);
}

定义RecyclerViewAdapter并实现ItemTouchHelperAdapter 接口

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> implements ItemTouchHelperAdapter{
    private List<String> mDataList = new ArrayList<>();

    /**
     * 当加载更多的时候可以使用
     *
     * @param dataList
     */
    public void addData(List<String> dataList) {
        if (dataList != null) {
            mDataList.addAll(dataList);
        }
        notifyDataSetChanged();
    }

    /**
     * 更新Adapter
     *
     * @param dataList
     */
    public void replaceData(List<String> dataList) {
        if (dataList != null) {
            mDataList.clear();
            addData(dataList);
        }
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_main, parent, false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        String data = mDataList.get(position);
        holder.tv.setText(data);
    }

    @Override
    public int getItemCount() {
        return mDataList.size();
    }

    @Override
    public void onMove(int fromPosition, int toPosition) {
        /**
         * 在这里进行给原数组数据的移动
         */
        Collections.swap(mDataList, fromPosition, toPosition);
        /**
         * 通知数据移动
         */
        notifyItemMoved(fromPosition, toPosition);
    }

    @Override
    public void onSwipe(int position) {
        /**
         * 原数据移除数据
         */
        mDataList.remove(position);
        /**
         * 通知移除
         */
        notifyItemRemoved(position);
    }

    class ViewHolder extends RecyclerView.ViewHolder{
        private TextView tv;
        private ImageView iv;
        public ViewHolder(View itemView) {
            super(itemView);
            tv = (TextView) itemView.findViewById(R.id.text);
            iv = (ImageView) itemView.findViewById(R.id.handle);
        }
    }
}

最后让RecyclerView绑定ItemTouchHelper

public class MainActivity extends AppCompatActivity {
    private RecyclerViewAdapter mAdapter;
    private RecyclerView mRecyclerView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRecyclerView = (RecyclerView) findViewById(R.id.rv);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));
//        mRecyclerView.setLayoutManager(new GridLayoutManager(this,3));
        mAdapter = new RecyclerViewAdapter();
        mRecyclerView.setAdapter(mAdapter);
        initData();
    }

    private void initData() {
        List<String> list = new ArrayList<>();
        for(int i=0;i<100;i++){
            list.add("第"+i+"个");
        }
        mAdapter.addData(list);

        ItemTouchHelperCallback helperCallback = new ItemTouchHelperCallback(mAdapter);
        helperCallback.setSwipeEnable(true);
        helperCallback.setDragEnable(true);
        ItemTouchHelper helper = new ItemTouchHelper(helperCallback);
        helper.attachToRecyclerView(mRecyclerView);
    }
}

到此结束
<a href="http://download.csdn.net/detail/zuo1141783497/9889404">源码下载</a>
申明:以上有些解释是借鉴网上的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容