【问题标题】:List element with swipe actions - buttons not clickable具有滑动操作的列表元素 - 按钮不可点击
【发布时间】:2020-07-20 13:14:01
【问题描述】:

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

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

为了显示我使用ItemTouchHelper.SimpleCallback 的按钮操作。在onChildDraw 中,我告诉项目列表项的 x 轴仅在达到按钮控件的宽度之前绘制。

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 的方式:

<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>

我的问题是按钮不可点击。 到目前为止我尝试了什么:

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

这可以在布局文件中看到。我想在 xml 中定义元素,如果可能的话不要手动绘制它们。

【问题讨论】:

    标签: android android-recyclerview swipe-gesture itemtouchhelper


    【解决方案1】:

    问题解决了。 ItemTouchHelper.SimpleCallback 吞下你所有的触摸事件。所以你需要为按钮注册一个TouchListener。在我的情况下,按钮来自 xml。受到this 的启发,我想出了以下解决方案:

    @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
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-12-15
      • 1970-01-01
      • 2018-04-27
      • 2019-02-04
      • 1970-01-01
      • 1970-01-01
      • 2012-06-25
      相关资源
      最近更新 更多