在 Android 开发中,为列表视图添加拖动排序功能可以极大地提升用户体验。本篇文章将详细指导您如何使用 Android 原生提供的 ItemTouchHelper 工具类,结合流行的 BRVAH (BaseRecyclerViewAdapterHelper) 库,实现 RecyclerView 的长按拖动排序功能。

本文将以一个使用 BaseQuickAdapter 的嘉宾列表 PaipaiGuestAdapter 为例进行讲解和代码实现。

一、核心原理与实现步骤

实现 RecyclerView 拖动排序的核心在于使用 ItemTouchHelper。它通过一个回调接口 ItemTouchHelper.Callback 监听用户的手势,并将事件传递给我们的 Adapter 来执行数据操作和 UI 刷新。

关键步骤:
定义接口 (ItemTouchHelperAdapter): 规范 Adapter 的数据移动方法。

实现 Adapter: 在 PaipaiGuestAdapter 中实现数据交换逻辑,并添加拖动时的视觉反馈。

创建 Callback: 编写 SimpleItemTouchHelperCallback,定义拖动方向和手势启用。

绑定: 在 Activity/Fragment 中将 ItemTouchHelper 附加到 RecyclerView。

二、代码实现

  1. 步骤一:定义数据移动接口 (ItemTouchHelperAdapter.java)

这个接口是 ItemTouchHelper.Callback 与 Adapter 之间沟通的桥梁。

package com.bgzb.bingganbgzb.ui.room.main.paipai.adapter;

// 接口:定义 Adapter 必须实现的拖动方法
public interface ItemTouchHelperAdapter {
/**
* 在数据源中交换 item 位置并通知 Adapter
* @param fromPosition 起始位置
* @param toPosition 目标位置
*/
void onItemMove(int fromPosition, int toPosition);
}
  1. 步骤二:修改 Adapter (PaipaiGuestAdapter.java)

我们让 PaipaiGuestAdapter 实现 ItemTouchHelperAdapter,并处理拖动过程中的数据交换和视觉反馈。

注意: 为了解决 Inconvertible types 的编译问题,我们在 onItemSelected 和 onItemClear 中使用了 RecyclerView.ViewHolder 作为参数,并配合 @SuppressWarnings(“unchecked”) 进行安全转换。

package com.bgzb.bingganbgzb.ui.room.main.paipai.adapter;

import android.graphics.Color;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
// ... 其他必要的 import 保持不变 ...
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.viewholder.BaseDataBindingHolder;

import java.util.Collections;
import java.util.List;

public class PaipaiGuestAdapter extends BaseQuickAdapter<GuestItem, BaseDataBindingHolder<UserItemBinding>>
implements ItemTouchHelperAdapter {

// ... 原有的成员变量和构造方法 ...

// =======================================================
// 拖动排序核心方法:实现 ItemTouchHelperAdapter 接口
// =======================================================
@Override
public void onItemMove(int fromPosition, int toPosition) {
// 1. 在数据列表中交换位置
List<GuestItem> list = getData();
Collections.swap(list, fromPosition, toPosition);

// 2. 通知 RecyclerView 适配器 item 已经移动,触发动画
notifyItemMoved(fromPosition, toPosition);

// TODO: 【重要】拖动排序完成后,应在此处通过回调通知业务层保存新的排序结果!
}

// =======================================================
// 拖动视觉反馈:解决类型转换问题
// =======================================================
@SuppressWarnings("unchecked")
public void onItemSelected(RecyclerView.ViewHolder viewHolder) {
if (viewHolder instanceof BaseDataBindingHolder) {
BaseDataBindingHolder<UserItemBinding> holder = (BaseDataBindingHolder<UserItemBinding>) viewHolder;
if (holder.getDataBinding() != null) {
// 拖动开始:改变背景色
holder.getDataBinding().bg.setBackgroundColor(Color.LTGRAY);
}
}
}

@SuppressWarnings("unchecked")
public void onItemClear(RecyclerView.ViewHolder viewHolder) {
if (viewHolder instanceof BaseDataBindingHolder) {
BaseDataBindingHolder<UserItemBinding> holder = (BaseDataBindingHolder<UserItemBinding>) viewHolder;

int position = viewHolder.getAdapterPosition();

// 检查位置是否有效
if (position == RecyclerView.NO_POSITION || position >= getData().size() || position < 0) {
return;
}

// 恢复原有背景
if (holder.getDataBinding() != null) {
GuestItem item = getData().get(position);

// 根据 setListType 恢复复杂的背景逻辑
if (setListType == 2) {
if (item.getUser_id().equals(UserManager.INSTANCE.getUserDto().user_id)) {
holder.getDataBinding().bg.setBackgroundResource(R.drawable.guest_list_mine_bg);
holder.getDataBinding().bg.setBackgroundColor(Color.parseColor("#00000000"));
} else {
holder.getDataBinding().bg.setBackgroundColor(Color.parseColor("#80FFFFFF"));
holder.getDataBinding().bg.setBackgroundResource(0);
}
} else {
holder.getDataBinding().bg.setBackgroundColor(Color.TRANSPARENT);
holder.getDataBinding().bg.setBackgroundResource(0);
}
}
}
}

@Override
protected void convert(@NonNull BaseDataBindingHolder<UserItemBinding> userItemBindingBaseDataBindingHolder, GuestItem guestItem) {
// ... 原有的 convert 逻辑 ...
// 更新 item 序号,使用 getAdapterPosition() 更准确
userItemBindingBaseDataBindingHolder.getDataBinding().num.setText(
String.valueOf(userItemBindingBaseDataBindingHolder.getAdapterPosition() + 1)
);
}
}
  1. 步骤三:创建 Callback (SimpleItemTouchHelperCallback.java)

这是定义手势行为的类,它将长按事件转换为拖动事件,并调用 Adapter 的方法。

package com.bgzb.bingganbgzb.ui.room.main.paipai.adapter;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;

// 继承 ItemTouchHelper.Callback,实现拖动和滑动的核心逻辑。
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {

private final PaipaiGuestAdapter mAdapter;

public SimpleItemTouchHelperCallback(PaipaiGuestAdapter adapter) {
mAdapter = adapter;
}

// 1. 定义运动方向
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
// 允许上下拖动(垂直列表)
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
// 不允许滑动
final int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
}

// 2. 启用长按拖动
@Override
public boolean isLongPressDragEnabled() {
return true;
}

// 3. 拖动处理:调用 Adapter 的数据移动方法
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
mAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}

// 4. 滑动处理(未启用)
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
// 如果未启用滑动,此方法留空
}

// 5. 拖动/选择状态改变时的视觉反馈 (调用 Adapter 的 onItemSelected)
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE && viewHolder != null) {
mAdapter.onItemSelected(viewHolder);
}
super.onSelectedChanged(viewHolder, actionState);
}

// 6. 拖动结束后的视觉恢复 (调用 Adapter 的 onItemClear)
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
mAdapter.onItemClear(viewHolder);
}

}
  1. 步骤四:在 Activity/Fragment 中绑定

最后,在您的 Activity 或 Fragment 初始化 RecyclerView 的地方添加以下几行代码来启用拖动功能。

// 假设这段代码位于您的 Activity 或 Fragment 中(使用 Kotlin 示例)

val recyclerView = view.findViewById<RecyclerView>(R.id.user_list)
val paipaiGuestAdapter = PaipaiGuestAdapter()
recyclerView.adapter = paipaiGuestAdapter

// =======================================================
// 【新增】长按拖动排序的绑定代码
// =======================================================
// 1. 创建 ItemTouchHelper.Callback 实例
val callback = SimpleItemTouchHelperCallback(paipaiGuestAdapter)

// 2. 创建 ItemTouchHelper 实例
val itemTouchHelper = ItemTouchHelper(callback)

// 3. 将 ItemTouchHelper 附加到 RecyclerView
itemTouchHelper.attachToRecyclerView(recyclerView)
// =======================================================

注意: itemTouchHelper 请只实例化和使用一次!刷新和替换数据直接setList即可