Android 具有滑动操作的列表元素-按钮不可单击
我有一个列表项,其中有三个滑动动作,如下所示: 常规列表项和按钮是xml中定义的两种不同布局 要显示按钮操作,我使用Android 具有滑动操作的列表元素-按钮不可单击,android,android-recyclerview,swipe-gesture,itemtouchhelper,Android,Android Recyclerview,Swipe Gesture,Itemtouchhelper,我有一个列表项,其中有三个滑动动作,如下所示: 常规列表项和按钮是xml中定义的两种不同布局 要显示按钮操作,我使用ItemTouchHelper.SimpleCallback。在onchildraw中,我告诉项目列表项目的x轴仅在达到按钮控件的宽度之前绘制 override fun onChildDraw( c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, d
ItemTouchHelper.SimpleCallback
。在onchildraw
中,我告诉项目列表项目的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中定义元素,如果可能的话,不要手动绘制它们。问题解决了
ItemTouchHelper.SimpleCallback
吞噬所有触摸事件。因此,您需要为按钮注册一个touchstener
。我的例子中的按钮来自xml。受此启发,我提出了以下解决方案:
@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
}
}
@suppressint(“ClickableViewAccessibility”)
NachrichtItemSwipeCallback类(私有val recyclerView:recyclerView):
ItemTouchHelper.SimpleCallback(0,左){
private val itemTouchHelper:itemTouchHelper
私有变量绑定:ListItemNachRchtBinding?=null
私有变量lastSwipedPosition:Int=-1
初始化{
//禁用动画,因为它们不适用于自定义列表操作
(this.recyclerView.itemAnimator作为?SimpleItemAnimator)?.SupportsAngeAnimations=false
this.recyclerView.setOnTouchListener{{uu0,touchEvent->
如果(上次开关位置<0)return@setOnTouchListener假的
if(touchEvent.action==MotionEvent.action\u向下){
val视窗座=
此.recyclerView.findViewHolderForAdapterPosition(最后一个SwipedPosition)
val SwipeItem:View=viewHolder?.itemView?:return@setOnTouchListener假的
val rect=rect()
SwipeItem.getGlobalVisibleRect(rect)
val point=point(touchEvent.rawX.toInt(),touchEvent.rawY.toInt())
if(rect.toppoint.y){
//直接消费触摸事件
val按钮=
绑定?.buttonActionBar?.children
.orEmpty()
.filter{it.isClickable}
托利斯先生()
val consumed=consumeTouchEvents(按钮、点x、点y)
如果(消耗){
动画关闭(绑定?.nachrichtListItem)
}
return@setOnTouchListener假的
}
}
return@setOnTouchListener假的
}
this.itemTouchHelper=itemTouchHelper(this)
this.itemTouchHelper.attachToRecyclerView(this.recyclerView)
}
//仅用于拖放功能
爱的乐趣(
回收视图:回收视图,
viewHolder:RecyclerView.viewHolder,
目标:RecyclerView.ViewHolder
):Boolean=false
在Childraw上覆盖乐趣(
画布:画布,
回收视图:回收视图,
viewHolder:RecyclerView.viewHolder,
dX:浮动,
戴:浮球,
actionState:Int,
isCurrentlyActive:Boolean
) {
绑定=(视图持有者为?NachrichtViewHolder)?。绑定
val前台=绑定?.nachrichtListItem
val背景=绑定?.buttonActionBar
val backgroundWidth=背景?.measuredWidth?.toFloat()
//仅在开始操作按钮之前绘制
val x:Float=when{
dX.absoluteValue>backgroundWidth?:dX->backgroundWidth?.unaryMinus():dX
else->dX
}
前景?.translationX=x
}
覆盖(viewHolder:RecyclerView.viewHolder,方向:Int){
this.lastSwipedPosition=viewHolder.adapterPosition
recyclerView.adapter?.notifyItemChanged(此.lastSwipedPosition)
}
私人娱乐(
前景:你呢?
) {
前景?:返回
卸载(前景,“translationX”,0f)。应用{
持续时间=持续时间\u动画
开始()
}.doOnEnd{applyUiWorkaround()}
}
//更多信息请访问https://stackoverflow.com/a/37342327/3734116
私人娱乐应用软件{
itemTouchHelper.attachToRecyclerView(空)
itemTouchHelper.attachToRecyclerView(recyclerView)
}
私人娱乐活动(
视图:列表,
x:Int,
y:Int
):布尔值{
views.forEach{view:view?->
val viewRect=Rect()
视图?.getGlobalVisibleRect(viewRect)
if(viewRect.contains(x,y)){
查看?.performClick()的性能
返回真值
}
}
返回错误
}
伴星{
私有const val DURATION_动画:长=250
}
}
问题解决了ItemTouchHelper.SimpleCallback
吞噬所有触摸事件。因此,您需要为按钮注册一个touchstener
。我的例子中的按钮来自xml。受此启发,我提出了以下解决方案:
@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
}
}
@suppressint(“ClickableViewAccessibility”)
NachrichtItemSwipeCallback类(私有val recyclerView:recyclerView):
ItemTouchHelper.SimpleCallback(0,左){
private val itemTouchHelper:itemTouchHelper
私有变量绑定:ListItemNachRchtBinding?=null
私有变量lastSwipedPosition:Int=-1
初始化{
//禁用动画,因为它们不适用于自定义列表操作
(this.recyclerView.itemAnimator作为