android RecyclerView实现条目Item拖拽排序与滑动删除
效果演示
需求和技术分析
- recyclerview item拖拽排序::长按recyclerview的item或者触摸item的某个按钮。
- recyclerview item滑动删除:recyclerview item滑动删除:recyclerview的item滑动删除。
实现方案与技术
利用itemtouchhelper绑定recyclerview、itemtouchhelper.callback来实现ui更新,并且实现动态控制是否开启拖拽功能和滑动删除功能。
实现步骤
- 继承抽象类itemtouchhelper,并在构造方法传入实现的itemtouchhelper.callback。
- recyclerview绑定itemtouchhelper:itemtouchhelper.attachtorecyclerview(recyclerview)。
- 自定义itemtouchhelper.callback的实现接口onitemtouchcallbacklistener,由外部更新recyclerview的item。
几个主要的布局
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v7.widget.recyclerview android:id="@+id/rv_main" android:layout_width="match_parent" android:layout_height="wrap_content" /> </linearlayout>
这个没啥好说的了吧,就是一个recyclerview啦。
接下来是recyclerview的item的布局item.xml:
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="?android:listpreferreditemheight" android:background="?selectableitembackground"> <imageview android:id="@+id/iv_touch" style="@style/itemstyle" android:layout_height="match_parent" android:layout_alignparentend="true" android:layout_alignparentright="true" android:src="@android:drawable/alert_dark_frame" /> <checkbox android:id="@+id/cb_item_check" style="@style/itemstyle" android:layout_alignparentleft="true" android:layout_alignparentstart="true" /> <textview android:id="@+id/tv_name" style="@style/itemstyle" android:layout_toendof="@id/cb_item_check" android:layout_torightof="@id/cb_item_check" /> <textview android:id="@+id/tv_sex" style="@style/itemstyle" android:layout_marginleft="@dimen/dp_10" android:layout_marginstart="@dimen/dp_10" android:layout_toendof="@id/tv_name" android:layout_torightof="@id/tv_name" /> </relativelayout>
这个也不用解释了,到时候下载看源码,就是普通item,展示数据而已。
实现自己的defaultitemtouchhelper:继承itemtouchhelper
public class defaultitemtouchhelper extends itemtouchhelper { public defaultitemtouchhelper(itemtouchhelp.callback callback) { super(callback); } }
好嘛,这个太简单了,基本上一行代码都不用写。但是这里需要一个itemtouchhelp.callback啊,所以我们还是要实现一个itemtouchhelp.callback,客观且看下文分解。
实现自己的itemtouchhelper.callback:继承itemtouchhelper.callback
这里是全文最重要的部分啦,要认真点看噢,先上代码,后解释,其他看注释和视频。
public class defaultitemtouchhelpcallback extends itemtouchhelper.callback { /** * item操作的回调 */ private onitemtouchcallbacklistener onitemtouchcallbacklistener; /** * 是否可以拖拽 */ private boolean iscandrag = false; /** * 是否可以被滑动 */ private boolean iscanswipe = false; public defaultitemtouchhelpcallback(onitemtouchcallbacklistener onitemtouchcallbacklistener) { this.onitemtouchcallbacklistener = onitemtouchcallbacklistener; } /** * 设置item操作的回调,去更新ui和数据源 * * @param onitemtouchcallbacklistener */ public void setonitemtouchcallbacklistener(onitemtouchcallbacklistener onitemtouchcallbacklistener) { this.onitemtouchcallbacklistener = onitemtouchcallbacklistener; } /** * 设置是否可以被拖拽 * * @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 recyclerview * @param srcviewholder 拖拽的viewholder * @param targetviewholder 目的地的viewholder * @return */ @override public boolean onmove(recyclerview recyclerview, viewholder srcviewholder, viewholder targetviewholder) { if (onitemtouchcallbacklistener != null) { return onitemtouchcallbacklistener.onmove(srcviewholder.getadapterposition(), targetviewholder.getadapterposition()); } return false; } @override public void onswiped(viewholder viewholder, int direction) { if (onitemtouchcallbacklistener != null) { onitemtouchcallbacklistener.onswiped(viewholder.getadapterposition()); } } public interface onitemtouchcallbacklistener { /** * 当某个item被滑动删除的时候 * * @param adapterposition item的position */ void onswiped(int adapterposition); /** * 当两个item位置互换的时候被回调 * * @param srcposition 拖拽的item的position * @param targetposition 目的地的item的position * @return 开发者处理了操作应该返回true,开发者没有处理就返回false */ boolean onmove(int srcposition, int targetposition); } }
好,其实上面最重要的就是五个方法:
/** * 是否可以长按拖拽排序。 */ @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的时候需要我们告诉系统滑动或者拖拽的方向,那我们又知道支持拖拽和滑动删除的无非就是linearlayoutmanager和gridlayoutmanager了,相当于我们老早的时候用的listview和gridview了。所以我们根据布局管理器的不同做了响应的区分。
getmovementflags()说明二:其他都好理解,就是这里的return makemovementflags(dragflag, swipeflag);这句话是最终的返回值,也就是它决定了我们的拖拽或者滑动的方法。第一个参数是拖拽flag,第二个是滑动的flag。
重新定义defaultitemtouchhelper
我们记得上面定义了一个defaultitemtouchhelper,它的构造中需要传一个itemtouchhelper.callback,既然我们实现礼了,我们再把defaultitemtouchhelper做个封装,使使用者更傻瓜式的调用。
public class defaultitemtouchhelper extends yolandaitemtouchhelper { private defaultitemtouchhelpcallback itemtouchhelpcallback; public defaultitemtouchhelper(defaultitemtouchhelpcallback.onitemtouchcallbacklistener onitemtouchcallbacklistener) { super(new defaultitemtouchhelpcallback(onitemtouchcallbacklistener)); itemtouchhelpcallback = (defaultitemtouchhelpcallback) getcallback(); } /** * 设置是否可以被拖拽 * * @param candrag 是true,否false */ public void setdragenable(boolean candrag) { itemtouchhelpcallback.setdragenable(candrag); } /** * 设置是否可以被滑动 * * @param canswipe 是true,否false */ public void setswipeenable(boolean canswipe) { itemtouchhelpcallback.setswipeenable(canswipe); } }
现在我们看到已经不需要传itemtouchhelper.callback给itemtouchhelper了,只需要传我们在defaultitemtouchhelpcallback中定义好的onitemtouchcallbacklistener就好了,而且提供了设置是否可以滑动和是否可以拖拽的方法,而onitemtouchcallbacklistener只是通知外部滑动了、删除了,你去更新ui吧。
这里可以有的同学会有疑问,上面原来不是继承itemtouchhelper吗?这里咋就变成了yolandaitemtouchhelper了呢?因为我们看到这里多了一句itemtouchhelpcallback = getcallback();,这个getcallback();这个方法是没有的,是我们在yolandaitemtouchhelper中自定义的,因为我们想在defaultitemtouchhelper中提供外部设置是否可以拖拽和滑动删除的方法,就得拿到这个callback,所以我看了下源码,我们在itemtouchhelper构造中把callback穿进去,它保存的时候一个package级别的成员变量,所以我在android.support.v7.widget.helper包下新建了一个yolandaitemtouchhelper类:
public class yolandaitemtouchhelper extends itemtouchhelper { public yolandaitemtouchhelper(callback callback) { super(callback); } public callback getcallback() { return mcallback; } }
如何投入使用
好扯淡也扯完了,封装也封装完了,那么接下来就来在activity中使用下咯:
recyclerview绑定itemtouchhelper
没啥好说的用itemtouchhelper.attachtorecyclerview(recyclerview)绑定recyclerview和itemtouchhelper,并且只是允许拖拽和滑动删除item:
defaultitemtouchhelper itemtouchhelper = new defaultitemtouchhelper(onitemtouchcallbacklistener); itemtouchhelper.attachtorecyclerview(recyclerview); itemtouchhelper.setdragenable(true); itemtouchhelper.setswipeenable(true);
看到上面还缺少一个onitemtouchcallbacklistener吧,这个也比较重要。
使用callback自定义的onitemtouchcallbacklistener刷新ui
我们在自定义callback的时候不是在onmove()和onswiped()方法中回调onitemtouchcallbacklistener去更新ui吗?这里就是onitemtouchcallbacklistener如何更新ui的操作了,完成这个操作,那么我们的目的就达到了:
private defaultitemtouchhelpcallback.onitemtouchcallbacklistener onitemtouchcallbacklistener = new defaultitemtouchhelpcallback.onitemtouchcallbacklistener() { @override public void onswiped(int adapterposition) { // 滑动删除的时候,从数据源移除,并刷新这个item。 if (userinfolist != null) { userinfolist.remove(adapterposition); mainadapter.notifyitemremoved(adapterposition); } } @override public boolean onmove(int srcposition, int targetposition) { if (userinfolist != null) { // 更换数据源中的数据item的位置 collections.swap(userinfolist, srcposition, targetposition); // 更新ui中的item的位置,主要是给用户看到交互效果 mainadapter.notifyitemmoved(srcposition, targetposition); return true; } return false; } };
到这里就结束了,不信你去试试,源码传送门。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。