Android RecycleView 实现拖拽和侧滑删除效果

效果.gif

简介

项目需要做一个拖拽排序的需求(类似头条栏目排序),原先随意找了个三方库简单的处理了一下.但是随着项目的的迭代,越来越多的需求堆积下来,三方库不满足自己定制的一些需求.所以决定自己写一写这个效果

思路:

RecycleView 实现列表样式,ItemTouchHelper实现子条目的拖拽和侧滑删除.中间牵扯到指定条目禁止排序,禁止删除的功能.

实现

1.页面搭建

1.1 主页面Activity代码


package com.wkq.dragrecycle

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import com.wkq.dragrecycle.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    var stringList = arrayListOf("北京", "河北", "河南", "山东", "天津", "陕西",
        "山西", "石家庄", "内蒙古", "黑龙江", "吉林", "新疆", "西藏", "安徽", "湖北"
    )

    private lateinit var mGridItemDecoration: GridSpaceItemDecoration
    var binding: ActivityMainBinding? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        initView()

    }

    private fun initView() {
        //设置布局选择
        binding!!.btChange.setOnClickListener {
            when ( binding!!.rvContent.layoutManager) {
                is GridLayoutManager -> {
                    binding!!.rvContent.layoutManager = LinearLayoutManager(this)
                }
                else -> {
                    binding!!.rvContent.layoutManager = GridLayoutManager(this, 4)
                }
            }
        }

        //默认网格布局
        var mAdapter = DragAdapter(this)
        mGridItemDecoration = GridSpaceItemDecoration(4)
        binding!!.rvContent.addItemDecoration(mGridItemDecoration, 0)
        binding!!.rvContent.layoutManager = GridLayoutManager(this, 4)
        binding!!.rvContent.adapter = mAdapter
        mAdapter.addItems(stringList)

        //拖拽绑定的监听器
        var callBack = DragItemCallback(mAdapter,true)
        //拖拽触摸的帮助类
        var itemTouchHelper = ItemTouchHelper(callBack)
        //绑定Rv
        itemTouchHelper.attachToRecyclerView(binding!!.rvContent)

        mAdapter.setOnItemClickListener(object : DragAdapter.OnItemClickListener {
            override fun onItemClick(position: Int) {
                Toast.makeText(this@MainActivity, mAdapter.getItems().get(position), Toast.LENGTH_SHORT).show()
            }

            override fun onItemLongClick(holder: DragAdapterViewHolder) {
                //长按开启拖拽
                itemTouchHelper.startDrag(holder)
            }
        })

    }
}

1.2 Adapter 代码实现(注意默认禁止操作Item的位置默认0)

package com.wkq.dragrecycle

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
import com.wkq.dragrecycle.databinding.ItemDragBinding


/**
 * @author wkq
 *
 * @date 2022年08月04日 13:05
 *
 * @des  拖拽的Adapter
 *  
 * @param limitPosition 禁止操作的条目位置 默认第一个
 *
 */

class DragAdapter(mContext: Context,limitPosition:Int=0) : RecyclerView.Adapter() {
    
    //上下文
    var mContext=mContext
    // 固定条目的位置
    val limitPosition = limitPosition
    //内容数据
   private var contentList = ArrayList()
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DragAdapterViewHolder {
        var binding = DataBindingUtil.inflate(
            LayoutInflater.from(mContext),
            R.layout.item_drag,
            parent,
            false
        )
        var holder = DragAdapterViewHolder(binding.root)
        holder.setDataBinding(binding)
        return holder
    }

    override fun onBindViewHolder(holder: DragAdapterViewHolder, position: Int) {
        var binding = holder.getBinding() as ItemDragBinding

        if (limitPosition==position){
            binding.tvContent.setBackgroundResource(R.drawable.shape_radius5_green2)
        }else{
            binding.tvContent.setBackgroundResource(R.drawable.shape_radius5_green)
        }
        binding.tvContent.text = contentList.get(position)

        binding.tvContent.setOnClickListener {
            mListener?.onItemClick(holder.adapterPosition)
        }
        binding.tvContent.setOnLongClickListener {
            mListener?.onItemLongClick(holder)
            return@setOnLongClickListener true
        }
    }

    override fun getItemCount(): Int {
        return contentList.size
    }

    fun addItems(items: ArrayList) {
        contentList.addAll(items)
    }

    fun addItems(item: String) {
        contentList.add(item)
    }

    fun remove(item: String) {
        contentList.remove(item)
        notifyDataSetChanged()
    }

    fun getItems():ArrayList{
        return contentList
    }
    private var mListener: OnItemClickListener? = null

    fun setOnItemClickListener(listener: OnItemClickListener) {
        mListener = listener
    }

    interface OnItemClickListener {
        fun onItemClick(position: Int)
        fun onItemLongClick(holder: DragAdapterViewHolder)
    }

}

2. DragItemCallback 拖拽的接口回调(主要实现逻辑)

package com.wkq.dragrecycle

import android.graphics.drawable.GradientDrawable
import android.util.Log
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import java.util.*


/**
 * @author wkq
 *
 * @date 2022年08月04日 13:27
 *
 *@des  拖拽的接口回调
 *
 *@param isSwipe  是否支持侧滑  默认支持
 *
 */

class DragItemCallback(adapter: DragAdapter, isSwipe: Boolean = true) : ItemTouchHelper.Callback() {

    private var mAdapter = adapter
    private var mData = adapter.getItems()
    private var isSwipe = isSwipe

    // 这个方法用于让RecyclerView拦截向上滑动,向下滑动,想左滑动
    // makeMovementFlags(dragFlags, swipeFlags); dragFlags显示项目支持拖动的方向  swipe标记物品可以滑动的方向  0 表示禁止
    //  网格布局:上下左右
    //  线性布局:上下/左右

    override fun getMovementFlags(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder
    ): Int {
        //拖动标记 0 表示禁止
        var dragFlags = 0
        //侧滑标记
        var swipeFlags = 0
        var layoytManager = recyclerView.layoutManager
        if (layoytManager is GridLayoutManager) {
            //网格布局  默认禁止侧滑
            if (viewHolder.adapterPosition != mAdapter.limitPosition) {
                dragFlags =ItemTouchHelper.LEFT or ItemTouchHelper.UP or ItemTouchHelper.RIGHT or ItemTouchHelper.DOWN
            }
            return makeMovementFlags(dragFlags, swipeFlags)
        } else if (layoytManager is LinearLayoutManager) {

            //处理 禁止滑动模块得逻辑  禁止移动得模块 禁止左右滑动
            if (viewHolder.adapterPosition != mAdapter.limitPosition) {
                dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
                swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
            }
            return makeMovementFlags(dragFlags, swipeFlags)
        }
        //默认 禁止滑动  禁止拖拽
        return makeMovementFlags(dragFlags, swipeFlags)

    }

    /**
     * 拖拽移动得回调
     */
    override fun onMove(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        target: RecyclerView.ViewHolder
    ): Boolean {
        // 起始位置
        val fromPosition = viewHolder.adapterPosition
        // 结束位置
        val toPosition = target.adapterPosition
        // 固定位置  处理笃定位置不要移动
        if (fromPosition == mAdapter.limitPosition || toPosition == mAdapter.limitPosition) {
            return false
        }
        // 根据滑动方向 交换数据  for 循环不包含  toPosition  1 替代2     2替代3  3 替代4
        if (fromPosition < toPosition) {
            // 含头不含尾
            for (index in fromPosition until toPosition) {
                //交换集合得位置
                Collections.swap(mData, index, index + 1)
            }
        } else {
            // 含头不含尾
            for (index in fromPosition downTo toPosition + 1) {
                Collections.swap(mData, index, index - 1)
            }
        }
        // 刷新布局
        mAdapter.notifyItemMoved(fromPosition, toPosition)
        return true
    }

    /**
     * 滑动结束得回调 滑动删除得逻辑
     */
    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        // direction 滑动的状态
        //ItemTouchHelper.START表示向左滑动
        // ItemTouchHelper.END  向右边滑动
        val position = viewHolder.adapterPosition
        if (position != mAdapter.limitPosition) {
            //表示  禁止移动得布局
            mData.removeAt(position)
            mAdapter.notifyItemRemoved(position)
        }

    }


    /**
     * 选中会回调这里  处理选中布局的展示样式
     */
    override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {

        if (viewHolder == null) return
        //空闲状态
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            ViewCompat.animate(viewHolder!!.itemView).setDuration(100).scaleX(1.2F).scaleY(1.2F)
                .start()
        }
        super.onSelectedChanged(viewHolder, actionState)

    }


    /**
     * 滑动结束 清理选中得状态  恢复布局的样式
     */
    override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
        // 恢复显示
        // 这里不能用if判断,因为GridLayoutManager是LinearLayoutManager的子类,改用when,类型推导有区别
        ViewCompat.animate(viewHolder.itemView).setDuration(100).scaleX(1F).scaleY(1F).start()
        super.clearView(recyclerView, viewHolder)
    }


    /**
     * 是否支持长按拖拽,默认true
     * 因为我们外部监听了长安处理操作所以 这里需要禁用没改掉
     */
    override fun isLongPressDragEnabled(): Boolean {
        return false
    }


    /**
     * 是否支持侧滑默认true(线性布局的时候外部可以传进来)
     */
    override fun isItemViewSwipeEnabled(): Boolean {
        return isSwipe
    }
}

3. ItemTouchHelper.Callback() 重要方法说明

3.1 getMovementFlags():Int{}
实现此方法获取移动的标志,方法内部需要调用makeMovementFlags(dragFlags, swipeFlags)方法设置拖拽和滑动的标志

该标志定义每个状态下启用的移动方向


 // 这个方法用于让RecyclerView拦截向上滑动,向下滑动,想左滑动
    // makeMovementFlags(dragFlags, swipeFlags); dragFlags显示项目支持拖动的方向  swipe标记物品可以滑动的方向  0 表示禁止
    //  网格布局:上下左右
    //  线性布局:上下/左右

    override fun getMovementFlags(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder
    ): Int {
        //拖动标记 0 表示禁止
        var dragFlags = 0
        //侧滑标记
        var swipeFlags = 0
        var layoytManager = recyclerView.layoutManager
        if (layoytManager is GridLayoutManager) {
            //网格布局  默认禁止侧滑
            if (viewHolder.adapterPosition != mAdapter.limitPosition) {
                dragFlags =ItemTouchHelper.LEFT or ItemTouchHelper.UP or ItemTouchHelper.RIGHT or ItemTouchHelper.DOWN
            }
            return makeMovementFlags(dragFlags, swipeFlags)
        } else if (layoytManager is LinearLayoutManager) {

            //处理 禁止滑动模块得逻辑  禁止移动得模块 禁止左右滑动
            if (viewHolder.adapterPosition != mAdapter.limitPosition) {
                dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
                swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
            }
            return makeMovementFlags(dragFlags, swipeFlags)
        }
        //默认 禁止滑动  禁止拖拽
        return makeMovementFlags(dragFlags, swipeFlags)

    }

3.2 makeMovementFlags(dragFlags, swipeFlags)

创建移动标志的便捷方法,通过此方法创建移动的标志.dragflag显示项目可以拖动的方向,swipe标记物品可以滑动的方向。返回由给定拖动和滑动标志组成的整数。

//源码
 /**
 
         * Convenience method to create movement flags.
         * 

* For instance, if you want to let your items be drag & dropped vertically and swiped * left to be dismissed, you can call this method with: * makeMovementFlags(UP | DOWN, LEFT); * * @param dragFlags The directions in which the item can be dragged. * @param swipeFlags The directions in which the item can be swiped. * @return Returns an integer composed of the given drag and swipe flags. */ public static int makeMovementFlags(int dragFlags, int swipeFlags) { return makeFlag(ACTION_STATE_IDLE, swipeFlags | dragFlags) | makeFlag(ACTION_STATE_SWIPE, swipeFlags) | makeFlag(ACTION_STATE_DRAG, dragFlags); }

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

Item拖动的项从其旧位置移动到新位置时调用onMove()方法,在这里我们可以处理移动数据的刷新(重新排序数据)

    /**
     * 拖拽移动得回调
     */
    override fun onMove(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        target: RecyclerView.ViewHolder
    ): Boolean {
        // 起始位置
        val fromPosition = viewHolder.adapterPosition
        // 结束位置
        val toPosition = target.adapterPosition
        // 固定位置  处理笃定位置不要移动
        if (fromPosition == mAdapter.limitPosition || toPosition == mAdapter.limitPosition) {
            return false
        }
        // 根据滑动方向 交换数据  for 循环不包含  toPosition  1 替代2     2替代3  3 替代4
        if (fromPosition < toPosition) {
            // 含头不含尾
            for (index in fromPosition until toPosition) {
                //交换集合得位置
                Collections.swap(mData, index, index + 1)
            }
        } else {
            // 含头不含尾
            for (index in fromPosition downTo toPosition + 1) {
                Collections.swap(mData, index, index - 1)
            }
        }
        // 刷新布局
        mAdapter.notifyItemMoved(fromPosition, toPosition)
        return true
    }
    

3.4 拖拽/滑动状态的处理

    /**
     * 选中会回调这里  处理选中布局的展示样式
     */
    override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {

        if (viewHolder == null) return
        //空闲状态
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            ViewCompat.animate(viewHolder!!.itemView).setDuration(100).scaleX(1.2F).scaleY(1.2F)
                .start()
        }
        super.onSelectedChanged(viewHolder, actionState)

    }


    /**
     * 滑动/拖拽结束 清理选中得状态  恢复布局的样式
     */
    override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
        // 恢复显示
        // 这里不能用if判断,因为GridLayoutManager是LinearLayoutManager的子类,改用when,类型推导有区别
        ViewCompat.animate(viewHolder.itemView).setDuration(100).scaleX(1F).scaleY(1F).start()
        super.clearView(recyclerView, viewHolder)
    }

注意:

actionState表示Item的状态

  • ItemTouchHelper.ACTION_STATE_IDLE 空闲状态。
  • ItemTouchHelper.ACTION_STATE_SWIPE 滑动状态。
  • ItemTouchHelper.ACTION_STATE_DRAG 拖拽状态。

3.5 滑动/拖拽的开关

  /**
     * 是否支持长按拖拽,默认true
     * 因为我们外部监听了长安处理操作所以 这里需要禁用没改掉
     */
    override fun isLongPressDragEnabled(): Boolean {
        return false
    }


    /**
     * 是否支持侧滑默认true(线性布局的时候外部可以传进来)
     */
    override fun isItemViewSwipeEnabled(): Boolean {
        return isSwipe
    }

总结

ItemTouchHelper是一个实用系统类,用于向RecyclerView添加滑动解除和拖放支持.通过这个类通过ItemTouchHelper.Callback() 回调帮助开发者实现了拖拽和滑动的效果

写作不易欢迎点赞

源码

DragRecycle源码

你可能感兴趣的:(Android RecycleView 实现拖拽和侧滑删除效果)