
簡(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)贊