Android开发在RecyclerView上面实现"拖放"和"滑动删除"-2
上篇给大家介绍了android一步步带你在recyclerview上面实现"拖放"和"滑动删除"功能
效果如下:
拖动手柄
在设计一个支持"拖放"的列表时, 通常提供一个在触摸时初始化拖拽的"拖动手柄". 因其可发现性和可用性而被material guidelines所推荐, 尤其是列表处于"可编辑模式"时.
首先更新item的布局(item_main.xml):
<framelayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/item" android:layout_width="match_parent" android:layout_height="?listpreferreditemheight" android:clickable="true" android:focusable="true" android:foreground="?selectableitembackground"> <textview android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginleft="16dp" android:textappearance="?android:attr/textappearancemedium" /> <imageview android:id="@+id/handle" android:layout_width="?listpreferreditemheight" android:layout_height="match_parent" android:layout_gravity="center_vertical|right" android:scaletype="center" android:src="@drawable/ic_reorder_grey_500_24dp" /> </framelayout>
用作"拖放手柄"的图片可以在material design icon找到, 也可以方便地通过android material design icon generator plugin添加.
我们曾经提到过, 可以通过代码itemtouchhelper.startdrag(recyclerview.viewholder)
来开启拖动. 所以我们要做的就是更新viewholder来包含新的手柄视图, 并设置一个简单的触摸事件接口, 以触发startdrag()方法.
我们需要定义一个接口来传递拖动事件.
public interface onstartdraglistener { /** * called when a view is requesting a start of a drag. * * @param viewholder the holder of the view to drag. */ void onstartdrag(recyclerview.viewholder viewholder); }
然后, 在itemviewholder中实现化手柄视图.
public final imageview handleview; public itemviewholder(view itemview) { super(itemview); // ... handleview = (imageview) itemview.findviewbyid(r.id.handle); }
并且更新adapter.
private final onstartdraglistener mdragstartlistener; public recyclerlistadapter(onstartdraglistener dragstartlistener) { mdragstartlistener = dragstartlistener; // ... } @override public void onbindviewholder(final itemviewholder holder, int position) { // ... holder.handleview.setontouchlistener(new ontouchlistener() { @override public boolean ontouch(view v, motionevent event) { if (motioneventcompat.getactionmasked(event) == motionevent.action_down) { mdragstartlistener.onstartdrag(holder); } return false; } }); }
完整的adapter应该看起来像这个.
剩下的是把onstartdraglistener添加到fragment.
public class recyclerlistfragment extends fragment implements onstartdraglistener { // ... @override public void onviewcreated(view view, bundle icicle) { super.onviewcreated(view, icicle); recyclerlistadapter a = new recyclerlistadapter(this); // ... } @override public void onstartdrag(recyclerview.viewholder viewholder) { mitemtouchhelper.startdrag(viewholder); } }
但你运行之后, 可以看到这样的效果:
标示选中视图
在上一篇的基础示例中, 被拖拽的item事实上是被选中的, 但是没有可视化的标示. 由于显著的理由, 这是不受欢迎的, 但也很容易修复. 事实上, 在itemtouchhelper的帮助下, 只要你的viewholder的itemview设置了background集合(selector), 就会得到相应的效果. 在lollipop及之后的版本, item view的elevation在拖拽和滑动期间会增加. 而在之前的版本中, 滑动时会有fade效果. 看起来就像:
效果看起来不错, 但你可能想要更多的控制. 一种方法是, 无论任何时候viewholder被选中或者清空, 让item自己处理这种改变. 由此, itemtouchhelper.callback提供了两种回调.
onselectedchanged(recyclerview.viewholder, int). 每一次viewholder的状态, 变成drag(action_state_drag)或者swipe(action_state_swipe)时, 该方法就会被调用. 这时候是将viewholder的状态变成active的完美时刻.
clearview(recyclerview, recyclerview.viewholder). 在被拖拽的viewholder放下时, 或者是滑动操作取消或者完成时(action_state_idle), 这里会是将itemview状态设置为idle的最好的地方.
那么, 我们就把两者绑定在一起实现.
首先, 创建一个接口, 让目标viewholder实现:
/** * notifies a view holder of relevant callbacks from * {@link itemtouchhelper.callback}. */ public interface itemtouchhelperviewholder { /** * called when the {@link itemtouchhelper} first registers an * item as being moved or swiped. * implementations should update the item view to indicate * it's active state. */ void onitemselected(); /** * called when the {@link itemtouchhelper} has completed the * move or swipe, and the active item state should be cleared. */ void onitemclear(); }
接着, 触发相应的回调方法:
@override public void onselectedchanged(recyclerview.viewholder viewholder, int actionstate) { // we only want the active item if (actionstate != itemtouchhelper.action_state_idle) { if (viewholder instanceof itemtouchhelperviewholder) { itemtouchhelperviewholder itemviewholder = (itemtouchhelperviewholder) viewholder; itemviewholder.onitemselected(); } } super.onselectedchanged(viewholder, actionstate); } @override public void clearview(recyclerview recyclerview, recyclerview.viewholder viewholder) { super.clearview(recyclerview, viewholder); if (viewholder instanceof itemtouchhelperviewholder) { itemtouchhelperviewholder itemviewholder = (itemtouchhelperviewholder) viewholder; itemviewholder.onitemclear(); } }
现在, 剩下的是让itemviewholder实现itemtouchhelperviewholder:
public class itemviewholder extends recyclerview.viewholder implements itemtouchhelperviewholder { // ... @override public void onitemselected() { itemview.setbackgroundcolor(color.ltgray); } @override public void onitemclear() { itemview.setbackgroundcolor(0); } }
考虑到这仅仅是一个示例, 我们仅仅是在item处于active状态时设置了灰色背景, 当item被清空时, 把灰色背景删除了. 如果你的adapter和itemtouchhelper紧密结对的话, 轻易地放弃这个设置也行, 转而可以直接在itemtouchhelper.callback转变item的状态.
grid布局
如果你想在本项目中转而使用gridlayoutmanager, 你会发现并没有正常地起作用. 原因以及修复原因都很简单: 必须告诉itemtouchhelper我们想要支持左右拖拽. 在simpletouchhelpercallback中, 我们已经声明:
@override public int getmovementflags(recyclerview recyclerview, recyclerview.viewholder viewholder) { int dragflags = itemtouchhelper.up | itemtouchhelper.down; int swipeflags = itemtouchhelper.start | itemtouchhelper.end; return makemovementflags(dragflags, swipeflags); }
支持grid布局所要求的唯一改变是dragflags中添加左右方向:
int dragflags = itemtouchhelper.up | itemtouchhelper.down | itemtouchhelper.left | itemtouchhelper.right;
但是, 对于grid而言, "滑动删除"并不是一个天然的功能模式, 所以你也许会写一些如下的代码:
@override public int getmovementflags(recyclerview recyclerview, recyclerview.viewholder viewholder) { int dragflags = itemtouchhelper.up | itemtouchhelper.down | itemtouchhelper.left | itemtouchhelper.right; int swipeflags = 0; return makemovementflags(dragflags, swipeflags); }
grid上面的上下左右"拖放"效果看起来如下:
自定义滑动动画
对我们而言, 在viewholder拖拽和滑动的时候, itemtouchhelper.callback为我们提供了一个十分方便的方式来完全控制viewholder的动画. 因为itemtouchhelper是一个itemdecoration, 我们可以用类似的方式, 详细地了解视图的绘制(hook into the view drawing).
下面是一个简单的例子, 来覆盖默认的滑动动画, 来展示一个线性的fade效果.
@override public void onchilddraw(canvas c, recyclerview recyclerview, viewholder viewholder, float dx, float dy, int actionstate, boolean iscurrentlyactive) { if (actionstate == itemtouchhelper.action_state_swipe) { float width = (float) viewholder.itemview.getwidth(); float alpha = 1.0f - math.abs(dx) / width; viewholder.itemview.setalpha(alpha); viewholder.itemview.settranslationx(dx); } else { super.onchilddraw(c, recyclerview, viewholder, dx, dy, actionstate, iscurrentlyactive); } }
参数dx和dy代表选中视图的当前位置,
- -1.0f表示完完全全的从itemtouchhelper.end到itemtouchhelper.start的滑动.
- 1.0f表示完完全全的从itemtouchhelper.start到itemtouchhelper.end的滑动.
对于任何没有处理的actionstate你都可以调用super的方法, 从而使用默认的动画.
总结
我们仅仅了解了自定义itemtouchhelper所能做的事情中有趣的部分, 我希望能够在一篇文章中包含更多内容, 但是考虑到文章长度, 还是分开比较好.
以上所述是小编给大家介绍的android开发在recyclerview上面实现"拖放"和"滑动删除功能(二),希望对大家有所帮助