简介
ItemTouchHandler 是 Google 提供的一个工具类,主要针对 RecyclerView 的上下左右拖动事件进行处理,可以同时实现拖动排序和侧滑删除功能。涉及一些事件分发的知识,感兴趣可以去分析源码。
第三方SwipeDelMenuLayout,毫无耦合性,一个Item根布局搞定 item侧滑删除菜单。不依赖任何父布局,不是针对 RecyclerView、ListView,而是任意的 ViewGroup 里的 childView 都可以使用侧滑删除。
一、ItemTouchHandler 拖动排序和侧滑删除
效果展示:
- 先创建一个回调类:ItemTouchHelperCallBack
- context:上下文
- listData:列表数据
- adapter:对应适配器
- isDrag:是否可以拖拽
- isSwipe:是否可以侧滑
class ItemTouchHelperCallBack <T> (val context: Context,val listData:List<T>,val adapter:BaseQuickAdapter<T,BaseViewHolder>,val isDrag:Boolean=true,val isSwipe:Boolean=true): ItemTouchHelper.Callback()
- 实现方法(这些方法也可以设置接口在外部去实现)
- getMovementFlags:一个是dragFlags,表示拖动效果支持的方向,另一个是swipeFlags,表示侧滑效果支持的方向
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
var dragFlag = 0
if (recyclerView.layoutManager is GridLayoutManager) {
dragFlag =
ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
} else if (recyclerView.layoutManager is LinearLayoutManager) {
dragFlag = ItemTouchHelper.UP or ItemTouchHelper.DOWN
}
return makeMovementFlags(dragFlag, 0)
}
- onMove:当拖动开始时会回调此方法,通常将拖动的item与其他item交换位置,更新数据源
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
//absoluteAdapterPosition和layoutPosition相差不大:
// layoutPosition相对于实际展示的布局:使用场景一般在notifyItemInserted之后, Layout不能马上获取到新的position, 布局还没更新时
// absoluteAdapterPosition相对于数据
val fromPosition = viewHolder.absoluteAdapterPosition
//拿到当前拖拽到的item的viewHolder
val toPosition = target.absoluteAdapterPosition
if (fromPosition < toPosition) {
for (i in fromPosition until toPosition) {
//交换
Collections.swap(listData, i, i + 1)
}
} else {
for (i in fromPosition downTo toPosition + 1) {
Collections.swap(listData, i, i - 1)
}
}
adapter.notifyItemMoved(fromPosition, toPosition);
return true
}
- onSwiped(可自行实现):当侧滑开始时调用,从数据源里面移除相应的数据
- onSelectedChanged :长按选中Item的时候开始调用,设置震动效果
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
//获取系统震动服务 震动70毫秒
val vib = context.getSystemService(AppCompatActivity.VIBRATOR_SERVICE) as Vibrator
vib.vibrate(50)
}
super.onSelectedChanged(viewHolder, actionState)
}
- clearView:手指松开的时候调用,刷新数据
override fun clearView(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
) {
super.clearView(recyclerView, viewHolder)
Handler().post(Runnable {
adapter.notifyDataSetChanged(); //完成拖动后刷新适配器,这样拖动后删除就不会错乱
})
}
- isLongPressDragEnabled、isItemViewSwipeEnabled
/**
* 是否支持长按拖拽,默认为true,表示支持长按拖拽
* 对应长按移动位置功能
* 也可以返回false,手动调用startDrag()方法启动拖拽
*/
override fun isLongPressDragEnabled(): Boolean {
return isDrag
}
/**
* 是否支持任意位置触摸事件发生时启用滑动操作,默认为true,表示支持滑动
* 对应滑动删除功能
* 也可以返回false,手动调用startSwipe()方法启动滑动
*/
override fun isItemViewSwipeEnabled(): Boolean {
return isSwipe
}
全部代码:
class ItemTouchHelperCallBack <T> (val context: Context,val listData:List<T>,val adapter:BaseQuickAdapter<T,BaseViewHolder>,val isDrag:Boolean=true,val isSwipe:Boolean=true): ItemTouchHelper.Callback() {
//一个是dragFlags,表示拖动效果支持的方向,另一个是swipeFlags,表示侧滑效果支持的方向
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
var dragFlag = 0
if (recyclerView.layoutManager is GridLayoutManager) {
dragFlag =
ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
} else if (recyclerView.layoutManager is LinearLayoutManager) {
dragFlag = ItemTouchHelper.UP or ItemTouchHelper.DOWN
}
return makeMovementFlags(dragFlag, 0)
}
//当拖动效果已经产生了,会回调此方法,通常会更新数据源
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
//absoluteAdapterPosition和layoutPosition相差不大:
// layoutPosition相对于实际展示的布局:使用场景一般在notifyItemInserted之后, Layout不能马上获取到新的position, 布局还没更新时
// absoluteAdapterPosition相对于数据
val fromPosition = viewHolder.absoluteAdapterPosition
//拿到当前拖拽到的item的viewHolder
val toPosition = target.absoluteAdapterPosition
if (fromPosition < toPosition) {
for (i in fromPosition until toPosition) {
Collections.swap(listData, i, i + 1)
}
} else {
for (i in fromPosition downTo toPosition + 1) {
//交换
Collections.swap(listData, i, i - 1)
}
}
adapter.notifyItemMoved(fromPosition, toPosition);
return true
//滑动事件 下面注释的代码,滑动后数据和条目错乱,被舍弃
//Collections.swap(datas,viewHolder.getAdapterPosition(),target.getAdapterPosition());
//ap.notifyItemMoved(viewHolder.getAdapterPosition(),target.getAdapterPosition());
}
//当侧滑效果以上产生了,从数据源里面移除相应的数据
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
}
/**
* 长按选中Item的时候开始调用
* 长按高亮
* @param viewHolder
* @param actionState
*/
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
//获取系统震动服务 震动70毫秒
val vib = context.getSystemService(AppCompatActivity.VIBRATOR_SERVICE) as Vibrator
vib.vibrate(50)
}
super.onSelectedChanged(viewHolder, actionState)
}
/**
* 手指松开的时候还原高亮
* @param recyclerView
* @param viewHolder
*/
override fun clearView(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
) {
super.clearView(recyclerView, viewHolder)
Handler().post(Runnable {
adapter.notifyDataSetChanged(); //完成拖动后刷新适配器,这样拖动后删除就不会错乱
})
}
/**
* 是否支持长按拖拽,默认为true,表示支持长按拖拽
* 对应长按移动位置功能
* 也可以返回false,手动调用startDrag()方法启动拖拽
*/
override fun isLongPressDragEnabled(): Boolean {
return isDrag
}
/**
* 是否支持任意位置触摸事件发生时启用滑动操作,默认为true,表示支持滑动
* 对应滑动删除功能
* 也可以返回false,手动调用startSwipe()方法启动滑动
*/
override fun isItemViewSwipeEnabled(): Boolean {
return isSwipe
}
}
- 使用
先设置isDrag为false,指定某个控件再去启动拖动
private val itemTouchHelper by lazy {
//数据listData会改变
ItemTouchHelper(
ItemTouchHelperCallBack(
context = this,
listData = itemData,
adapter = itemAdapter,
isDrag = false
)
)
}
//与recyclerView控件绑定
itemTouchHelper.attachToRecyclerView(rv_add_item)
//指定item里面的某个控件长按才能拖动
adapter.setLongClickInterface(object :SetShowTabAdapter.LongClickInterface{
override fun onDrag(holder: BaseViewHolder) {
itemTouchHelper.startDrag(holder)
}
})
二、SwipeDelMenuLayout 侧滑删除
效果展示:
- 导入依赖
implementation 'com.github.mcxtzhang:SwipeDelMenuLayout:V1.3.0'
- xml设置
在适配器的item布局中,给需要滑动删除那个控件外层添加一个 SwipeMenuLayout 标签包裹住控件和一个删除按钮,让控件宽度铺满屏幕
<com.mcxtzhang.swipemenulib.SwipeMenuLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:paddingTop="15dp"
android:paddingBottom="15dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="实例"
android:textColor="@color/api_grey_deep1"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
<Button
android:id="@+id/btn_delete"
android:layout_width="60dp"
android:layout_height="match_parent"
android:background="#ff0000"
android:text="删除"
android:textSize="16sp"
android:textColor="@android:color/white"/>
</com.mcxtzhang.swipemenulib.SwipeMenuLayout>
- 使用
使用非常简单,只需要在适配器配置相应的点击事件即可
holder.getView<Button>(R.id.btn_delete).setOnClickListener {
if(holder.absoluteAdapterPosition in 0 until itemCount){
list.removeAt(holder.absoluteAdapterPosition) //移除数据
notifyItemRemoved(holder.absoluteAdapterPosition) //刷新adapter
}
}
总结
实现这两个功能有很多方法,都是比较简单的。
很长时间没更新了,之前去备考了,希望能有个好结果吧