RecyclerView 知识梳理(5) - ItemTouchHelper

一、概述

ItemTouchHelperRecyclerView的整个体系中,负责监听Item的手势操作,我们通过给它设置一个继承于ItemTouchHelper.Callback的子类,在其中处理ItemUI变化,就可以完成侧滑删除、拖动排序等操作,下面,我们分以下几部介绍:

  • API解析
  • 实战
    • 采用默认动画
    • 自定义侧滑删除动画

二、API分析

对于Item的手势操作分为两种:侧滑和拖动,如果需要支持这两种,那么需要给ItemTouchHelper传入一个ItemTouchHelper.Callback的子类,并把ItemTouchHelperRecyclerView关联起来,下面,我们先来介绍一下ItemTouchHelper.Callback个回调方法的含义:

控制相关

  • public boolean isLongPressDragEnabled()
    是否可以通过长按来触发拖动操作,默认返回true,如果返回false,那么可以通过startDrag(ViewHolder)方法来触发某个特定Item的拖动的机制。
  • public boolean isItemViewSwipeEnabled()
    是否可以对每个Item进行侧滑。
  • public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
    返回对于某个ViewHolder可以移动的方向,可选的值有UP/DOWN/LEFT/RIGHT/START/END。对于纵向排列的线性布局而言,如果要支持上下拖动排序,那么就要标志位中就要包含UP&DOWN,而如果需要支持左滑删除,那么标志位中就要包含LEFT

结果相关

  • public abstract boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target)
    当某个被拖动的Item被从旧位置拖动到了新位置后回调,如果返回true,那么ItemTouchHelper就认为viewHolder已经被移动到了targetAdapter中的位置。
  • public abstract void onSwiped(ViewHolder viewHolder, int direction)
    当某个Item被滑动到消失时回调,direction表示滑动的方向。

状态相关

  • public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState)
    Item的状态发生改变时,回调该方法,actionState的取值有ACTION_STATE_IDLE/ACTION_STATE_SWIPE/ACTION_STATE_DRAG
  • public void clearView(RecyclerView recyclerView, ViewHolder viewHolder)
    标志着用户对于某个Item的操作并且Item的动画结束,此时我们应该恢复它的状态,以保证它被重新使用的时候能正确地展现。
  • public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)
    • Canvas:绘制RecyclerViewCanvas
    • dx, dy:偏移。
    • actionState:拖拽还是侧滑,对应ACTION_STATE_DRAGACTION_STATE_SWIPE
    • isCurrentlyActivetrue表示这个Item正在被用户所控制,false则表示它仅仅是在回到原本状态的动画过程当中。
  • public void onChildDrawOver(Canvas c, RecyclerView recyclerView, ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)
    和上面类似,只不过它是绘制在Item之上。

三、实战

3.1 使用系统默认效果

如果我们希望使用系统默认的效果,那么只需要做以下几步:

  • 继承于ItemTouchHelper.Callback编写自己的回调类,并在拖动和侧滑操作完成之后更新数据:

    public class SimpleItemTouchHelper extends ItemTouchHelper.Callback {
    
      private ItemTouchAdapter mAdapter;
    
      public SimpleItemTouchHelper(ItemTouchAdapter adapter) {
          mAdapter = adapter;
      }
    
      @Override
      public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
          Log.d("SimpleItemTouchHelper", "onSwiped, onMove, source=" + viewHolder.getAdapterPosition() + ",target=" + target.getAdapterPosition());
          mAdapter.onItemDragged(viewHolder.getAdapterPosition(), target.getAdapterPosition());
          return true;
      }
    
      @Override
      public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
          Log.d("SimpleItemTouchHelper", "onSwiped, direction=" + direction);
          mAdapter.onItemSwiped(viewHolder.getAdapterPosition());
      }
    
      @Override
      public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
          return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
      }
    }
  • 编写数据操作的代码:
    public class NormalAdapter extends RecyclerView.Adapter<NormalAdapter.NormalViewHolder> implements ItemTouchAdapter {
    
      //......
    
      @Override
      public void onItemDragged(int from, int to) {
          Collections.swap(mTitles, from, to);
          notifyItemMoved(from, to);
      }
    
      @Override
      public void onItemSwiped(int position) {
          mTitles.remove(position);
          notifyItemRemoved(position);
      }
    }
  • ItemTouchHelper.CallbackRecyclerView关联起来,看注释中的1,2,3步:
      private void init() {
          List<String> titles = new ArrayList<>();
          for (int i = 0; i < 20; i++) {
              titles.add(String.valueOf(i));
          }
          LinearLayoutManager layoutManager = new LinearLayoutManager(this);
          RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_content);
          recyclerView.setLayoutManager(layoutManager);
          NormalAdapter adapter = new NormalAdapter(titles);
          //1.自定义的ItemTouchHeloer.Callback
          SimpleItemTouchHelper simpleItemTouchHelper = new
    SimpleItemTouchHelper(adapter);
          //2.利用这个Callback构造ItemTouchHelper
          ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchHelper);
          //3.把ItemTouchHelper和RecyclerView关联起来.
          itemTouchHelper.attachToRecyclerView(recyclerView);
          recyclerView.setAdapter(adapter);
      }

    下面就是最终的效果:

3.2 自定义侧滑删除动画

当我们需要自定侧滑删除动画时,那么需要重写onChildDraw或者onChildDrawOver方法,在其中监听滑动距离的变化,并根据它来实时改变viewHolder中的UI,首先看效果:

  • 首先,我们需要重写Item的布局,它包含两层,顶层是普通状态的标题文案,而底层则是蓝色底的删除提示:

    <FrameLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="66dp">
      <!-- 删除提示 -->
      <LinearLayout
          android:id="@+id/ll_delete"
          android:orientation="vertical"
          android:gravity="center"
          android:layout_gravity="end"
          android:background="@android:color/holo_blue_dark"
          android:paddingLeft="10dp"
          android:paddingRight="10dp"
          android:layout_width="wrap_content"
          android:layout_height="match_parent">
          <ImageView
              android:src="@android:drawable/ic_input_delete"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"/>
          <TextView
              android:text="delete"
              android:textColor="@android:color/white"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"/>
      </LinearLayout>
      <!-- 普通文案 -->
      <TextView
          android:id="@+id/tv_title"
          android:gravity="center"
          android:background="@android:color/white"
          android:layout_width="match_parent"
          android:layout_height="match_parent"/>
    </FrameLayout>

    接着,我们需要重写ItemTouchHelper.Callback

    public class AdvancedItemTouchHelper extends ItemTouchHelper.Callback {
    
      private ItemTouchAdapter mAdapter;
    
      public AdvancedItemTouchHelper(ItemTouchAdapter itemTouchAdapter) {
          mAdapter = itemTouchAdapter;
      }
    
      @Override
      public boolean isLongPressDragEnabled() {
          return false;
      }
    
      @Override
      public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
          return makeMovementFlags(0, ItemTouchHelper.LEFT);
      }
    
      @Override
      public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
          mAdapter.onItemSwiped(viewHolder.getAdapterPosition());
      }
    
      @Override
      public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
          return false;
      }
    
      @Override
      public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
          super.clearView(recyclerView, viewHolder);
          ((NormalAdapter.NormalViewHolder) viewHolder).mTv.setTranslationX(0);
      }
    
      @Override
      public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
          NormalAdapter.NormalViewHolder mViewHolder = (NormalAdapter.NormalViewHolder) viewHolder;
          int deleteWidth = mViewHolder.mDeleteLayout.getWidth();
          float fraction = deleteWidth / (float) mViewHolder.itemView.getWidth();
          mViewHolder.mTv.setTranslationX(dX * fraction);
      }
    }

这里面有几点需要注意:

  • 为了让Item支持左滑删除,我们需要在getMovementFlags中返回ItemTouchHelper.LEFT标志位。
  • onChildDraw当中,通过传入的dX动态改变了普通文案的translationX,使得底层的删除提示能够漏出。
  • 在侧滑操作完成之后,通过Adapter来删除数据。
  • clearView中,需要把mTv重置为初始的状态。

最后,我们按照前面的方法,把它和RecyclerView关联起来:

    private void init() {
        List<String> titles = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            titles.add("Item " + String.valueOf(i));
        }
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_content);
        recyclerView.setLayoutManager(layoutManager);
        NormalAdapter adapter = new NormalAdapter(titles);
        AdvancedItemTouchHelper advancedItemTouchHelper = new AdvancedItemTouchHelper(adapter);
        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(advancedItemTouchHelper);
        itemTouchHelper.attachToRecyclerView(recyclerView);
        recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
        recyclerView.setAdapter(adapter);
    }

四、小结

自定义RecyclerView的手势动画,关键是要理解ItemTouchHelper.Callback中各回调函数的含义,再通过回调函数中传入的数值来动态改变viewHolder中保存的itemView以及其子View的展现形式,就可以做出各种绚丽的效果。

时间: 2024-09-29 16:00:08

RecyclerView 知识梳理(5) - ItemTouchHelper的相关文章

RecyclerView 知识梳理(4) - ItemDecoration

一.概述 通过ItemDecoration,可以给RecyclerView或者RecyclerView中的每个Item添加额外的装饰效果,最常用的就是用来为Item之间添加分割线,今天,我们就来一起学习有关的知识: API DividerItemDecoration解析 自定义ItemDecoration 二.API介绍 当我们实现自己的ItemDecoration时,需要继承于ItemDecoration,并根据需要实现以下三个方法: 2.1 public void onDraw(Canvas

RecyclerView 知识梳理(2) - Adapter

一.概述 当我们使用RecyclerView时,第一件事就是要继承于RecyclerView.Adapter,实现其中的抽象方法,来处理数据的展示逻辑,今天,我们就来介绍一下Adapter中的相关方法. 二.基础用法 我们从一个简单的线性列表布局开始,介绍RecyclerView.Adapter的基础用法.首先,需要导入远程依赖包: compile'com.android.support:recyclerview-v7:25.3.1' 接着,继承于RecyclerView.Adapter来实现自

指针知识梳理7- 函数指针

一.函数的地址 前面讲 程序运行起来以后,在内存中有代码区,程序运行每一条指令,是从内存中读出来这条指令,然后再运行. 所谓函数的地址是指函数的入口地址,这个函数的从这个地址開始进入运行,也就是从这个地址处取指令运行. 那么在代码层面,函数的地址用 函数指针变量 来存储. 二.基本使用 1.函数指针定义 函数指针的定义,在语法看起来略微有点怪,仅仅须要记住形式 返回值 (*指针变量名)(形參类型): 比方,下面4个函数 void func1(void) { } int func2(void) {

[SQL] SQL 基础知识梳理(一)- 数据库与 SQL

SQL 基础知识梳理(一)- 数据库与 SQL [博主]反骨仔 [原文地址]http://www.cnblogs.com/liqingwen/p/5902856.html 序 目录 What's 数据库 数据库结构 SQL 概要 创建表 删除和更新表 1-1 What's 数据库 1.数据库(Database,DB):将大量数据保存起来,通过计算机加工而成的可以进行高效访问的数据集合.如:大型-银行存储的信息,小型-电话簿. 2.数据库管理系统(Batabase Management Syste

JavaScript基础知识梳理--数组

JavaScript基础知识梳理--数组 1.创建方法 空数组:var obj=new Array(); 指定长度数组: var obj=new Array( size ); 指定元素数组 :  var obj=new Array( 元素1,元素2,....): 单位数组:var obj=new Array[ 元素1,元素2,元素3,...,元素N]; 多维数组:var a=new Array( [数组1],[数组2],[数组3],...,[数组N] ); 2.基本操作 存取数组元素: 单维数组

Java基础知识梳理《一》

一.Java数据类型(简单称之为“四类八种”) java 基本的数据类型长度都是固定的,好处是在实现跨平台时就统一了. 1.整型 byte short int long (分别是1,2,4,8个字节) 类型 存储需求 位数 取值范围 byte 1字节 8位 -128~127 short 2字节 16位 -2^15 ~2^15-1 int 4字节 32位 -2^31~2^31-1 long 8字节 64位 -2^63~2^63-1 当超出int表示范围时,应该使用long型,添加后缀一大写的L 注

struts2 知识梳理

一:struts.xml配置详解: 1.<include> 表示引入其他配置文件 2.<constant> 定义常量 3.<package>:  属性 是否必需 描述name 是 包名,作为其它包应用本包的标记extends 否 设置本包继承其它包namespace 否 设置包的命名空间,会改变url,abstact 否 设置为抽象包 4<action>和<result> <action>有name,class,method,conv

指针知识梳理6-const与指针

const 定义的变量为只读变量,在语法层面上通过这个变量去修改内存是不允许的. 但是对于以下代码,就有很多人绕了: const int  *p1;  //p1能变,*p1不能变 int const  *p2;  //p2能变,*p2不能变 int *const  p3;  //p3不能变,*p2能变 我们通过代码来验证说明这三种写法: </pre><p></p><pre> #include <stdio.h> int main() { int

[SQL] SQL 基础知识梳理(四) - 数据更新

SQL 基础知识梳理(四) - 数据更新 [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/5929786.html 目录 一.插入数据 1.INSERT 语句的基本语法 --语法: --INSERT INTO <表名>(列1, 列2, ...) VALUES (值1, 值2, ...) INSERT INTO dbo.Shohin ( shohin_id , shohin_mei , shohin_bunrui , hanbai_tanka , s