Android RecycleView 實(shí)現(xiàn)拖拽和側(cè)滑刪除效果

效果.gif

簡(jiǎn)介

項(xiàng)目需要做一個(gè)拖拽排序的需求(類似頭條欄目排序),原先隨意找了個(gè)三方庫(kù)簡(jiǎn)單的處理了一下.但是隨著項(xiàng)目的的迭代,越來(lái)越多的需求堆積下來(lái),三方庫(kù)不滿足自己定制的一些需求.所以決定自己寫(xiě)一寫(xiě)這個(gè)效果

思路:

RecycleView 實(shí)現(xiàn)列表樣式,ItemTouchHelper實(shí)現(xiàn)子條目的拖拽和側(cè)滑刪除.中間牽扯到指定條目禁止排序,禁止刪除的功能.

實(shí)現(xiàn)

1.頁(yè)面搭建

1.1 主頁(yè)面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<String>("北京", "河北", "河南", "山東", "天津", "陜西",
        "山西", "石家莊", "內(nèi)蒙古", "黑龍江", "吉林", "新疆", "西藏", "安徽", "湖北"
    )

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

    }

    private fun initView() {
        //設(shè)置布局選擇
        binding!!.btChange.setOnClickListener {
            when ( binding!!.rvContent.layoutManager) {
                is GridLayoutManager -> {
                    binding!!.rvContent.layoutManager = LinearLayoutManager(this)
                }
                else -> {
                    binding!!.rvContent.layoutManager = GridLayoutManager(this, 4)
                }
            }
        }

        //默認(rèn)網(wǎng)格布局
        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)

        //拖拽綁定的監(jiān)聽(tīng)器
        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) {
                //長(zhǎng)按開(kāi)啟拖拽
                itemTouchHelper.startDrag(holder)
            }
        })

    }
}

1.2 Adapter 代碼實(shí)現(xiàn)(注意默認(rèn)禁止操作Item的位置默認(rèn)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 禁止操作的條目位置 默認(rèn)第一個(gè)
 *
 */

class DragAdapter(mContext: Context,limitPosition:Int=0) : RecyclerView.Adapter<DragAdapterViewHolder>() {
    
    //上下文
    var mContext=mContext
    // 固定條目的位置
    val limitPosition = limitPosition
    //內(nèi)容數(shù)據(jù)
   private var contentList = ArrayList<String>()
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DragAdapterViewHolder {
        var binding = DataBindingUtil.inflate<ItemDragBinding>(
            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<String>) {
        contentList.addAll(items)
    }

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

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

    fun getItems():ArrayList<String>{
        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 拖拽的接口回調(diào)(主要實(shí)現(xiàn)邏輯)

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  拖拽的接口回調(diào)
 *
 *@param isSwipe  是否支持側(cè)滑  默認(rèn)支持
 *
 */

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

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

    // 這個(gè)方法用于讓RecyclerView攔截向上滑動(dòng),向下滑動(dòng),想左滑動(dòng)
    // makeMovementFlags(dragFlags, swipeFlags); dragFlags顯示項(xiàng)目支持拖動(dòng)的方向  swipe標(biāo)記物品可以滑動(dòng)的方向  0 表示禁止
    //  網(wǎng)格布局:上下左右
    //  線性布局:上下/左右

    override fun getMovementFlags(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder
    ): Int {
        //拖動(dòng)標(biāo)記 0 表示禁止
        var dragFlags = 0
        //側(cè)滑標(biāo)記
        var swipeFlags = 0
        var layoytManager = recyclerView.layoutManager
        if (layoytManager is GridLayoutManager) {
            //網(wǎng)格布局  默認(rèn)禁止側(cè)滑
            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) {

            //處理 禁止滑動(dòng)模塊得邏輯  禁止移動(dòng)得模塊 禁止左右滑動(dòng)
            if (viewHolder.adapterPosition != mAdapter.limitPosition) {
                dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
                swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
            }
            return makeMovementFlags(dragFlags, swipeFlags)
        }
        //默認(rèn) 禁止滑動(dòng)  禁止拖拽
        return makeMovementFlags(dragFlags, swipeFlags)

    }

    /**
     * 拖拽移動(dòng)得回調(diào)
     */
    override fun onMove(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        target: RecyclerView.ViewHolder
    ): Boolean {
        // 起始位置
        val fromPosition = viewHolder.adapterPosition
        // 結(jié)束位置
        val toPosition = target.adapterPosition
        // 固定位置  處理篤定位置不要移動(dòng)
        if (fromPosition == mAdapter.limitPosition || toPosition == mAdapter.limitPosition) {
            return false
        }
        // 根據(jù)滑動(dòng)方向 交換數(shù)據(jù)  for 循環(huán)不包含  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
    }

    /**
     * 滑動(dòng)結(jié)束得回調(diào) 滑動(dòng)刪除得邏輯
     */
    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        // direction 滑動(dòng)的狀態(tài)
        //ItemTouchHelper.START表示向左滑動(dòng)
        // ItemTouchHelper.END  向右邊滑動(dòng)
        val position = viewHolder.adapterPosition
        if (position != mAdapter.limitPosition) {
            //表示  禁止移動(dòng)得布局
            mData.removeAt(position)
            mAdapter.notifyItemRemoved(position)
        }

    }


    /**
     * 選中會(huì)回調(diào)這里  處理選中布局的展示樣式
     */
    override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {

        if (viewHolder == null) return
        //空閑狀態(tài)
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            ViewCompat.animate(viewHolder!!.itemView).setDuration(100).scaleX(1.2F).scaleY(1.2F)
                .start()
        }
        super.onSelectedChanged(viewHolder, actionState)

    }


    /**
     * 滑動(dòng)結(jié)束 清理選中得狀態(tài)  恢復(fù)布局的樣式
     */
    override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
        // 恢復(fù)顯示
        // 這里不能用if判斷,因?yàn)镚ridLayoutManager是LinearLayoutManager的子類,改用when,類型推導(dǎo)有區(qū)別
        ViewCompat.animate(viewHolder.itemView).setDuration(100).scaleX(1F).scaleY(1F).start()
        super.clearView(recyclerView, viewHolder)
    }


    /**
     * 是否支持長(zhǎng)按拖拽,默認(rèn)true
     * 因?yàn)槲覀兺獠勘O(jiān)聽(tīng)了長(zhǎng)安處理操作所以 這里需要禁用沒(méi)改掉
     */
    override fun isLongPressDragEnabled(): Boolean {
        return false
    }


    /**
     * 是否支持側(cè)滑默認(rèn)true(線性布局的時(shí)候外部可以傳進(jìn)來(lái))
     */
    override fun isItemViewSwipeEnabled(): Boolean {
        return isSwipe
    }
}

3. ItemTouchHelper.Callback() 重要方法說(shuō)明

3.1 getMovementFlags():Int{}
實(shí)現(xiàn)此方法獲取移動(dòng)的標(biāo)志,方法內(nèi)部需要調(diào)用makeMovementFlags(dragFlags, swipeFlags)方法設(shè)置拖拽和滑動(dòng)的標(biāo)志

該標(biāo)志定義每個(gè)狀態(tài)下啟用的移動(dòng)方向


 // 這個(gè)方法用于讓RecyclerView攔截向上滑動(dòng),向下滑動(dòng),想左滑動(dòng)
    // makeMovementFlags(dragFlags, swipeFlags); dragFlags顯示項(xiàng)目支持拖動(dòng)的方向  swipe標(biāo)記物品可以滑動(dòng)的方向  0 表示禁止
    //  網(wǎng)格布局:上下左右
    //  線性布局:上下/左右

    override fun getMovementFlags(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder
    ): Int {
        //拖動(dòng)標(biāo)記 0 表示禁止
        var dragFlags = 0
        //側(cè)滑標(biāo)記
        var swipeFlags = 0
        var layoytManager = recyclerView.layoutManager
        if (layoytManager is GridLayoutManager) {
            //網(wǎng)格布局  默認(rèn)禁止側(cè)滑
            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) {

            //處理 禁止滑動(dòng)模塊得邏輯  禁止移動(dòng)得模塊 禁止左右滑動(dòng)
            if (viewHolder.adapterPosition != mAdapter.limitPosition) {
                dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
                swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
            }
            return makeMovementFlags(dragFlags, swipeFlags)
        }
        //默認(rèn) 禁止滑動(dòng)  禁止拖拽
        return makeMovementFlags(dragFlags, swipeFlags)

    }

3.2 makeMovementFlags(dragFlags, swipeFlags)

創(chuàng)建移動(dòng)標(biāo)志的便捷方法,通過(guò)此方法創(chuàng)建移動(dòng)的標(biāo)志.dragflag顯示項(xiàng)目可以拖動(dòng)的方向,swipe標(biāo)記物品可以滑動(dòng)的方向。返回由給定拖動(dòng)和滑動(dòng)標(biāo)志組成的整數(shù)。

//源碼
 /**
 
         * Convenience method to create movement flags.
         * <p>
         * 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:
         * <code>makeMovementFlags(UP | DOWN, LEFT);</code>
         *
         * @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拖動(dòng)的項(xiàng)從其舊位置移動(dòng)到新位置時(shí)調(diào)用onMove()方法,在這里我們可以處理移動(dòng)數(shù)據(jù)的刷新(重新排序數(shù)據(jù))

    /**
     * 拖拽移動(dòng)得回調(diào)
     */
    override fun onMove(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        target: RecyclerView.ViewHolder
    ): Boolean {
        // 起始位置
        val fromPosition = viewHolder.adapterPosition
        // 結(jié)束位置
        val toPosition = target.adapterPosition
        // 固定位置  處理篤定位置不要移動(dòng)
        if (fromPosition == mAdapter.limitPosition || toPosition == mAdapter.limitPosition) {
            return false
        }
        // 根據(jù)滑動(dòng)方向 交換數(shù)據(jù)  for 循環(huán)不包含  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 拖拽/滑動(dòng)狀態(tài)的處理

    /**
     * 選中會(huì)回調(diào)這里  處理選中布局的展示樣式
     */
    override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {

        if (viewHolder == null) return
        //空閑狀態(tài)
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            ViewCompat.animate(viewHolder!!.itemView).setDuration(100).scaleX(1.2F).scaleY(1.2F)
                .start()
        }
        super.onSelectedChanged(viewHolder, actionState)

    }


    /**
     * 滑動(dòng)/拖拽結(jié)束 清理選中得狀態(tài)  恢復(fù)布局的樣式
     */
    override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
        // 恢復(fù)顯示
        // 這里不能用if判斷,因?yàn)镚ridLayoutManager是LinearLayoutManager的子類,改用when,類型推導(dǎo)有區(qū)別
        ViewCompat.animate(viewHolder.itemView).setDuration(100).scaleX(1F).scaleY(1F).start()
        super.clearView(recyclerView, viewHolder)
    }

注意:

actionState表示Item的狀態(tài)

  • ItemTouchHelper.ACTION_STATE_IDLE 空閑狀態(tài)。
  • ItemTouchHelper.ACTION_STATE_SWIPE 滑動(dòng)狀態(tài)。
  • ItemTouchHelper.ACTION_STATE_DRAG 拖拽狀態(tài)。

3.5 滑動(dòng)/拖拽的開(kāi)關(guān)

  /**
     * 是否支持長(zhǎng)按拖拽,默認(rèn)true
     * 因?yàn)槲覀兺獠勘O(jiān)聽(tīng)了長(zhǎng)安處理操作所以 這里需要禁用沒(méi)改掉
     */
    override fun isLongPressDragEnabled(): Boolean {
        return false
    }


    /**
     * 是否支持側(cè)滑默認(rèn)true(線性布局的時(shí)候外部可以傳進(jìn)來(lái))
     */
    override fun isItemViewSwipeEnabled(): Boolean {
        return isSwipe
    }

總結(jié)

ItemTouchHelper是一個(gè)實(shí)用系統(tǒng)類,用于向RecyclerView添加滑動(dòng)解除和拖放支持.通過(guò)這個(gè)類通過(guò)ItemTouchHelper.Callback() 回調(diào)幫助開(kāi)發(fā)者實(shí)現(xiàn)了拖拽和滑動(dòng)的效果

寫(xiě)作不易歡迎點(diǎn)贊

源碼

DragRecycle源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容