使用 ItemTouchHelper 时,如何在拖动时取消对 RecyclerView 中项目的拖动?

2024-03-12

背景

我试图拥有一个具有不同视图类型的 RecyclerView,但具有拖放功能以及单击和长按操作的能力。

它与“电话”应用程序类似,您可以在其中更改收藏夹项目的顺序。在“电话”应用程序上,当您长按某个项目时,会立即出现上下文菜单,如果继续拖动,上下文菜单就会消失。

然而,在这种情况下,我需要做相反的事情。长按时,如果用户在很短的时间内没有拖动,或者用户停止长按而不拖动,我们会在屏幕上显示一个对话框,并且我需要停止拖动过程。

问题

虽然我成功地处理了长触摸机制,并且在这些特殊情况下显示了一个对话框,但我未能使拖动停止。

这意味着,如果用户在对话框出现后继续触摸屏幕,仍然可以继续拖动:

完整代码可用here https://github.com/AndroidDeveloperLB/RecyclerViewDragAndDropTest(没有可用长触摸行为的代码here https://stackoverflow.com/a/57204561/878126),但主要代码如下:

class MainActivity : AppCompatActivity() {
    sealed class Item(val id: Long, val itemType: Int) {
        class HeaderItem(id: Long) : Item(id, ITEM_TYPE_HEADER)
        class NormalItem(id: Long, val data: Long) : Item(id, 1)
    }

    enum class ItemActionState {
        IDLE, LONG_TOUCH_OR_SOMETHING_ELSE, DRAG, SWIPE, HANDLED_LONG_TOUCH
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)
        val items = ArrayList<Item>(100)
        var itemDataCounter = 0L
        items.add(Item.HeaderItem(0L))
        for (i in 0 until 100) {
            items.add(Item.NormalItem(itemDataCounter, itemDataCounter))
            ++itemDataCounter
        }
        val gridLayoutManager = recyclerView.layoutManager as GridLayoutManager
        gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
            override fun getSpanSize(position: Int): Int {
                return when (recyclerView.adapter!!.getItemViewType(position)) {
                    ITEM_TYPE_HEADER -> gridLayoutManager.spanCount
                    ITEM_TYPE_NORMAL -> 1
                    else -> throw Exception("unknown item type")
                }
            }
        }
        recyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
            init {
                setHasStableIds(true)
            }

            override fun getItemViewType(position: Int): Int = items[position].itemType

            override fun getItemId(position: Int): Long = items[position].id

            override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
                val view = when (viewType) {
                    ITEM_TYPE_HEADER -> LayoutInflater.from(parent.context).inflate(R.layout.header_item, parent, false)
                    ITEM_TYPE_NORMAL -> LayoutInflater.from(parent.context).inflate(R.layout.grid_item, parent, false)
                    else -> throw Exception("unknown item type")
                }
                return object : RecyclerView.ViewHolder(view) {}
            }

            override fun getItemCount() = items.size

            override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
                when (getItemViewType(position)) {
                    ITEM_TYPE_NORMAL -> {
                        val data = (items[position] as Item.NormalItem).data
                        holder.itemView.setBackgroundColor(when (data % 4L) {
                            0L -> 0xffff0000.toInt()
                            1L -> 0xffffff00.toInt()
                            2L -> 0xff00ff00.toInt()
                            else -> 0xff00ffff.toInt()
                        })
                        holder.itemView.textView.text = "item $data"
                    }
                    ITEM_TYPE_HEADER -> {
                    }
                    else -> throw Exception("unknown item type")
                }
            }
        }
        val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
            val touchSlop = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1f, resources.displayMetrics)
            //            val touchSlop = ViewConfiguration.get(this@MainActivity).scaledTouchSlop
            val longTouchTimeout = ViewConfiguration.getLongPressTimeout() * 2
            var touchState: ItemActionState = ItemActionState.IDLE
            var lastViewHolderPosHandled: Int? = null
            val handler = Handler()
            val longTouchRunnable = Runnable {
                if (lastViewHolderPosHandled != null && touchState == ItemActionState.LONG_TOUCH_OR_SOMETHING_ELSE) {
                    //                    Log.d("AppLog", "timer timed out to trigger long touch")
                    onItemLongTouch(lastViewHolderPosHandled!!)
                }
            }

            private fun onItemLongTouch(pos: Int) {
                //                Log.d("AppLog", "longTouchTimeout:$longTouchTimeout")
                val item = items[pos] as Item.NormalItem
                //                Toast.makeText(this@MainActivity, "long touch on :$pos ", Toast.LENGTH_SHORT).show()
                AlertDialog.Builder(this@MainActivity).setTitle("long touch").setMessage("long touch on pos: $pos - item ${item.data}").show()
                touchState = ItemActionState.HANDLED_LONG_TOUCH
                lastViewHolderPosHandled = null
                handler.removeCallbacks(longTouchRunnable)
            }

            override fun onChildDrawOver(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder?, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
                super.onChildDrawOver(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
                //                Log.d("AppLog", "onChildDrawOver $dX $dY pos:${viewHolder?.adapterPosition} actionState:$actionState isCurrentlyActive:$isCurrentlyActive")
                if (touchState == ItemActionState.LONG_TOUCH_OR_SOMETHING_ELSE && (dX >= touchSlop || dY >= touchSlop)) {
                    lastViewHolderPosHandled = null
                    handler.removeCallbacks(longTouchRunnable)
                    touchState = if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) ItemActionState.DRAG else ItemActionState.SWIPE
                    Log.d("AppLog", "decided it's not a long touch, but $touchState instead")
                }
            }

            override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
                super.onSelectedChanged(viewHolder, actionState)
                //                Log.d("AppLog", "onSelectedChanged adapterPosition: ${viewHolder?.adapterPosition} actionState:$actionState")
                when (actionState) {
                    ItemTouchHelper.ACTION_STATE_IDLE -> {
                        //user finished drag or long touch
                        if (touchState == ItemActionState.LONG_TOUCH_OR_SOMETHING_ELSE)
                            onItemLongTouch(lastViewHolderPosHandled!!)
                        touchState = ItemActionState.IDLE
                        handler.removeCallbacks(longTouchRunnable)
                        lastViewHolderPosHandled = null
                    }
                    ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.ACTION_STATE_SWIPE -> {
                        if (touchState == ItemActionState.IDLE) {
                            lastViewHolderPosHandled = viewHolder!!.adapterPosition
                            //                            Log.d("AppLog", "setting timer to trigger long touch")
                            handler.removeCallbacks(longTouchRunnable)
                            //started as long touch, but could also be dragging or swiping ...
                            touchState = ItemActionState.LONG_TOUCH_OR_SOMETHING_ELSE
                            handler.postDelayed(longTouchRunnable, longTouchTimeout.toLong())
                        }
                    }
                }
            }

            override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
                //                Log.d("AppLog", "onMove")
                if (touchState == ItemActionState.LONG_TOUCH_OR_SOMETHING_ELSE) {
                    lastViewHolderPosHandled = null
                    handler.removeCallbacks(longTouchRunnable)
                    touchState = ItemActionState.DRAG
                }
                if (viewHolder.itemViewType != target.itemViewType)
                    return false
                val fromPosition = viewHolder.adapterPosition
                val toPosition = target.adapterPosition
                //                val item = items.removeAt(fromPosition)
                //                recyclerView.adapter!!.notifyItemRemoved(fromPosition)
                //                items.add(toPosition, item)
                //                recyclerView.adapter!!.notifyItemInserted(toPosition)
                Collections.swap(items, fromPosition, toPosition)
                recyclerView.adapter!!.notifyItemMoved(fromPosition, toPosition)
                //                recyclerView.adapter!!.notifyDataSetChanged()
                return true
            }

            override fun isLongPressDragEnabled(): Boolean = true

            override fun isItemViewSwipeEnabled(): Boolean = false

            override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
                if (viewHolder.itemViewType == ITEM_TYPE_HEADER)
                    return makeMovementFlags(0, 0)
                //                Log.d("AppLog", "getMovementFlags")
                val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
                val swipeFlags = if (isItemViewSwipeEnabled) ItemTouchHelper.START or ItemTouchHelper.END else 0
                return makeMovementFlags(dragFlags, swipeFlags)
            }

            override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
                if (touchState == ItemActionState.LONG_TOUCH_OR_SOMETHING_ELSE) {
                    lastViewHolderPosHandled = null
                    handler.removeCallbacks(longTouchRunnable)
                    touchState = ItemActionState.DRAG
                }
                val position = viewHolder.adapterPosition
                items.removeAt(position)
                recyclerView.adapter!!.notifyItemRemoved(position)
            }
        })
        itemTouchHelper.attachToRecyclerView(recyclerView)
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_main, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        var url: String? = null
        when (item.itemId) {
            R.id.menuItem_all_my_apps -> url = "https://play.google.com/store/apps/developer?id=AndroidDeveloperLB"
            R.id.menuItem_all_my_repositories -> url = "https://github.com/AndroidDeveloperLB"
            R.id.menuItem_current_repository_website -> url = "https://github.com/AndroidDeveloperLB/RecyclerViewDragAndDropTest"
        }
        if (url == null)
            return true
        val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
        @Suppress("DEPRECATION")
        intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
        startActivity(intent)
        return true
    }

    companion object {
        const val ITEM_TYPE_HEADER = 0
        const val ITEM_TYPE_NORMAL = 1
    }
}

我尝试过的

我尝试查看 RecyclerView 和 ItemTouchHelper 的所有文档。还尝试在这里和互联网上寻找类似的问题。

我看不到任何方法来告诉拖动机制:“我现在已经完成拖动,取消拖动”。

问题

如何取消由 ItemTouchHelper 发起和维护的拖动?


覆盖isLongPressDragEnabled方法与false:

override fun isLongPressDragEnabled() :Boolean {
    return false;
}

Source

https://android.googlesource.com/platform/frameworks/support/+/c045910/v7/recyclerview/src/android/support/v7/widget/helper/ItemTouchHelper.java https://android.googlesource.com/platform/frameworks/support/+/c045910/v7/recyclerview/src/android/support/v7/widget/helper/ItemTouchHelper.java

/**
     * Starts dragging the provided ViewHolder. By default, ItemTouchHelper starts a drag when a
     * View is long pressed. You can disable that behavior via
     * {@link ItemTouchHelper.Callback#isLongPressDragEnabled()}.
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 ItemTouchHelper 时,如何在拖动时取消对 RecyclerView 中项目的拖动? 的相关文章

  • Android TCP Server 仅在客户端关闭后显示来自 Python 客户端的消息

    我正在使用一些示例代码 它允许我从 Python 客户端向 Android 服务器 TCP 发送消息 但该消息仅在客户端关闭后才会显示在 Android 模拟器上 我可能缺少 tcp 套接字背后的一些基本理解 第一次使用和实现 我的主要目的
  • 我所有的布局 xml 文件都变成了自动生成的文件

    昨天我的应用程序上的所有内容都运行完美 但今天当我打开 Android Studio 时 所有 xml 文件都已损坏 不确定这是否是正确的术语 每个人都是这样的 我今天遇到了同样的问题 下面是我所做的几个步骤 我取得了成功来解决这个问题 只
  • AlarmManager setInexactRepeating、setWindow、setRepeating 方法在工作日内循环调用时不会触发警报

    要求 我需要在一周中选定的几天以及警报开始的日期发出警报 例如 我想从 2017 年 5 月 26 日开始 在每周的周五和周六下午 6 45 发出警报 问题 广播接收器onReceive AlarmManager 时方法不执行setInex
  • 无法解析符号“AndroidJUnit4”

    显然我需要正确的导入语句来解决这个问题 根据文档用于AndroidJUnit4 http developer android com reference android support test runner AndroidJUnit4 h
  • 为 DownloadManager 的 BroadcastReceiver 设置附加功能 [重复]

    这个问题在这里已经有答案了 有一种方法可以添加额外内容DownloadManager已登记行动意图DownloadManager ACTION DOWNLOAD COMPLETE 例如 接收一个在意图中设置为额外的布尔值 这就是我创建请求的
  • 如何使用 SyncAdapter 处理远程服务器的 RESTful 更新

    我观看了 Google I O REST 演讲并阅读了幻灯片 http www google com events io 2010 sessions developing RESTful android apps html http www
  • android widget 和 localservice 绑定

    我编写播放器 它的主要活动是运行本地服务 我找不到如何将本地服务绑定到小部件 当我尝试像在活动中一样绑定它时 它失败了 请帮助我 添加 1 何时可以联系后台服务 http www developer com ws data article
  • 使用选项卡式活动中的捆绑包将值从活动传递到片段

    我是一个java文盲 但仍在尝试开发一个供我个人使用的应用程序 我从 android studio 的 Tabbed Activity 开始 除了 MainActivity 中的一个片段和一个包之外 大部分没有改变 这是我的代码 主要活动
  • Android NumberPicker 带字符串

    I have customised the NumberPicker to show text The output is this 当我按 确定 时 我想将 e x 鼠标添加到我的列表 文章 中 我得到的是索引值 int 它由 array
  • 指标元素之间的空间

    如何增加 减少指标元素之间的空间ViewPagerIndicator 我用过CirclePageIndicator 我能够通过以下步骤在两个指标之间留出更多空间 打开源代码CirclePageIndicator并找到变量mRadius 在第
  • 安卓市场。 Google Checkout 和银行帐户

    请原谅 这不是一个编程问题 但它仍然与软件开发有关 所以我希望它没问题 为付费应用创建 Android 开发者帐户意味着注册一个 GoogleCheckout 帐户 这又意味着将其链接到来自这 31 个符合条件的国家 地区之一的银行帐户 有
  • Android BLE - 如何分块读取大特征值(使用偏移量)?

    我正在使用 Android SDKandroid 蓝牙 and android 蓝牙 le APIs 我想实现一个应用程序 发挥核心作用 并连接到 BLE 外设以读取特征值和描述符 应用程序需要读取的特征值较大 因此需要分块连续读取 我对如
  • 发送 OneSignal 推送通知时 Android 应用程序崩溃

    Android 应用程序在发送信号推送通知后立即崩溃 我收到这个错误 尝试查看其他 stackoverflow 答案 但没有帮助 请检查下面的代码 build gradle 和错误 不明白为什么它不起作用 P 请检查下面的代码 build
  • 颤动附近的连接

    当我尝试在设备上做广告或发现时 我收到此错误 但是前一天在环路上效果很好 PlatformException Failure 17 API Nearby CONNECTIONS API is not available on this de
  • android:widgetLayout 和 android:layout 之间的区别?

    我得到一些奇怪的配置 其中 widgetLayout 配置列表项的内部空间 而布局配置整个项目列表和屏幕背景 有人能真正解释一下什么是 widgetLayout 吗 android layout 整个首选项的布局 包括标题 摘要和小部件 a
  • 使用 Asp.Net 的 GCM 推送通知

    正如您可能已经看到的 Google 正在迁移其推送通知系统 http developer android com guide google gcm c2dm html http developer android com guide goo
  • 如何在将数据发送到 Firebase 数据库之前对其进行加密?

    我正在使用 Firebase 实时数据库制作聊天应用程序 我知道 Firebase 非常安全 只要您的规则正确 但我自己可以阅读使用我的应用程序的人的所有聊天记录 我想阻止这种情况 为此我需要一种解密和加密方法 我尝试使用凯撒解密 但失败了
  • Android - 如何合并两个视频

    基本上 我正在寻找一种将两个 mp4 视频文件 在 SD 卡上 组合在一起的方法 更像是在第一个视频的末尾附加第二个视频 我进行了很多搜索 但找不到合适的解决方案 好吧 我根本找不到任何解决方案 所以我的问题是 是否有一个库可以组合 并可能
  • Android 中的字符串加密

    我正在使用代码进行加密和加密 它没有给出字符串结果 字节数组未转换为字符串 我几乎尝试了所有方法将字节数组转换为字符 但没有给出结果 public class EncryptionTest extends Activity EditText
  • 如何在android中获取当前一周的所有天数?

    我想在字符串数组中获取本周的所有日期 我怎样才能做到这一点 提前致谢 I think你想要这样的东西 假设你总是想要从星期一开始的几周 以及 MM dd yyyy 的日期格式 DateFormat format new SimpleDate

随机推荐