FSA全栈行动 FSA全栈行动
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr

公众号:FSA全栈行动

记录学习过程中的知识
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr
  • AndroidUI

    • RecyclerView遇到notifyDataSetChanged无效时的解决方案
    • Material Design 兼容性控件学习
    • RecyclerView之ItemDecoration
    • RecyclerView之使用ItemTouchHelper实现交互动画
      • 一、简述
      • 二、初识ItemTouchHelper
        • 1、创建ItemTouchHelper
        • 2、自定义ItemTouchHelper.Callback
      • 三、深入ItemTouchHelper
        • 1、上下拖动时与其他item进行位置交换
        • 2、左右滑出屏幕时其他item补上
        • 3、交互时背景变化
        • 4、左右滑动时item渐变
        • 最后附上Demo链接
    • MaterialDesign之SearchView全面解锁
    • MaterialDesign之学一波Palette
    • MaterialDesign之AppBarLayout与CollapsingToolbarLayout的学习
    • MaterialDesign之对TabLayout的探索
    • 多RecyclerView同步滚动
    • Libgdx - 使用pixmap绘制透明圆角矩形
    • 使用PorterDuff解决clipPath无法抗锯齿问题
    • 解决ImageView图片挤压问题
  • Android第三方SDK

  • Android混淆

  • Android仓库

  • Android新闻

  • Android系统开发

  • Android源码

  • Android注解AOP

  • Android脚本

  • AndroidTv开发

  • AndroidNDK

  • Android音视频

  • Android热修复

  • Android性能优化

  • Android云游戏

  • Android插件化

  • iOSUI

  • iOS工具

  • iOS底层原理与应用

  • iOS组件化

  • iOS音视频

  • iOS疑难杂症

  • iOS之Swift

  • iOS之RxSwift

  • iOS开源项目

  • iOS逆向

  • Flutter开发

  • 移动端
  • AndroidUI
GitLqr
2017-05-05
目录

RecyclerView之使用ItemTouchHelper实现交互动画

欢迎关注微信公众号:[FSA全栈行动 👋]

# 一、简述

RecyclerView默认就有item动画,例如在增加或删除item时,都会有一个条目间位移的动画,但本文要说的不是这个!!!本文的主角是v7包中的ItemTouchHelper,它跟RecyclerView结合后将会带来神奇的交互效果。示例如下:

效果还是比较酷炫的吧,上图中有四步操作:

  1. 长按item后拖动,与其他item交换位置
  2. 按住item右面的图标后拖动,与其他item交换位置
  3. 左滑item变透明并缩小,超出屏幕后,其他item补上
  4. 右滑item变透明并缩小,超出屏幕后,其他item补上

下面将一一实现出这些效果

# 二、初识ItemTouchHelper

# 1、创建ItemTouchHelper

// 创建ItemTouchHelper,并跟RecyclerView绑定
mItemTouchHelper = new ItemTouchHelper(mCallback);
mItemTouchHelper.attachToRecyclerView(mRv);

上面就是ItemTouchHelper在本例中出场的三行代码中的两行代码,但这并不能完成上面的效果,ItemTouchHelper只是一个中间人,它将ItemTouchHelper.Callback和RecyclerView连接起来,具体效果还需要由Callback实现。

# 2、自定义ItemTouchHelper.Callback

# 1)创建一个自己的Callback

继承ItemTouchHelper.Callback后必须实现如下三个方法。

public class MyItemTouchHelperCallback extends ItemTouchHelper.Callback {

    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        return 0;
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder targetViewHolder) {
        return false;
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {

    }
}

# 2)重写getMovementFlags()

getMovementFlags()是用来判断RecyclerView上的哪些方向操作交由ItemTouchHelper.Callback控制,详细介绍如下:

/**
 * 获取动作标识
 * 动作标识分:dragFlags和swipeFlags
 * dragFlags:列表滚动方向的动作标识(如竖直列表就是上和下,水平列表就是左和右)
 * wipeFlags:与列表滚动方向垂直的动作标识(如竖直列表就是左和右,水平列表就是上和下)
 */
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
	// 如果你不想上下拖动,可以将 dragFlags = 0
    int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;

	// 如果你不想左右滑动,可以将 swipeFlags = 0
    int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;

	//最终的动作标识(flags)必须要用makeMovementFlags()方法生成
    int flags = makeMovementFlags(dragFlags, swipeFlags);
    return flags;
}

上面我让item的上下左右都交由ItemTouchHelper.Callback控制,看下效果:

可以看到左右有效,但上下无效。仔细想想也是,本来就是竖直滚动列表,如果上下都直接交给ItemTouchHelper.Callback控制了,那RecyclerView的列表滚动功能该怎么办?所以,要触发上下拖动的交互效果,肯定有其他开启的方式。

# 3)开启长按item拖动效果

直接重写ItemTouchHelper.Callback的isLongPressDragEnabled()。

/**
 * 是否开启item长按拖拽功能
 */
@Override
public boolean isLongPressDragEnabled() {
    return true;
}

默认返回是false,重写返回true。现在看下效果如何:

长按后可以拖动了,但是这样不太方便,接下来实现按住item右图标进行拖动的效果。

# 4)开启按住图标拖动效果

ItemTouchHelper的startDrag(viewHolder)方法可以手动开启拖动效果,上面的ItemTouchHelper实例创建在Activity中,而图标实例在Adapter中,为了降低耦合,这里先写一个接口:

public interface ItemDragListener {
    void onStartDrags(RecyclerView.ViewHolder viewHolder);
}

接口由Activity实现,在Adapter创建时传入

public class ItemTouchHelperActivity extends AppCompatActivity implements ItemDragListener {

	private ItemTouchHelper mItemTouchHelper;
	...

	private void setRecyclerView() {
        mAdapter = new ItemTouchHelperAdapter(mData, this);
		...
    }

    @Override
    public void onStartDrags(RecyclerView.ViewHolder viewHolder) {
        mItemTouchHelper.startDrag(viewHolder);
    }
}

接口由Adapter的图标触摸时调用

public class ItemTouchHelperAdapter extends RecyclerView.Adapter<ItemTouchHelperAdapter.ItemTouchHelperViewHolder>  {

    private List<String> mData;
    private ItemDragListener mItemDragListener;

    public ItemTouchHelperAdapter(List<String> data, ItemDragListener itemDragListener) {
        mData = data;
        mItemDragListener = itemDragListener;
    }

    ...

    @Override
    public void onBindViewHolder(final ItemTouchHelperViewHolder viewHolder, int position) {
        ...
        viewHolder.mIvDrag.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                mItemDragListener.onStartDrags(viewHolder);
                return false;
            }
        });
    }
}

这样在让图标在触摸时,间接的调用了mItemTouchHelper.startDrag(viewHolder),看下效果:

# 三、深入ItemTouchHelper

上面做到了item的上下拖动和左右滑动效果,但只是当前item的动画效果罢了,下面继续完成与其他item互动的效果吧。

# 1、上下拖动时与其他item进行位置交换

# 1)原理

其实ItemTouchHelper.Callback本身不具备将两个item互换位置的功能,但RecyclerView可以,我们可以在item拖动的时候把当前item与另一个item的数据位置交换,再调用RecyclerView的notifyItemMoved()方法刷新布局,同时,因为RecyclerView自带item动画,就可以完成上面的交互效果了。

# 2)实现

item拖动要在ItemTouchHelper.Callback中监听,而数据交换处理要在Adapter中进行,为了降低耦合,这里先写一个接口:

public interface ItemMoveListener {
    boolean onItemMove(int fromPosition, int toPosition);
}

接口由Adapter实现

public class ItemTouchHelperAdapter extends RecyclerView.Adapter<ItemTouchHelperAdapter.ItemTouchHelperViewHolder> implements ItemMoveListener{

    ...

	@Override
    public boolean onItemMove(int fromPosition, int toPosition) {
        //1、交换数据
        Collections.swap(mData, fromPosition, toPosition);
        //2、刷新
        notifyItemMoved(fromPosition, toPosition);
        return true;
    }
}

接口在创建ItemTouchHelper.Callback时传入,在onMove()中调用

public class MyItemTouchHelperCallback extends ItemTouchHelper.Callback {

    ItemMoveListener mItemMoveListener;

    public MyItemTouchHelperCallback(ItemMoveListener itemMoveListener) {
        mItemMoveListener = itemMoveListener;
    }

	...		

    /**
     * 当item拖拽移动时触发
     *
     * @param recyclerView
     * @param viewHolder       当前被拖拽的item的viewHolder
     * @param targetViewHolder 当前被拖拽的item下方的另一个item的viewHolder
     * @return
     */
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder targetViewHolder) {
        return mItemMoveListener.onItemMove(viewHolder.getAdapterPosition(), targetViewHolder.getAdapterPosition());
    }
}

# 3)效果:

# 2、左右滑出屏幕时其他item补上

# 1)原理

相同的,只要在item滑出屏幕时,将对应的数据删掉,再调用RecyclerView的notifyItemRemoved()方法刷新布局即可。

# 2)实现

item滑动要在ItemTouchHelper.Callback中监听,而数据删除处理要在Adapter中进行,所以只需要加上面的接口中增加一个方法:

public interface ItemMoveListener {
    ...
    boolean onItemRemove(int position);
}

接口由Adapter实现

public class ItemTouchHelperAdapter extends RecyclerView.Adapter<ItemTouchHelperAdapter.ItemTouchHelperViewHolder> implements ItemMoveListener{

    ...

    @Override
    public boolean onItemRemove(int position) {
        //1、删除数据
        mData.remove(position);
        //2、刷新
        notifyItemRemoved(position);
        return true;
    }
}

接口在ItemTouchHelper.Callback在onSwiped()中调用

/**
 * 当item侧滑出去时触发(竖直列表是侧滑,水平列表是竖滑)
 *
 * @param viewHolder
 * @param direction  滑动的方向
 */
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
    mItemMoveListener.onItemRemove(viewHolder.getAdapterPosition());
}

# 3)效果:

现在大部分效果已经实现,接下来是细节处理。

# 3、交互时背景变化

# 1)原理

在item被拖拽或侧滑时修改背景色,当动作结束后将背景色恢复回来,而ItemTouchHelper.Callback中正好有对应这两个状态的方法,分别是:onSelectedChanged()、clearView()。

# 2)实现

/**
 * 当item被拖拽或侧滑时触发
 *
 * @param viewHolder
 * @param actionState 当前item的状态
 */
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
    super.onSelectedChanged(viewHolder, actionState);
    //不管是拖拽或是侧滑,背景色都要变化
    if (actionState != ItemTouchHelper.ACTION_STATE_IDLE)
        viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources().getColor(android.R.color.darker_gray));
}

/**
 * 当item的交互动画结束时触发
 *
 * @param recyclerView
 * @param viewHolder
 */
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
    super.clearView(recyclerView, viewHolder);
    viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources().getColor(android.R.color.white));
}

# 3)效果

# 4、左右滑动时item渐变

# 1)分析及实现

在最开始的图中,当item被侧滑出去的过程中,可以看到item的透明度在渐渐变浅,高度在渐渐变小,其实就是让item执行了两种属性动画而已,在ItemTouchHelper.Callback中有一个方法可以拿到item被拖拽或滑动时的位移变化,那就是onChildDraw()方法,这样就很好办了,看如下代码实现:

@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {

	//这句代码就是item拖拽和滑动效果的实现,所以这句不能省略
    super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);

	//我们只需要在左右滑动时,将透明度和高度的值变小(1 --> 0)
    if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
        float value = 1 - Math.abs(dX) / viewHolder.itemView.getWidth();
        viewHolder.itemView.setAlpha(value);
        viewHolder.itemView.setScaleY(value);
    }
}

# 2)效果(有瑕疵)

可以看到,item在滑动过程中渐渐透明并高度缩小了,但是,我明明是删除了两条,怎么结果列表中多出来两条空白的数据!这又是为什么呢?

# 3)修复

其实上图中并不是多出了两条空白数据,它们是正常的数据,只是看不到了,这是因为RecyclerView条目(itemView)覆用导致的,前面在onChildDraw()方法中对itemView设置了透明和缩小,而一个列表中固定只有几个itemView而已,当那两个透明缩小的itemView被再次使用时,之前设置的透明度和高度比例已经是0,所以就出现了这种情况,解决方法也很简单,只要在item被移除后,将itemView的透明度和高度比例设置回来即可,代码如下:

/**
 * 当item的交互动画结束时触发
 *
 * @param recyclerView
 * @param viewHolder
 */
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
    super.clearView(recyclerView, viewHolder);
    ...

    viewHolder.itemView.setAlpha(1);
    viewHolder.itemView.setScaleY(1);
}

# 4)效果(完美)

到这里,最开始的所有交互效果都已经实现了。

# 最后附上Demo链接

https://github.com/GitLqr/MaterialDesignDemo (opens new window)

#MaterialDesign#RecyclerView#ItemTouchHelper
RecyclerView之ItemDecoration
MaterialDesign之SearchView全面解锁

← RecyclerView之ItemDecoration MaterialDesign之SearchView全面解锁→

最近更新
01
Flutter - Xcode16 还原编译速度
04-05
02
AI - 免费的 Cursor 平替方案
03-30
03
Android - 2025年安卓真的闭源了吗
03-28
更多文章>
Theme by Vdoing | Copyright © 2020-2025 FSA全栈行动
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×