草庐IT

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

没有了遇见 2023-09-24 原文
效果.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<String>("北京", "河北", "河南", "山东", "天津", "陕西",
        "山西", "石家庄", "内蒙古", "黑龙江", "吉林", "新疆", "西藏", "安徽", "湖北"
    )

    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() {
        //设置布局选择
        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<DragAdapterViewHolder>() {
    
    //上下文
    var mContext=mContext
    // 固定条目的位置
    val limitPosition = limitPosition
    //内容数据
   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 拖拽的接口回调(主要实现逻辑)

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.
         * <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拖动的项从其旧位置移动到新位置时调用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 实现拖拽和侧滑删除效果的更多相关文章

  1. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  2. ruby - 我可以使用 Ruby 从 CSV 中删除列吗? - 2

    查看Ruby的CSV库的文档,我非常确定这是可能且简单的。我只需要使用Ruby删除CSV文件的前三列,但我没有成功运行它。 最佳答案 csv_table=CSV.read(file_path_in,:headers=>true)csv_table.delete("header_name")csv_table.to_csv#=>ThenewCSVinstringformat检查CSV::Table文档:http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV/Table.html

  3. ruby - 我可以使用 aws-sdk-ruby 在 AWS S3 上使用事务性文件删除/上传吗? - 2

    我发现ActiveRecord::Base.transaction在复杂方法中非常有效。我想知道是否可以在如下事务中从AWSS3上传/删除文件:S3Object.transactiondo#writeintofiles#raiseanexceptionend引发异常后,每个操作都应在S3上回滚。S3Object这可能吗?? 最佳答案 虽然S3API具有批量删除功能,但它不支持事务,因为每个删除操作都可以独立于其他操作成功/失败。该API不提供任何批量上传功能(通过PUT或POST),因此每个上传操作都是通过一个独立的API调用完成的

  4. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  5. ruby - 如何安全地删除文件? - 2

    在Ruby中是否有Gem或安全删除文件的方法?我想避免系统上可能不存在的外部程序。“安全删除”指的是覆盖文件内容。 最佳答案 如果您使用的是*nix,一个很好的方法是使用exec/open3/open4调用shred:`shred-fxuz#{filename}`http://www.gnu.org/s/coreutils/manual/html_node/shred-invocation.html检查这个类似的帖子:Writingafileshredderinpythonorruby?

  6. ruby-on-rails - 标准化文件名的字符串,删除重音和特殊字符 - 2

    我正在尝试找到一种方法来规范化字符串以将其作为文件名传递。到目前为止我有这个:my_string.mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/n,'').downcase.gsub(/[^a-z]/,'_')但第一个问题:-字符。我猜这个方法还有更多问题。我不控制名称,名称字符串可以有重音符、空格和特殊字符。我想删除所有这些,用相应的字母('é'=>'e')替换重音符号,并将其余的替换为'_'字符。名字是这样的:“Prélèvements-常规”“健康证”...我希望它们像一个没有空格/特殊字符的文件名:“prelevements_routin

  7. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  8. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  9. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  10. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

随机推荐