【Android】RecyclerView拖动排序和侧滑删除功能实现


简介

ItemTouchHandler 是 Google 提供的一个工具类,主要针对 RecyclerView 的上下左右拖动事件进行处理,可以同时实现拖动排序和侧滑删除功能。涉及一些事件分发的知识,感兴趣可以去分析源码。

第三方SwipeDelMenuLayout,毫无耦合性,一个Item根布局搞定 item侧滑删除菜单。不依赖任何父布局,不是针对 RecyclerView、ListView,而是任意的 ViewGroup 里的 childView 都可以使用侧滑删除。


一、ItemTouchHandler 拖动排序和侧滑删除

效果展示:

在这里插入图片描述

  1. 先创建一个回调类: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()
  1. 实现方法(这些方法也可以设置接口在外部去实现)
  • 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
    }
}
  1. 使用

先设置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 侧滑删除

效果展示:

在这里插入图片描述

  1. 导入依赖
 implementation 'com.github.mcxtzhang:SwipeDelMenuLayout:V1.3.0'
  1. 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>
  1. 使用
    使用非常简单,只需要在适配器配置相应的点击事件即可
holder.getView<Button>(R.id.btn_delete).setOnClickListener {
    
    
            if(holder.absoluteAdapterPosition in 0 until itemCount){
    
    
                list.removeAt(holder.absoluteAdapterPosition) //移除数据
                notifyItemRemoved(holder.absoluteAdapterPosition) //刷新adapter
            }
        }

总结

实现这两个功能有很多方法,都是比较简单的。


很长时间没更新了,之前去备考了,希望能有个好结果吧

猜你喜欢

转载自blog.csdn.net/T01151018/article/details/135334302