一、背景
前面已经实现了 RecyclerView 的上拉加载更多,增加 header,自定义滑动菜单,基本能满足大部分场景的样式了,就算不满足也能通过直接改部分代码轻松实现新的样式;不过这一次产品玩别的 app 时发现了一个新的交互方式,某些列表不需要复杂的操作,只需要删除操作,那么如果用那种滑动菜单的交互方式,用户就得先把菜单滑出来,再点删除按钮才能删掉,这样一来用户就多操作了一步,不如直接让用户滑动删除,他说这叫返璞归真,简约效率。
二、思路分析
第一想法是通过修改之前的滑动菜单逻辑实现,打算修改一下边界,再控制一下状态判定阀值,但是总感觉这样搞不太合适,感觉有更简洁干脆的方法来实现这个,去网上看了一圈果然发现api里有个现成的工具类是专门来搞这类需求的,ItemTouchHelper。因为用的是现成的原生api,所以此文是对其一些用法的记录。那么这也不需要啥思路了,因为可以直接自定义一个类继承 ItemTouchHelper.Callback,然后可以直接通过监听 onSwiped 来实现滑动删除,另外在看这个类的监听时发现还有个onMove() 方法,这个太适合做拖拽排序了,所以干脆把排序的做法也记录一下。
三、具体实现
1.首先自定义一个类继承 ItemTouchHelper.Callback,通过实现 onSwiped 方法来监听滑动的操作,在此方法里执行实际删除列表 item 的逻辑;通过实现 onMove 方法来监听拖拽换位结果,在此方法里执行实际排序 item 的逻辑;
...
/**
* 返回拖拽中换位的回调
*
* @param recyclerView
* @param viewHolder
* @param target
* @return true:换位成功
* false:没换成功
*/
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
if (null == onItemTouchHelperCallBack) {
return false;
}
return onItemTouchHelperCallBack.onMove(viewHolder, target);
}
/**
* 滑动后的回调
*
* @param viewHolder
* @param direction
*/
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
if (null != onItemTouchHelperCallBack) {
onItemTouchHelperCallBack.onSwiped(viewHolder);
}
}
...
在 RecyclerView的adapter 里实现这两个方法:
...
@Override
public boolean onMove(RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int adapterPosition = viewHolder.getAdapterPosition();
int adapterPosition1 = target.getAdapterPosition();
if (adapterPosition1 <= 2) {
//例如设置position小于2的item不让换
return false;
}
if (getHeaderViewCount() > 0) {
Collections.swap(mDatas, adapterPosition - 1, adapterPosition1 - 1);
} else {
Collections.swap(mDatas, adapterPosition, adapterPosition1);
}
notifyItemMoved(adapterPosition, adapterPosition1);
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder) {
int adapterPosition = viewHolder.getAdapterPosition();
mDatas.remove(getHeaderViewCount() > 0 ? adapterPosition - 1 : adapterPosition);
notifyItemRemoved(adapterPosition);
}
...
然后在自定义的 Callback 类里设置一下允许滑动的参数:
/**
* 设置允许拖动换位和滑动删除的方向
*
* @param recyclerView
* @param viewHolder
* @return
*/
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
if (isContentType(viewHolder)) {
//dragFlags:设置为可以向上和向下拖动
//swipeFlags:设置成可以从左向右滑动删除
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.END);
}
return makeMovementFlags(0, 0);
}
private boolean isContentType(RecyclerView.ViewHolder viewHolder) {
return viewHolder.getItemViewType() == BaseAdapter.TYPE_CONTENT_VIEW;
}
这里需要注意的是要控制一下 header 和 footer 不能参与滑动删除和排序。
这样就实现了滑动删除和拖拽排序,这也太简单了,突然就实现了这个需求。这时产品设计上可能会有一些美化,例如滑动时要在 item 下面提示用户这是删除操作,这个可以通过实现 onChildDraw 方法,用 canvas 来画一些东西,例如在item后面显示 “delete” 文字提示用户:
/**
* 拖拽或滑动删除时,在item下面画点什么时用到
*/
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
View itemView = viewHolder.