现在RecyclerView的应用越来越广泛了,不同的应用场景需要其作出不同的改变。有时候我们可能需要实现侧滑删除的功能,比如知乎首页的侧滑删除,又或者长按Item进行拖动与其他Item进行位置的交换,但RecyclerView没有提供现成的API供我们操作,所幸SDK提供了ItemTouchHelper这样一个工具类帮助我们快速实现以上功能。不多说别的,我们来介绍一下ItemTouchHelper。

什么是ItemTouchHelper

This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.It works with a RecyclerView and a Callback class, which configures what type of interactions are enabled and also receives events when user performs these actions.Depending on which functionality you support, you should override onMove(RecyclerView, ViewHolder, ViewHolder) and / or onSwiped(ViewHolder, int).

以上是官方文档的介绍,ItemTouchHelper是一个工具类,可实现侧滑删除和拖拽移动,使用这个工具类需要RecyclerView和Callback。同时根据需要重写onMove和onSwiped方法。接下来就来讲述ItemTouchHelper的使用方法。

ItemTouchHelper基本使用方法

step.1新建一个接口,让Adapter实现之

从解耦的角度考虑,我们需要一个接口来实现Adapter和ItemTouchHelper之间涉及数据的操作,因为ItemTouchHelper在完成触摸的各种动画后,就要对Adapter的数据进行操作,比如侧滑删除操作,最后需要调用Adapter的notifyItemRemove()方法来移除该数据。因此我们可以把数据操作的部分抽象成一个接口方法,让ItemTouchHelper.Callback调用该方法即可。具体如下: 
新建ItemTouchHelperAdapter:

public interface ItemTouchHelperAdapter {
//数据交换
void onItemMove(int fromPosition,int toPosition);
//数据删除
void onItemDissmiss(int position);
}

让我们的Adapter实现该接口:

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements ItemTouchHelperAdapter {
//数据
private List<String> mData;
...
@Override
public void onItemMove(int fromPosition, int toPosition) {
//交换位置
Collections.swap(mData,fromPosition,toPosition);
notifyItemMoved(fromPosition,toPosition);
} @Override
public void onItemDissmiss(int position) {
//移除数据
mData.remove(position);
notifyItemRemoved(position);
} }

那么我们在ItemTouchHelper.Callback内直接调用接口的方法即可。

step.2新建类继承自ItemTouchHelper.Callback

从官方文档我们知道,使用ItemTouchHelper需要一个Callback,该Callback是ItemTouchHelper.Callback的子类,所以我们需要新建一个类比如SimpleItemTouchHelperCallback继承自ItemTouchHelper.Callback。我们可以重写其数个方法来实现我们的需求。我们先来看看ItemTouchHelper.Callback需要重写的几个常用的方法。 
1、public int getMovementFlags(RecyclerView, RecyclerView.ViewHolder):该方法用于返回可以滑动的方向,比如说允许从右到左侧滑,允许上下拖动等。我们一般使用makeMovementFlags(int,int)或makeFlag(int, int)来构造我们的返回值。 
例如:要使RecyclerView的Item可以上下拖动,同时允许从右到左侧滑,但不许允许从左到右的侧滑,我们可以这样写:

    @Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; //允许上下的拖动
int swipeFlags = ItemTouchHelper.LEFT; //只允许从右向左侧滑
return makeMovementFlags(dragFlags,swipeFlags);
}

2、public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target)
当用户拖动一个Item进行上下移动从旧的位置到新的位置的时候会调用该方法,在该方法内,我们可以调用Adapter的notifyItemMoved方法来交换两个ViewHolder的位置,最后返回true,表示被拖动的ViewHolder已经移动到了目的位置。所以,如果要实现拖动交换位置,可以重写该方法(前提是支持上下拖动):

    @Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
//onItemMove是接口方法
mAdapter.onItemMove(viewHolder.getAdapterPosition(),target.getAdapterPosition());
return true;
}

3、public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) 
当用户左右滑动Item达到删除条件时,会调用该方法,一般手指触摸滑动的距离达到RecyclerView宽度的一半时,再松开手指,此时该Item会继续向原先滑动方向滑过去并且调用onSwiped方法进行删除,否则会反向滑回原来的位置。在该方法内部我们可以这样写:

    @Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
//onItemDissmiss是接口方法
mAdapter.onItemDissmiss(viewHolder.getAdapterPosition());
}

如果在onSwiped方法内我们没有进行任何操作,即不删除已经滑过去的Item,那么就会留下空白的地方,因为实际上该ItemView还占据着该位置,只是移出了我们的可视范围内罢了。

4、public boolean isLongPressDragEnabled():该方法返回true时,表示支持长按拖动,即长按ItemView后才可以拖动,我们遇到的场景一般也是这样的。默认是返回true。

5、public boolean boolean isItemViewSwipeEnabled():该方法返回true时,表示如果用户触摸并左右滑动了View,那么可以执行滑动删除操作,即可以调用到onSwiped()方法。默认是返回true。

6、public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState):从静止状态变为拖拽或者滑动的时候会回调该方法,参数actionState表示当前的状态。

7、public void clearView(RecyclerView recyclerView, ViewHolder viewHolder):当用户操作完毕某个item并且其动画也结束后会调用该方法,一般我们在该方法内恢复ItemView的初始状态,防止由于复用而产生的显示错乱问题。

8、public void onChildDraw(…):我们可以在这个方法内实现我们自定义的交互规则或者自定义的动画效果。 
那么完整的SimpleItemTouchHelperCallback文件是这样的:

public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback{

    private ItemTouchHelperAdapter mAdapter;

    public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter){
mAdapter = adapter;
} @Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.LEFT;
return makeMovementFlags(dragFlags,swipeFlags);
} @Override
public boolean isLongPressDragEnabled() {
return true;
} @Override
public boolean isItemViewSwipeEnabled() {
return true;
} @Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
mAdapter.onItemMove(viewHolder.getAdapterPosition(),target.getAdapterPosition());
return true;
} @Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
mAdapter.onItemDissmiss(viewHolder.getAdapterPosition());
}
}

step.3为RecycleView添加ItemTouchHelper

上面我们修改了Adapter和新建了ItemTouchHelper.Callback的子类,接下来我们要为RecyclerView添加ItemTouchHelper:

    //先实例化Callback
ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(myAdapter);
//用Callback构造ItemtouchHelper
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
//调用ItemTouchHelper的attachToRecyclerView方法建立联系
touchHelper.attachToRecyclerView(mRecyclerView);

经过以上步骤,我们已经实现了Item的拖拽和侧滑删除功能了,看一下效果: 

自定义侧滑动画

有时候我们对默认的动画效果可能不满意,需要自己实现想要的动画效果,ItemTouchHelper.Callback提供的onChildDraw方法可以让我们很方便地实现想要的效果。以下带来一种自定义的实现效果,当做抛砖引玉,让大家熟悉自定义效果的运用。先来看看要实现的效果: 
 
该效果是比较常见的,用户向左滑动Item的时候,一开始提示的是“左滑删除”,滑动到一定距离后,显示删除的图标,并且随着滑动距离的增加该图标不断变大,达到最大后用户松开手指,该Item被删除。 
接下来我们来分析一下怎样实现以上的效果: 
首先,要想左滑出现一个删除的方块,可以在LinearLayout放一个这样的“方块”,让它与Item水平并排排列,以下是布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="horizontal"> <android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="#ffffff"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginBottom="4dp"
app:cardCornerRadius="1dp"
app:elevation="1dp"
app:contentPadding="1dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff">
<TextView
android:id="@+id/item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="22sp"
android:padding="4dp"
android:layout_centerInParent="true"/> </RelativeLayout>
</android.support.v7.widget.CardView> <FrameLayout
android:layout_width="100dp"
android:layout_height="match_parent"
android:layout_marginRight="4dp"
android:layout_marginBottom="4dp"
android:background="#f33213"> <ImageView
android:id="@+id/iv_img"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center"
android:src="@mipmap/ic_eye_72"
android:visibility="invisible"/>
<TextView
android:id="@+id/tv_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="左滑删除"
android:textSize="18sp"
android:textColor="#ffffff"
android:layout_gravity="center"/>
</FrameLayout>
</LinearLayout>

布局文件修改后,我们尝试来滑动一下,发现后面的删除方块并不会出现,这是因为默认的滑动方式是setTranslationX(int),即是对整个View的滑动,所以无论我们怎样滑动,都不会出现删除方块。因此,我们要改变一个种滑动方式,比如使用scrollTo(int,int),这种是对View的内容的滑动,所以随着左滑,item会向左滑去,而位于右方的方块自然也就出现了。 
接着,我们考虑该“删除眼睛”的图标是怎样从小变大的,这个实现也比较简单,只要根据滑动的距离对该ImageView的LayoutParams.width进行改变就行了,不过要注意限制大小,否则过大会造成图片的失真。当滑动距离等于RecyclerView宽度的一半时,此时松开手会使Item删除,那么我们可以在该滑动距离达到该值时时“眼睛”变得最大,此时可以达到良好的交互效果,提示了用户无需继续滑动即可删除该Item了。 
最后我们要考虑的是:在删除了Item或者没删除而滑回原来的位置后,我们要把所做的改变重置一下,否则,会由于RecyclerView的复用而导致其他位置的ViewHolder与当前的ViewHolder所做的改变一样,即造成显示的错误。我们可以在clearView()方法内重置改变,这样就能解决因复用而导致的显示问题了。 
最后我们来看看SimpleItemTouchHelperCallback的代码:

public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback{

    //省略上面的代码....

    //限制ImageView长度所能增加的最大值
private double ICON_MAX_SIZE = 50;
//ImageView的初始长宽
private int fixedWidth = 150; @Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
//重置改变,防止由于复用而导致的显示问题
viewHolder.itemView.setScrollX(0);
((MyAdapter.NormalItem)viewHolder).tv.setText("左滑删除");
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) ((MyAdapter.NormalItem) viewHolder).iv.getLayoutParams();
params.width = 150;
params.height = 150;
((MyAdapter.NormalItem) viewHolder).iv.setLayoutParams(params);
((MyAdapter.NormalItem) viewHolder).iv.setVisibility(View.INVISIBLE);
} @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){
//如果dX小于等于删除方块的宽度,那么我们把该方块滑出来
if (Math.abs(dX) <= getSlideLimitation(viewHolder)){
viewHolder.itemView.scrollTo(-(int) dX,0);
}
//如果dX还未达到能删除的距离,此时慢慢增加“眼睛”的大小,增加的最大值为ICON_MAX_SIZE
else if (Math.abs(dX) <= recyclerView.getWidth() / 2){
double distance = (recyclerView.getWidth() / 2 -getSlideLimitation(viewHolder));
double factor = ICON_MAX_SIZE / distance;
double diff = (Math.abs(dX) - getSlideLimitation(viewHolder)) * factor;
if (diff >= ICON_MAX_SIZE)
diff = ICON_MAX_SIZE;
((MyAdapter.NormalItem)viewHolder).tv.setText(""); //把文字去掉
((MyAdapter.NormalItem) viewHolder).iv.setVisibility(View.VISIBLE); //显示眼睛
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) ((MyAdapter.NormalItem) viewHolder).iv.getLayoutParams();
params.width = (int) (fixWidth + diff);
params.height = (int) (fixWidth + diff);
((MyAdapter.NormalItem) viewHolder).iv.setLayoutParams(params);
}
}else {
//拖拽状态下不做改变,需要调用父类的方法
super.onChildDraw(c,recyclerView,viewHolder,dX,dY,actionState,isCurrentlyActive);
}
} /**
* 获取删除方块的宽度
*/
public int getSlideLimitation(RecyclerView.ViewHolder viewHolder){
ViewGroup viewGroup = (ViewGroup) viewHolder.itemView;
return viewGroup.getChildAt(1).getLayoutParams().width;
}
}

好了,到目前为止,自定义效果介绍完毕,读者可以根据需求来实现多样化的效果。最后,感谢你的阅读,如有错误欢迎指出~

RecyclerView进阶:使用ItemTouchHelper实现拖拽和侧滑删除的更多相关文章

  1. RecyclerViewItemTouchHelperDemo【使用ItemTouchHelper进行拖拽排序功能】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 记录使用ItemTouchHelper对Recyclerview进行拖拽排序功能的实现. 效果图 代码分析 ItemTouchHel ...

  2. 高级UI-RecyclerView拖拽和侧滑

    RecyclerView强大的地方在于高度的可定制,正式由于此优点,现在的项目大多使用RecyclerView,这里我们仿照QQ的功能,实现RecyclerView的拖拽和侧滑功能 功能说明 上下拖拽 ...

  3. RecyclerView 与 ItemTouchHelper 实现拖拽效果

    截图 需求 App 开发新的需求,要求 RecyclerView 实现的九宫格样式可以拖拽,松手以后变更位置,类似于手机桌面拖动 app 变更位置. 分析 经过搜索,发现 support 中带有一个类 ...

  4. Javascript 事件对象进阶(二)拖拽的应用 - 登录框的拖拽

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  5. Javascript 事件对象进阶(一)拖拽的原理

    拖拽原理 鼠标和Div的相对距离不变 三大事件 把拖拽加到document上 拖拽简单点来说就是不停的更改物体到页面左边&顶部的距离! 那么如何计算出物体到页面左端的距离呢? 当鼠标按下的时候 ...

  6. Android学习之ItemTouchHelper实现RecylerView的拖拽以及滑动删除功能

    今天在群里见大神们提到控件的拖动以及滑动删除的效果实现,就在网上找了资料ItemTouchHelper学习,并实现其功能.不胜窃喜之至,忍不住跟大家分享一下,如今就对学习过程做下简介.帮助大家实现这样 ...

  7. 国产网络损伤仪 SandStorm -- 只需要拖拽就能删除链路规则

    国产网络损伤仪SandStorm可以模拟出带宽限制.时延.时延抖动.丢包.乱序.重复报文.误码.拥塞等网络状况,在实验室条件下准确可靠地测试出网络应用在真实网络环境中的性能,以帮助应用程序在上线部署前 ...

  8. Android 仿微信朋友圈发表图片拖拽和删除功能

    朋友圈实现原理 我们使用 Android Device Monitor 来分析朋友圈发布图片的界面实现原理.如果需要分析其他应用的界面实现也是采用这种方法哦. 打开 Android Device Mo ...

  9. RecyclerView拖拽排序和滑动删除实现

    效果图 如何实现 那么是如何实现的呢?主要就要使用到ItemTouchHelper ,ItemTouchHelper 一个帮助开发人员处理拖拽和滑动删除的实现类,它能够让你非常容易实现侧滑删除.拖拽的 ...

随机推荐

  1. Flutter 37: 图解 Flutter 基本动画 (一)

    小菜一直对动画不太熟悉,最近学习了一些关于动画的皮毛知识,网上资料很多,小菜按自己的理解整理一下. Animation Animation 可以生成动画过程中的值,生成的值并非单一的 double 也 ...

  2. 1.JavaWeb 知识点概览

    1.tomcat服务器的安装和配置.http协议 1.1 虚拟目录的 /*映射*/(配置Context元素)(server.xml catalina\localhost\) http://blog.c ...

  3. 记录lucene.net的使用过程

    之前公司要做一个信息展示的网站,领导说要用lucene.net来实现全文检索,类似百度的搜索功能,但是本人技术有限,只是基本实现搜索和高亮功能,特此记录: 先看下页面效果,首先我搜索“为什么APP消息 ...

  4. 分布式任务队列 Celery —— Task对象

    转载至 JmilkFan_范桂飓:http://blog.csdn.net/jmilk  目录 目录 前文列表 前言 Task 的实例化 任务的名字 任务的绑定 任务的重试 任务的请求上下文 任务的继 ...

  5. 用window.showModalDialog()打开的页面Request.UrlReferrer为null

    今天在解决一个问题,怎么也找不到解决方案.我的一个窗体是IE通过window.showModalDialog()打开的,但为了防止用户手工输的地址,所以我需要判断是通过别的页面调整获得,用Reques ...

  6. linux 下安装 jdk1.7

    1.官网 下载jdk7版本 地址: http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads-jav ...

  7. python学习笔记:安装boost python库以及使用boost.python库封装

    学习是一个累积的过程.在这个过程中,我们不仅要学习新的知识,还需要将以前学到的知识进行回顾总结. 前面讲述了Python使用ctypes直接调用动态库和使用Python的C语言API封装C函数, C+ ...

  8. web开发:javascript基础

    一.js引入 二.变量的定义 三.三种弹出框 四.调试方式 五.数据类型 六.数据类型转换 七.运算符 八.分支机构 九.循环结构 十.异常处理 十一.函数 一.js引入 - ES: ECMAScri ...

  9. linux内核无锁缓冲队列kfifo原理

    Linux kernel里面从来就不缺少简洁,优雅和高效的代码 比如,通过限定写入的数据不能溢出和内存屏障实现在单线程写单线程读的情况下不使用锁.因为锁是使用在共享资源可能存在冲突的情况下.还用设置b ...

  10. JVM命令jinfo

          jinfo也是jvm中参与的一个命令,可以查看运行中jvm的全部参数,还可以设置部分参数.   格式      jinfo [ option ] pid      jinfo [ opti ...