带有滑动动作的列表元素-按钮不可点击 [英] List element with swipe actions - buttons not clickable

查看:96
本文介绍了带有滑动动作的列表元素-按钮不可点击的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个列表项,其中包含三个滑动动作,如下所示:

I have a list item with three swipe actions which looks like this:

常规列表项和按钮是xml中定义的两种不同的布局.

The regular list item and the buttons are two different layouts defined in xml.

要显示按钮操作,我使用 ItemTouchHelper.SimpleCallback .在 onChildDraw 中,我告诉项目列表项目的x轴只能绘制到达到按钮控件的宽度为止.

To reveal the button actions I use ItemTouchHelper.SimpleCallback. In onChildDraw I tell the item list item's x-axis to be only drawn until it reaches the width of the button controls.

override fun onChildDraw(
    c: Canvas,
    recyclerView: RecyclerView,
    viewHolder: RecyclerView.ViewHolder,
    dX: Float,
    dY: Float,
    actionState: Int,
    isCurrentlyActive: Boolean
) {
    val foreground = (viewHolder as? NachrichtViewHolder)?.binding?.nachrichtListItem
    val background = (viewHolder as? NachrichtViewHolder)?.binding?.background

    val x: Float = when {
        dX.absoluteValue > background?.measuredWidth?.toFloat() ?: dX -> background?.measuredWidth?.toFloat()
            ?.unaryMinus() ?: dX
        else -> dX
    }

    getDefaultUIUtil().onDraw(
        c,
        recyclerView,
        foreground,
        x,
        dY,
        actionState,
        isCurrentlyActive
    )
}

这是一个简短的布局文件,展示了我构建ui的方式:

Here is an abbreviated layout file demonstrating the way I built the ui:

<FrameLayout
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/background"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="end"
        android:clickable="@{backgroundVisible}"
        android:focusable="@{backgroundVisible}"
        android:focusableInTouchMode="@{backgroundVisible}"
        android:elevation="@{backgroundVisible ? 4 : 0}">
    
        <ImageButton
            android:id="@+id/actionReply"/>
    
        <ImageButton
            android:id="@+id/actionShare"/>
    
        <ImageButton
            android:id="@+id/actionDelete"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/nachrichtListItem"
        android:elevation="@{backgroundVisible ? 0 : 4}"
        android:clickable="@{!backgroundVisible}"
        android:focusable="@{!backgroundVisible}"
        android:focusableInTouchMode="@{!backgroundVisible}">
    
        <!-- regular list item -->
    
    </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

我的问题是按钮不可单击.到目前为止,我尝试过的事情:

My problem is that the buttons are not clickable. What I tried so far:

  • 设置高度以将元素置于顶部
  • 根据按钮的可见性状态设置可点击的项目

这可以在布局文件中看到.我想在xml中定义元素,并且在可能的情况下不要手动绘制它们.

This can be seen in the layout file. I want to define the elements inside xml and not draw them manually if possible.

推荐答案

问题已解决. ItemTouchHelper.SimpleCallback 吞噬了您所有的触摸事件.因此,您需要为按钮注册 TouchListener .就我而言,这些按钮来自xml.受的启发,我想出了以下解决方案:

The problem is solved. ItemTouchHelper.SimpleCallback swallows all your touch events. So you need to register a TouchListener for the buttons. The buttons come in my case from xml. Inspired by this I came up with the following solution:

@SuppressLint("ClickableViewAccessibility")
class NachrichtItemSwipeCallback(private val recyclerView: RecyclerView) :
    ItemTouchHelper.SimpleCallback(0, LEFT) {
    private val itemTouchHelper: ItemTouchHelper

    private var binding: ListItemNachrichtBinding? = null
    private var lastSwipedPosition: Int = -1

    init {
        // Disable animations as they don't work with custom list actions
        (this.recyclerView.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false

        this.recyclerView.setOnTouchListener { _, touchEvent ->
            if (lastSwipedPosition < 0) return@setOnTouchListener false
            if (touchEvent.action == MotionEvent.ACTION_DOWN) {
                val viewHolder =
                    this.recyclerView.findViewHolderForAdapterPosition(lastSwipedPosition)
                val swipedItem: View = viewHolder?.itemView ?: return@setOnTouchListener false
                val rect = Rect()
                swipedItem.getGlobalVisibleRect(rect)

                val point = Point(touchEvent.rawX.toInt(), touchEvent.rawY.toInt())
                if (rect.top < point.y && rect.bottom > point.y) {
                    // Consume touch event directly
                    val buttons =
                        binding?.buttonActionBar?.children
                            .orEmpty()
                            .filter { it.isClickable }
                            .toList()

                    val consumed = consumeTouchEvents(buttons, point.x, point.y)
                    if (consumed) {
                        animateClosing(binding?.nachrichtListItem)
                    }
                    return@setOnTouchListener false
                }
            }
            return@setOnTouchListener false
        }

        this.itemTouchHelper = ItemTouchHelper(this)
        this.itemTouchHelper.attachToRecyclerView(this.recyclerView)
    }

    // Only for drag & drop functionality
    override fun onMove(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        target: RecyclerView.ViewHolder
    ): Boolean = false

    override fun onChildDraw(
        canvas: Canvas,
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        dX: Float,
        dY: Float,
        actionState: Int,
        isCurrentlyActive: Boolean
    ) {
        binding = (viewHolder as? NachrichtViewHolder)?.binding
        val foreground = binding?.nachrichtListItem
        val background = binding?.buttonActionBar

        val backgroundWidth = background?.measuredWidth?.toFloat()

        // only draw until start of action buttons
        val x: Float = when {
            dX.absoluteValue > backgroundWidth ?: dX -> backgroundWidth?.unaryMinus() ?: dX
            else -> dX
        }

        foreground?.translationX = x
    }

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        this.lastSwipedPosition = viewHolder.adapterPosition
        recyclerView.adapter?.notifyItemChanged(this.lastSwipedPosition)
    }

    private fun animateClosing(
        foreground: ConstraintLayout?
    ) {
        foreground ?: return
        ObjectAnimator.ofFloat(foreground, "translationX", 0f).apply {
            duration = DURATION_ANIMATION
            start()
        }.doOnEnd { applyUiWorkaround() }
    }

    // See more at https://stackoverflow.com/a/37342327/3734116
    private fun applyUiWorkaround() {
        itemTouchHelper.attachToRecyclerView(null)
        itemTouchHelper.attachToRecyclerView(recyclerView)
    }

    private fun consumeTouchEvents(
        views: List<View?>,
        x: Int,
        y: Int
    ): Boolean {
        views.forEach { view: View? ->
            val viewRect = Rect()
            view?.getGlobalVisibleRect(viewRect)

            if (viewRect.contains(x, y)) {
                view?.performClick()
                return true
            }
        }
        return false
    }

    companion object {
        private const val DURATION_ANIMATION: Long = 250
    }
}

这篇关于带有滑动动作的列表元素-按钮不可点击的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆