Jos*_*lan 5 android android-recyclerview
I have a RecyclerView that displays an Button which extends outside its parent ViewHolder. To the button I added a clickListener to display a toast. If you click on the Button and the click is on the area of the Button parent ViewHolder, the toast shows, but if you click on the button but outside its parent ViewHolder the toast doesn't show anymore.
Here's what I currently have
RecyclerView:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="0dp"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:focusedByDefault="false"
android:scrollbars="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/indicator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:reverseLayout="true"
app:stackFromEnd="true" />
Run Code Online (Sandbox Code Playgroud)
Item view:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/layout"
android:layout_width="15dp"
android:layout_height="match_parent"
android:clipChildren="false"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:clipToPadding="false"
android:elevation="0dp"
android:scaleY="-1.0">
<View
android:id="@+id/vertical"
android:layout_width="1dp"
android:layout_height="match_parent"
android:alpha="0.25"
android:background="#ffffff"
android:visibility="gone"
android:layout_marginVertical="5dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/annotation"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:visibility="gone"
android:scaleY="-1.0"
android:elevation="100dp"
android:text="TEST ANNOTATION"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Run Code Online (Sandbox Code Playgroud)
Adapter:
class ChartAdapter(
private val dataSet: MutableList<Float>,
private val context: Context
) : RecyclerView.Adapter<ChartAdapter.ViewHolder>() {
private var scaleFactor: Float = 1.0f
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val layout: ConstraintLayout = view.findViewById(R.id.layout)
val vertical: View = view.findViewById(R.id.vertical)
val annotation: View = view.findViewById(R.id.annotation)
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(
LayoutInflater.from(viewGroup.context)
.inflate(R.layout.layout_quadrant, viewGroup, false)
)
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
viewHolder.layout.layoutParams = ConstraintLayout.LayoutParams(
(39 * this.scaleFactor).roundToInt(),
ConstraintLayout.LayoutParams.MATCH_PARENT
)
ConstraintSet().apply {
clone(viewHolder.layout)
applyTo(viewHolder.layout)
}
viewHolder.annotation.scaleX = this.scaleFactor
viewHolder.annotation.scaleY = this.scaleFactor * -1
viewHolder.vertical.visibility = when {
position % 5 == 0 || position == dataSet.size - 1 -> View.VISIBLE
else -> View.GONE
}
viewHolder.annotation.visibility = when (position) {
15 -> View.VISIBLE
else -> View.GONE
}
viewHolder.annotation.setOnClickListener {
Toast.makeText(context, "annotation clicked!", Toast.LENGTH_SHORT).show()
}
}
override fun getItemCount() = dataSet.size
fun setScaleFactor(scaleFactor: Float) {
this.scaleFactor = scaleFactor
notifyDataSetChanged()
}
}
Run Code Online (Sandbox Code Playgroud)
As you can see I've tried to add android:clickable="false", android:focusable="false" and android:focusableInTouchMode="false" to the Item layout and the RecyclerView to prevent it from intercepting the click action but no luck the parent always keep intercepting the click thus preventing the button from being clicked.
Also I've tried this on the ViewHolder but no luck
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val layout: ConstraintLayout = view.findViewById(R.id.layout)
val vertical: View = view.findViewById(R.id.vertical)
val annotation: View = view.findViewById(R.id.annotation)
init {
view.layoutParams = WindowManager.LayoutParams().apply {
height = WindowManager.LayoutParams.MATCH_PARENT;
width = WindowManager.LayoutParams.WRAP_CONTENT;
flags =
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
}
view.isClickable = false
view.isFocusable = false
view.isFocusableInTouchMode = false
val itemTouchListener = View.OnTouchListener { v, event -> false }
view.setOnTouchListener(itemTouchListener)
}
}
Run Code Online (Sandbox Code Playgroud)
问题是 Android 触摸系统在ViewGroup(此处为ConstraintLayout)上启动触摸,然后将其传播到ViewGroup 的子级,但触摸必须位于子级与 ViewGroup 重叠的部分。这就是你所看到的。
这是对所发生情况的一个很好的解释。
我认为,如果您需要坚持当前的设计,最好的方法是捕获封装整个按钮的视图项的第一个祖先的触摸。然后,您可以测试该祖先上的触摸事件,看看它们是否也在按钮的范围内。如果是,您将向dispatchTouchEvent()按钮发送触摸事件。
这是正在发生的事情的简单演示。我不使用RecyclerView,而是使用更简单的布局,该布局显示一个横跨LinearLayout中包含的ConstraintLayout右边缘的按钮。目标是让按钮 1/2 位于其父ViewGroup中,并取出 1/2 以显示点击是如何发生的。
在演示中,开关确定我们是否要检测位于其父级之外的按钮部分的点击。当开关“关闭”时,不会检测到外部的咔哒声,而当开关打开时,会检测到外部的咔哒声。
这是演示的代码。该代码为按钮建立一个屏幕上的点击矩形,并在主活动的dispatchTouchEvent()中检查触摸是否位于点击矩形内。
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var mBaseLayout: LinearLayoutCompat
private lateinit var mButton: Button
private val mButtonOnScreenBounds = Rect()
private var mIsOutsideClickEnabled = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mBaseLayout = findViewById(R.id.baseLayout)
findViewById<SwitchCompat>(R.id.enableClicks).setOnCheckedChangeListener { _, isChecked ->
mIsOutsideClickEnabled = isChecked
}
mButton = findViewById(R.id.button)
}
override fun dispatchTouchEvent(ev: MotionEvent) =
super.dispatchTouchEvent(ev) ||
(mIsOutsideClickEnabled && isClickWithinView(mButton, ev)
&& mButton.dispatchTouchEvent(ev))
fun isClickWithinView(view: View, ev: MotionEvent) =
Rect().let { rect ->
view.getGlobalVisibleRect(rect)
rect.contains(ev.x.toInt(), ev.y.toInt())
}
fun onClick(view: View) {
when (view.id) {
R.id.baseLayout -> Toast.makeText(this, "baseLayout clicked!", Toast.LENGTH_SHORT)
R.id.innerLayout -> Toast.makeText(this, "innerLayout clicked!", Toast.LENGTH_SHORT)
R.id.button -> Toast.makeText(this, "button clicked!", Toast.LENGTH_SHORT)
else -> Toast.makeText(this, "Something else clicked!", Toast.LENGTH_SHORT)
}.show()
}
}
Run Code Online (Sandbox Code Playgroud)
这是使用的布局:
活动主文件
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/baseLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_red_light"
android:clipChildren="false"
android:orientation="horizontal"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/innerLayout"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginBottom="96dp"
android:background="@android:color/holo_blue_light"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/enableClicks"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Enable clicks outside "
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="Button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
Run Code Online (Sandbox Code Playgroud)
该处理或类似的处理也可以在触摸处理期间调用的其他函数(例如触摸监听器)中完成。
虽然前面的方法看起来可行,但如果您看一下在 LinearLayout 内部和外部触摸按钮时的涟漪效果,就会发现它是不同的。当触摸位于按钮上但位于 LinearLayout 内部时,波纹会按预期从触摸点延伸。当触摸位于LinearLayout外部时,波纹不是来自触摸点,而是来自底部中心。这引起了我的担忧,因为它表明,某种程度上,处理是不同的。可能还存在其他问题,例如可访问性等。因此,买者自负。
更好的方法是以某种方式扩展视图持有者以包含按钮的整个范围。
| 归档时间: |
|
| 查看次数: |
206 次 |
| 最近记录: |