版权声明:个人原创,欢迎转载。 https://blog.csdn.net/chuyangchangxi/article/details/86549209
RecyclerView使用ItemTouchHelper实现拖拽排序
一、目标
拖拽完成文件夹收藏排序。
通过拖拽右侧拖拽按钮,实现文件夹收藏排序。
二、体验地址
神马笔记最新版本:【神马笔记 版本1.3.0.apk】
三、功能设计
收藏文件夹时,总是添加到第一个位置,显示在列表最上方。
当收藏的文件夹数量增加时,需要对收藏进行排序,方便查找。
常见的几种排序方式:
- 手动排序
- 名称
- ……
这里,我们使用手动排序方式。
四、准备工作
1. ItemTouchHelper
ItemTouchHelper有2个功能:
- 拖拽——调整列表项顺序
- 侧向滑动——删除列表项
public ItemTouchHelper(Callback callback);
public void attachToRecyclerView(@Nullable RecyclerView recyclerView);
public void startDrag(ViewHolder viewHolder);
public void startSwipe(ViewHolder viewHolder);
ItemTouchHelper
提供给外部调用的接口主要有以上4个。
- 创建ItemTouchHelper对象
- 附加到
RecyclerView
上 - 开始拖拽
- 开始侧向滑动
ItemTouchHelper
的行为由Callback
对象来决定,使用ItemTouchHelper
的关键在于实现Callback
接口。
2. ItemTouchHelper.Callback
将Callback
的需要实现的接口,分为4个类别。
- 必须实现的抽象方法
// 必须通过makeMovementFlags返回值,上、下、左、右四个方向
// SimpleCallback提供了该方法的一个实现版本
public abstract int getMovementFlags(RecyclerView recyclerView,
ViewHolder viewHolder);
// 发生拖拽事件
public abstract boolean onMove(RecyclerView recyclerView,
ViewHolder viewHolder, ViewHolder target);
// 发生侧滑事件
public abstract void onSwiped(ViewHolder viewHolder, int direction);
- 拖拽行为接口
// 是否支持长按触发拖拽
public boolean isLongPressDragEnabled() {
return true;
}
// 是否可以放置到target位置
public boolean canDropOver(RecyclerView recyclerView, ViewHolder current,
ViewHolder target) {
return true;
}
// 触发移动的阈值
public float getMoveThreshold(ViewHolder viewHolder) {
return .5f;
}
- 侧滑行为接口
// Item是否支持侧向滑动
public boolean isItemViewSwipeEnabled() {
return true;
}
// 侧滑事件的滑动距离触发值
public float getSwipeThreshold(ViewHolder viewHolder) {
return .5f;
}
// 侧滑事件的速度触发值
public float getSwipeEscapeVelocity(float defaultValue) {
return defaultValue;
}
// ???
public float getSwipeVelocityThreshold(float defaultValue) {
return defaultValue;
}
- UI相关接口(包括拖拽及侧滑)
// 选中目标Item时
public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
if (viewHolder != null) {
sUICallback.onSelected(viewHolder.itemView);
}
}
// 动作完成时
public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
sUICallback.clearView(viewHolder.itemView);
}
// ItemDecoration#onDraw
public void onChildDraw(Canvas c, RecyclerView recyclerView,
ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
sUICallback.onDraw(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
isCurrentlyActive);
}
// ItemDecoration#onDrawOver
public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
ViewHolder viewHolder,
float dX, float dY,
int actionState, boolean isCurrentlyActive) {
sUICallback.onDrawOver(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
isCurrentlyActive);
}
3. ItemTouchUIUtil
拖拽及侧滑发生时,控制Item的UI。
interface ItemTouchUIUtil {
void onDraw(Canvas c, RecyclerView recyclerView, View view,
float dX, float dY, int actionState, boolean isCurrentlyActive);
void onDrawOver(Canvas c, RecyclerView recyclerView, View view,
float dX, float dY, int actionState, boolean isCurrentlyActive);
void clearView(View view);
void onSelected(View view);
}
4. ItemTouchUIUtilImpl.BaseImpl
BaseImple
在事件发生时,通过设置translationX及translationY移动View的位置。
static class BaseImpl implements ItemTouchUIUtil {
@Override
public void clearView(View view) {
view.setTranslationX(0f);
view.setTranslationY(0f);
}
@Override
public void onSelected(View view) {
}
@Override
public void onDraw(Canvas c, RecyclerView recyclerView, View view,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
view.setTranslationX(dX);
view.setTranslationY(dY);
}
@Override
public void onDrawOver(Canvas c, RecyclerView recyclerView,
View view, float dX, float dY, int actionState, boolean isCurrentlyActive) {
}
}
5. ItemTouchUIUtilImpl.Api21Impl
Api21Impl
在BaseImpl
基础上设置View的elevation实现了阴影效果。
在RecyclerView最大elevation的基础上**+1**。
static class Api21Impl extends BaseImpl {
@Override
public void onDraw(Canvas c, RecyclerView recyclerView, View view,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (isCurrentlyActive) {
Object originalElevation = view.getTag(R.id.item_touch_helper_previous_elevation);
if (originalElevation == null) {
originalElevation = ViewCompat.getElevation(view);
float newElevation = 1f + findMaxElevation(recyclerView, view);
ViewCompat.setElevation(view, newElevation);
view.setTag(R.id.item_touch_helper_previous_elevation, originalElevation);
}
}
super.onDraw(c, recyclerView, view, dX, dY, actionState, isCurrentlyActive);
}
private float findMaxElevation(RecyclerView recyclerView, View itemView) {
final int childCount = recyclerView.getChildCount();
float max = 0;
for (int i = 0; i < childCount; i++) {
final View child = recyclerView.getChildAt(i);
if (child == itemView) {
continue;
}
final float elevation = ViewCompat.getElevation(child);
if (elevation > max) {
max = elevation;
}
}
return max;
}
@Override
public void clearView(View view) {
final Object tag = view.getTag(R.id.item_touch_helper_previous_elevation);
if (tag != null && tag instanceof Float) {
ViewCompat.setElevation(view, (Float) tag);
}
view.setTag(R.id.item_touch_helper_previous_elevation, null);
super.clearView(view);
}
}
五、组合起来
1. 触发拖拽
// dragBtn按下时即触发拖拽
dragBtn.setOnTouchListener(this::onDragTouch);
// 必须返回false,ItemTouchHelper会接管
boolean onDragTouch(View view, MotionEvent event) {
parent.requestDrag(this);
return false;
}
// 调用ItemTouchHelper#startDrag开始拖拽
void requestDrag(RecyclerView.ViewHolder viewHolder) {
itemTouchHelper.startDrag(viewHolder);
dividerDecoration.setDragViewHolder(viewHolder);
}
2. SimpleDragCallback
SimpleDragCallback
禁用了侧滑功能,为拖拽量身定做。
- 禁用侧滑功能——
isItemViewSwipeEnabled
返回false
- 禁用长按触发拖拽功能——
isLongPressDragEnabled
返回false
- 实现
onSwiped
方法为空方法 - 将elevation值在增加12,使阴影效果更加明显
public abstract class SimpleDragCallback extends ItemTouchHelper.SimpleCallback {
protected float elevation;
public SimpleDragCallback(int dragDirs, int swipeDirs) {
super(dragDirs, swipeDirs);
this.elevation = 12.f;
}
@Override
public boolean isLongPressDragEnabled() {
return false;
}
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
@Override
public float getMoveThreshold(RecyclerView.ViewHolder viewHolder) {
return super.getMoveThreshold(viewHolder);
}
@CallSuper
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
// order attention
{
View view = viewHolder.itemView;
final Object tag = view.getTag(R.id.anc_item_drag_previous_elevation);
if (tag != null && tag instanceof Float) {
ViewCompat.setElevation(view, (Float) tag);
}
view.setTag(R.id.anc_item_drag_previous_elevation, null);
}
{
super.clearView(recyclerView, viewHolder);
}
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
return super.getMovementFlags(recyclerView, viewHolder);
}
@Override
public boolean canDropOver(RecyclerView recyclerView, RecyclerView.ViewHolder current, RecyclerView.ViewHolder target) {
return super.canDropOver(recyclerView, current, target);
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder, float dX, float dY,
int actionState, boolean isCurrentlyActive) {
// order attention
{
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
if (isCurrentlyActive) {
View view = viewHolder.itemView;
Object originalElevation = view.getTag(R.id.anc_item_drag_previous_elevation);
if (originalElevation == null) {
originalElevation = ViewCompat.getElevation(view);
float newElevation = elevation + findMaxElevation(recyclerView, view);
ViewCompat.setElevation(view, newElevation);
view.setTag(R.id.anc_item_drag_previous_elevation, originalElevation);
}
}
}
@Override
public void onChildDrawOver(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDrawOver(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
}
private float findMaxElevation(RecyclerView recyclerView, View itemView) {
final int childCount = recyclerView.getChildCount();
float max = 0;
for (int i = 0; i < childCount; i++) {
final View child = recyclerView.getChildAt(i);
if (child == itemView) {
continue;
}
final float elevation = ViewCompat.getElevation(child);
if (elevation > max) {
max = elevation;
}
}
return max;
}
}
3. ItemDragCallback
继承自SimpleDragCallback
,实现收藏相关的功能。
- 实现
onMove
接口,完成文件夹收藏排序 - 重载
canDropOver
方法,限定只在文件夹收藏间移动 - 重载
clearView
方法,拖拽完成后,保存数据
private static class ItemDragCallback extends SimpleDragCallback {
HashMap<Class, BiConsumer<RecyclerView.ViewHolder, RecyclerView.ViewHolder>> actionMap;
EntranceFragment parent;
public ItemDragCallback(EntranceFragment f) {
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
this.elevation = 12.f;
this.parent = f;
this.actionMap = new HashMap<>();
actionMap.put(FavoriteViewHolder.class, (viewHolder, target) -> {
FavoriteEntity from = ((FavoriteViewHolder)viewHolder).getItem();
FavoriteEntity to = ((FavoriteViewHolder)target).getItem();
FavoriteEntity ds = FavoriteEntity.obtain();
ds.swap(from, to);
});
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
{
BiConsumer consumer = actionMap.get(viewHolder.getClass());
if (consumer != null) {
consumer.accept(viewHolder, target);
}
}
{
int from = viewHolder.getAdapterPosition();
int to = target.getAdapterPosition();
recyclerView.getAdapter().notifyItemMoved(from, to);
}
return true;
}
@Override
public boolean canDropOver(RecyclerView recyclerView,
RecyclerView.ViewHolder current,
RecyclerView.ViewHolder target) {
return (current.getClass() == target.getClass());
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
parent.dividerDecoration.setDragViewHolder(null);
{
FavoriteEntity ds = FavoriteEntity.obtain();
ds.save();
}
}
}
六、Finally
~仙人有待乘黄鹤~海客无心随白鸥~