美文网首页各种viewAndroid开发经验谈Android开发
深入学习RecyclerView之让你轻松实现拖拽与滑动删除功能

深入学习RecyclerView之让你轻松实现拖拽与滑动删除功能

作者: 皮球二二 | 来源:发表于2017-11-20 16:31 被阅读156次

但凡遇到列表数据显示,产品经理可能都会告诉你,我需要对列表数据进行增删改查的操作。我们可能会对产品经理说:android默认采用长按Item视图来进行选择功能操作,但是产品经理肯定会怼过去说:你看IOS的那种效果不是更高大上吗?然后你无言以对,只能默默的想办法实现。早期我采用SwipeMenuListView,不过已经被废弃不更新了,后来RecyclerView来了之后又用SwipeRecyclerView,效果还是不错的。既然是好东西,我们就要加以利用,同时最好还能学习到大神编写的思路。所以我们就从最基础的功能来,一步步自己手动实现简单的拖动排序以及滑动删除功能
本文设计到的代码已同步在github上,欢迎star、fork

RecyclerView很贴心,它已经给你提供好了拖动排序和滑动的接口,这时候只需要你实现相应的接口即可

准备工作

先实现一个RecyclerView,不废话直接上代码,可以无视

class DragSwipeActivity : AppCompatActivity() {

    val beans = ArrayList<String>()
    var adapter: DragSwipeAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        (0..30).mapTo(beans) { "$it" }

        adapter = DragSwipeAdapter(beans, this)
        rv_demo.layoutManager = LinearLayoutManager(this)
        rv_demo.setHasFixedSize(true)
        rv_demo.adapter = adapter
    }

}
class DragSwipeAdapter(val beans: ArrayList<String>, val context: Context) : RecyclerView.Adapter<DragSwipeAdapter.DragSwipeHolder>() {
    override fun getItemCount(): Int {
        return beans.size
    }

    override fun onBindViewHolder(holder: DragSwipeHolder?, position: Int) {
        holder?.iv_adapter?.imageResource = R.mipmap.ic_launcher
        holder?.tv_adapter?.text = beans[holder?.layoutPosition!!]
    }

    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): DragSwipeHolder {
        val view = LayoutInflater.from(context).inflate(R.layout.adapter_main, parent, false)
        return DragSwipeHolder(view)
    }

    class DragSwipeHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val iv_adapter: ImageView = itemView.find(R.id.iv_adapter)
        val tv_adapter: TextView = itemView.find(R.id.tv_adapter)
    }
}
RecyclerView效果

ItemTouchHelper

下面就开始说正题,如何使用ItemTouchHelper。我们刚才说了,RecycleView已经帮你做了拖动排序以及侧滑删除,只需要你实现相应的接口,这个接口就是ItemTouchHelper.Callback,这个接口有几个重要的方法是一定要实现的

class StartDragCallBack : ItemTouchHelper.Callback() {
    override fun getMovementFlags(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?): Int {

    }

    override fun onMove(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?, target: RecyclerView.ViewHolder?): Boolean {

    }

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder?, direction: Int) {

    }
}

getMovementFlags:指定拖动排序以及滑动的方向,只有指定了,才能在相应的方向上发生移动。可以ItemTouchHelper.makeMovementFlags(int, int)来构造返回的flag。这里实现上下方向为拖动排序,左右方向为滑动(swipe)

    override fun getMovementFlags(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?): Int {
        val up = ItemTouchHelper.UP
        val down = ItemTouchHelper.DOWN
        val left = ItemTouchHelper.LEFT
        val right = ItemTouchHelper.RIGHT
        val dragFlags = up or down
        val swipeFlags = left or right
        return makeMovementFlags(dragFlags, swipeFlags)
    }

默认情况下你可以通过长按Item达到拖动排序、直接侧滑Item触发滑动删除。你也可以通过isLongPressDragEnabled()方法来关闭这个功能

override fun isLongPressDragEnabled(): Boolean = false

最后我们将其作用在RecyclerView上吧

val itemTouchHelper = ItemTouchHelper(callBack)
itemTouchHelper.attachToRecyclerView(rv_demo)

这样我们就实现了初步效果


ItemTouchHelper长按触发

但是这样显然是不完善的,用户也不一定知道要长按才可以实现这样效果,假如能够通过某一个ImageView来进行引导就好了。这时候就需要ItemTouchHelper.startDrag(ViewHolder)来帮忙了。
这个方法需要的是viewHolder,那肯定就要在adapter中去获取,所以我们需要通过接口将这个viewHolder从adapter中拿到activity里面去

interface StartDragAndSwipeListener {
    fun onStartDrag(viewHolder: RecyclerView.ViewHolder)
}

然后就是adapter去调用这个接口,将viewHolder传出来,我们把这个功能放在ImageView上

    override fun onBindViewHolder(holder: DragSwipeHolder?, position: Int) {
        holder?.iv_adapter?.imageResource = R.mipmap.ic_launcher
        holder?.tv_adapter?.text = beans[holder?.layoutPosition!!]
        holder?.iv_adapter?.setOnTouchListener { _, _ ->
            startDragAndSwipeListener?.onStartDrag(holder)
            true
        }
    }

最后activity去实现这个接口

val adapter = DragSwipeAdapter(beans, this, object : StartDragListener {
    override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) {
        itemTouchHelper?.startDrag(viewHolder)
    }
})

这样的话我们通过直接触摸ImageView也可以进行拖动排序了
同理侧滑删除也是一样的道理来实现
最后我们只要通过isLongPressDragEnabled和isItemViewSwipeEnabled关闭默认效果即可

override fun isLongPressDragEnabled(): Boolean = false
override fun isItemViewSwipeEnabled(): Boolean = false

来看看效果

指定具体某个View执行拖动排序以及侧滑删除
我想你应该注意到一个问题了,这里拖动排序并没有上下交换的效果,并且值也没有发生任何交换。是的,我们还需要借助adapter.notifyItemMoved去进行Item的交换,通知交换的,正是ItemTouchHelper.Callback()中的onMove方法。
onMove():用于通知拖动之后底层数据的更新
同样这里我们也需要通过接口将CallBack与Activity进行关联
interface OnDragMoveListener {
    fun onMove(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?, target: RecyclerView.ViewHolder?)
}

onMove中的参数直接传递到接口中进行调用

    override fun onMove(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?, target: RecyclerView.ViewHolder?): Boolean {
        onDragMoveListener.onMove(recyclerView, viewHolder, target)
        return true
    }

activity中实现数据交换以及视图刷新

var callBack = StartDragCallBack(object : OnDragMoveListener {
    override fun onMove(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?, target: RecyclerView.ViewHolder?) {
        if (viewHolder?.itemViewType == target?.itemViewType) {
            Collections.swap(beans, viewHolder?.layoutPosition!!, target?.layoutPosition!!)
            adapter?.notifyItemMoved(viewHolder?.layoutPosition!!, target?.layoutPosition!!)
        }
    }
})

来看看效果

拖动排序效果
排序的我们解决完了,剩下就是侧滑删除了,这个就轮到剩下的onSwiped()了
onSwiped():与onMove一样,只不过是用于通知侧滑之后底层数据的更新
具体操作跟onMove也一样,通过接口来实现,这里就不贴重复的代码了,你可以直接通过git查看
侧滑删除效果
至此,基本功能就完成了

美化效果

基本功能完成之后,下面就剩下美化的工作了。我们看到我在拖动排序的过程中,由于Item内容接近,导致排序过程不明显,有没有办法对其进行高亮突出呢?答案是肯定的,我们需要借助onSelectedChanged(ViewHolder, int) 与clearView(RecyclerView, ViewHolder)
onSelectedChanged:从静止状态变为拖拽或者滑动的时候会回调该方法,参数actionState表示当前的状态
clearView:当用户操作完某个Item并且其动画也执行结束后会调用该方法,一般我们在该方法内恢复Item的初始状态,防止由于复用而产生显示错乱问题。

    override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
        viewHolder?.itemView?.backgroundColor = Color.BLUE
    }

    override fun clearView(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?) {
        super.clearView(recyclerView, viewHolder)
        viewHolder?.itemView?.backgroundColor = android.R.color.holo_green_light
    }
高亮效果
你还可以额外的自定义拖动排序与滑动删除效果,这就需要onChildDraw(…)的帮助了
onChildDraw:我们可以在这个方法内实现我们自定义的交互规则或者自定义的动画效果。
dX与dY参数代表目前被选择view的移动距离,正方向为向右以及向下移动。
actionState为ACTION_STATE_DRAG或者ACTION_STATE_SWIPE。为了不漏掉我们没有处理的actionState,请记住务必调用super方法,这样其他的默认动画才会运行。当然如果你想跳过默认动画直接自己实现Item上的动画效果,你可以选择不调用super方法
这里我们实现侧滑时随着拖动距离的改变而产生渐变、缩放效果
    override fun clearView(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?) {
        super.clearView(recyclerView, viewHolder)
        viewHolder?.itemView?.backgroundColor = android.R.color.holo_green_light

        viewHolder?.itemView?.alpha = 1f
        viewHolder?.itemView?.scaleX = 1f
        viewHolder?.itemView?.scaleY = 1f
    }

    override fun onChildDraw(c: Canvas?, recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)

        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
            val radio = 1 - Math.abs(dX) / viewHolder?.itemView?.width?.toFloat()!!
            viewHolder?.itemView?.alpha = radio
            viewHolder?.itemView?.scaleX = radio
            viewHolder?.itemView?.scaleY = radio
        }
    }

来看看效果


自定义滑动效果

至此,你应该学会如何使用ItemTouchHelper了

相关文章

网友评论

本文标题:深入学习RecyclerView之让你轻松实现拖拽与滑动删除功能

本文链接:https://www.haomeiwen.com/subject/bocjvxtx.html