RecyclerView进阶:使用ItemTouchHelper实现拖拽和侧滑删除效果

前言

现在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;
  }
}

好了,到目前为止,自定义效果介绍完毕,读者可以根据需求来实现多样化的效果。

(0)

相关推荐

  • android RecyclerView侧滑菜单,滑动删除,长按拖拽,下拉刷新上拉加载

    本文介绍的库中的侧滑效果借鉴自SwipeMenu,并对SipwMenu的源码做了修改与Bug修复,然后才开发出的SwipeRecyclerView. 需要说明的是,本库没有对RecyclerView做大的修改,只是ItemView的封装.看起来是对RecyclerView的修改,其实仅仅是为RecyclerView添加了使用的方法API而已. 本库已经更新了三个版本了,会一直维护下去,根据小伙伴的要求,以后也会添加一些其它功能. SwipeRecyclerView将完美解决这些问题: 以下功能全

  • Android应用开发中RecyclerView组件使用入门教程

    RecyclerView是一种列表容器, 发布很久了, 才想起来写点什么. RecyclerView相比于ListView, 在回收重用时更具有灵活性, 也就是低耦合, 并且提供了扩展. 加载多个视图时, 应该多用RecyclerView代替ListView. 那么我们来看看这东西应该怎么用? 比如生成一个瀑布流的视图. 首先我们从一个HelloWorld写起, 看看如何构建一个RecyclerView. 1. 依赖库 Gradle配置, 添加Recycler库 compile 'com.and

  • Android利用RecyclerView实现全选、置顶和拖拽功能示例

    前言 今天给大家分享是如何在RecyclerView实现全选,ItemTouchHelper实现侧滑删除,拖拽功能.比较基础.关于RecyclerView的强大,就不多说了.在Android L SDK发布的新API中最有意思的就是RecyclerView 和 CardView了, 按照官方的说法, RecyclerView 一个ListView 的一个更高级更灵活的一个版本, 可以自定义的东西太多了. 效果: RecyclerView实现全选,ItemTouchHelper实现侧滑删除,拖拽功

  • Android中RecyclerView实现横向滑动代码

    RecyclerView 是Android L版本中新添加的一个用来取代ListView的SDK,它的灵活性与可替代性比listview更好.本文给大家介绍Android中RecyclerView实现横向滑动代码,一起看看吧. android.support.v7.widget.RecyclerView 功能:RecyclerView横向滑动 控件:<android.support.v7.widget.RecyclerView /> Java类:RecyclerView.GalleryAdap

  • Android recyclerview实现拖拽排序和侧滑删除

    Recyclerview现在基本已经替代Listview了,RecyclerView也越来越好用了  当我们有实现条目的拖拽排序和侧滑删除时  可以直接时候Recyclerview提供的API就可以直接实现了 先贴上主要代码 private void initveiw() { ArrayList<String> items = new ArrayList<>(Arrays.asList("itme1", "item2", "itme

  • android的RecyclerView实现拖拽排序和侧滑删除示例

    在平时开发应用的时候,经常会遇到列表排序.滑动删除的需求.如果列表效果采用的是 ListView 的话,需要经过自定义 View 才能实现效果:但是如果采用的是 RecyclerView 的话,系统 API 就已经为我们提供了相应的功能. 接下来,我们就来看一下怎么用系统 API 来实现排序和删除的效果. 创建 ItemTouchHelper 创建一个 ItemTouchHelper 对象,然后其调用 attachToRecyclerView 方法: RecyclerView recyclerV

  • Android实现评论栏随Recyclerview滑动左右移动

    最近在玩一个叫"约会吧"的应用,也是在看直播app,默认下载安装的,安装点进去看这个应用做的不错,就留下来了.然后看他们动态详情页底部有一个效果:Recyclerview滑动到的评论列表的时候,底部点赞那栏会往左滑动,出现一个输入评论的栏:然后下拉到底部的时候输入评论栏会往右滑动,出现点赞栏.详细细节直接来看效果图吧. 其实这种效果现在在应用中还是很常见的,有上拉,toolbar.底部view隐藏,下拉显示,或者像现在约会吧这样左右滑动的效果.而且网上资料现在也有很多,有通过Objec

  • RecyclerView进阶:使用ItemTouchHelper实现拖拽和侧滑删除效果

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

  • android ItemTouchHelper实现可拖拽和侧滑的列表的示例代码

    前言 话不多说,直接上图: 笔者使用 RecyclerView 的 ItemTouchHelper 来实现这个效果,过程非常简单.为了学习,这里顺便实现了一下侧滑删除. 实现功能: 按住 item 左侧的按钮可以上下拖动 item 向右侧滑删除 item item 拖动或侧滑时有阴影效果 实现基本功能 循序渐进学习,这里我们先实现基本的功能: 长按 item 实现上下拖拽 向右侧滑删除 布局文件 很简单,不多说,直接上代码: activity_main.xml <?xml version="

  • Android使用CardView作为RecyclerView的Item并实现拖拽和左滑删除

    引言 CardView是Android 5.0系统之后引入的众多控件之一,实现之后的效果也是比较酷的,它经常被用在RecyclerView和ListView中的Item中.今天我们就来了解一下CardView的属性,然后使用CardView和RecyclerView结合实现一个可以拖拽Item的布局. CardView的属性 CardView继承自FrameLayout,所以子控件的布局规则和FrameLayout的一样,是按照层次堆叠的 下面是CardView的一些常用属性: CardView

  • Android实现View拖拽跟随手指移动效果

    今天想实现这个功能,但是网上搜索代码,都是利用setPadding,setMargin 等方法去实现的,这在Android 4.0 以前是没问题的,但是,android 4.0 后系统已经提供了更简单的方法给我们用了,就是setTranslationX() 和setTranslationY() .这两个是View的属性方法.现在我就用这两个方法实现一个View可以跟着手指移动拖拽的效果.代码非常非常简单: public class DragView extends TextView { floa

  • jQuery实现可拖拽的许愿墙效果【附demo源码下载】

    本文实例讲述了jQuery实现可拖拽的许愿墙效果.分享给大家供大家参考,具体如下: 运行效果图如下: 这里简单介绍下功能点: ① 随机显示背景图或背景色 ② 出现的位置随机 ③ 可以通过拖拽改变位置 好了,附上代码: <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"/> <title>许愿墙</title> <lin

  • vue + any-touch实现一个iscroll 实现拖拽和滑动动画效果

    https://github.com/383514580/any-touch 先看demo demo 说点湿的 iscroll其实代码量挺大的(近2100行, 还有另一个类似的库 betterScroll 他的代码量和iscroll差不多, 因为原理都是一样的), 阅读他们的代码 发现里面很多逻辑 其实都是在做手势判断 , 比如拖拽(pan), 和划(swipe), 还有部分元素(表单元素等)需要单独判断点击(tap), 这部分代码接近1/3, 所以我决定用自己开发的手势库(any-touch)

  • 原生js实现拖拽移动与缩放效果

    本文实例为大家分享了js实现拖拽移动与缩放效果的具体代码,供大家参考,具体内容如下 效果图如下-实现了简单的拖拽和缩放功能 第一步-简单的拖拽功能 // 创建一个MoveClass构造函数 function MoveClass(id, options = {}) { // 绑定ele属性 this.ele = document.querySelector(id); this.move(); } // 给MoveClass原型上绑定move方法 MoveClass.prototype.move =

  • 超酷的鼠标拖拽翻页(分页)效果实现javascript代码

    拖动分页 body{ border:0px; margin:0px; overflow:hidden; background-color:transparent; font-family:宋体; } .page{ position:absolute; width:700px; border:1px solid #999; background-color:#000; left:425px; margin-left:-350px; cursor:default; z-index:0; } ul{

  • jQuery实现鼠标拖拽登录框移动效果

    本文实例为大家分享了jQuery鼠标拖拽登录框移动的具体代码,供大家参考,具体内容如下 1.jQuery代码 <script src="js/jquery-3.5.1.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> $(function () { // 点击登录跳转 $(

随机推荐