热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

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

前言 现在RecyclerView的应用越来越广泛了,不同的应用场景需要其作出不同的改变。有时候我们可能需要实现侧滑删除的功能,比如知乎首

前言

现在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 implements ItemTouchHelperAdapter {
  //数据
  private List 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水平并排排列,以下是布局文件:

<&#63;xml version="1.0" encoding="utf-8"&#63;>


  
    
      

    
  

  


布局文件修改后,我们尝试来滑动一下,发现后面的删除方块并不会出现,这是因为默认的滑动方式是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;
  }
}

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


推荐阅读
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文总结了2018年的关键成就,包括职业变动、购车、考取驾照等重要事件,并分享了读书、工作、家庭和朋友方面的感悟。同时,展望2019年,制定了健康、软实力提升和技术学习的具体目标。 ... [详细]
  • 在计算机技术的学习道路上,51CTO学院以其专业性和专注度给我留下了深刻印象。从2012年接触计算机到2014年开始系统学习网络技术和安全领域,51CTO学院始终是我信赖的学习平台。 ... [详细]
  • CSS 布局:液态三栏混合宽度布局
    本文介绍了如何使用 CSS 实现液态的三栏布局,其中各栏具有不同的宽度设置。通过调整容器和内容区域的属性,可以实现灵活且响应式的网页设计。 ... [详细]
  • Linux 系统启动故障排除指南:MBR 和 GRUB 问题
    本文详细介绍了 Linux 系统启动过程中常见的 MBR 扇区和 GRUB 引导程序故障及其解决方案,涵盖从备份、模拟故障到恢复的具体步骤。 ... [详细]
  • 本文介绍了如何使用jQuery根据元素的类型(如复选框)和标签名(如段落)来获取DOM对象。这有助于更高效地操作网页中的特定元素。 ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 本文将详细介绍如何使用剪映应用中的镜像功能,帮助用户轻松实现视频的镜像效果。通过简单的步骤,您可以快速掌握这一实用技巧。 ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 本文介绍如何在 Xcode 中使用快捷键和菜单命令对多行代码进行缩进,包括右缩进和左缩进的具体操作方法。 ... [详细]
  • 本文介绍了一款用于自动化部署 Linux 服务的 Bash 脚本。该脚本不仅涵盖了基本的文件复制和目录创建,还处理了系统服务的配置和启动,确保在多种 Linux 发行版上都能顺利运行。 ... [详细]
  • 在Linux系统中配置并启动ActiveMQ
    本文详细介绍了如何在Linux环境中安装和配置ActiveMQ,包括端口开放及防火墙设置。通过本文,您可以掌握完整的ActiveMQ部署流程,确保其在网络环境中正常运行。 ... [详细]
  • Android 渐变圆环加载控件实现
    本文介绍了如何在 Android 中创建一个自定义的渐变圆环加载控件,该控件已在多个知名应用中使用。我们将详细探讨其工作原理和实现方法。 ... [详细]
  • 本文总结了在使用Ionic 5进行Android平台APK打包时遇到的问题,特别是针对QRScanner插件的改造。通过详细分析和提供具体的解决方法,帮助开发者顺利打包并优化应用性能。 ... [详细]
  • 理解存储器的层次结构有助于程序员优化程序性能,通过合理安排数据在不同层级的存储位置,提升CPU的数据访问速度。本文详细探讨了静态随机访问存储器(SRAM)和动态随机访问存储器(DRAM)的工作原理及其应用场景,并介绍了存储器模块中的数据存取过程及局部性原理。 ... [详细]
author-avatar
BAEKHYUN-MANDY
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有