在ConstraintLayout中使用group监听多个view的点击事件

Use group in ConstraintLayout to listen for click events on multiple views

基本上我想将单个 OnClickListener 附加到 ConstraintLayout 内的多个视图。

在迁移到 ConstraintLayout 之前,我可以在一个布局中添加一个侦听器的视图。现在它们与 ConstraintLayout 下的其他视图位于同一层。

我尝试将视图添加到 android.support.constraint.Group 并以编程方式向其添加 OnClickListener。

group.setOnClickListener {
    Log.d("OnClick", "groupClickListener triggered")
}

然而,这在 ConstraintLayout 版本中似乎不起作用 1.1.0-beta2

我是不是做错了什么,有没有办法实现这种行为,或者我是否需要将侦听器附加到每个单独的视图?

ConstraintLayout 中的 Group 只是一个松散的视图关联 AFAIK。它不是 ViewGroup,因此您将无法像视图位于 ViewGroup.

时那样使用单击侦听器

作为替代方案,您可以在代码中获取作为 Group 成员的 ID 列表,并明确设置点击侦听器。 (我没有找到关于此功能的官方文档,但我相信它只是滞后于代码发布。) 请参阅 getReferencedIds here 上的文档。

Java:

    Group group = findViewById(R.id.group);
    int refIds[] = group.getReferencedIds();
    for (int id : refIds) {
        findViewById(id).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // your code here.
            }
        });
    }

在 Kotlin 中,您可以为此构建一个扩展函数。

科特林:

    fun Group.setAllOnClickListener(listener: View.OnClickListener?) {
        referencedIds.forEach { id ->
            rootView.findViewById<View>(id).setOnClickListener(listener)
        }
    }

然后调用群上的函数:

    group.setAllOnClickListener(View.OnClickListener {
        // code to perform on click event
    })

更新

引用的 ID 在 2.0.0-beta2 中不会立即可用,尽管它们在 2.0.0-beta1 和更早版本中可用。 "Post" 上面的代码在布局后获取引用 ID。这样的东西会起作用。

class MainActivity : AppCompatActivity() {
    fun Group.setAllOnClickListener(listener: View.OnClickListener?) {
        referencedIds.forEach { id ->
            rootView.findViewById<View>(id).setOnClickListener(listener)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Referenced ids are not available here but become available post-layout.
        layout.post {
            group.setAllOnClickListener(object : View.OnClickListener {
                override fun onClick(v: View) {
                    val text = (v as Button).text
                    Toast.makeText(this@MainActivity, text, Toast.LENGTH_SHORT).show()
                }
            })
        }
    }
}

这应该适用于 2.0.0-beta2 之前的版本,因此您可以只执行此操作而不必进行任何版本检查。

从多个视图监听点击事件的更好方法是在所有需要的视图之上添加一个透明视图作为容器。此视图必须位于您需要执行点击的所有视图的末尾(即顶部)。

示例容器视图:

<View
   android:id="@+id/view_container"
   android:layout_width="0dp"
   android:layout_height="0dp"
   app:layout_constraintBottom_toBottomOf="@+id/view_bottom"
   app:layout_constraintEnd_toEndOf="@+id/end_view_guideline"
   app:layout_constraintStart_toStartOf="@+id/start_view_guideline"
   app:layout_constraintTop_toTopOf="parent"/>

上面的示例包含所有四个约束边界,我们可以添加视图一起听,因为它是一个视图,我们可以做任何我们想做的事情,比如涟漪效应。

虽然我喜欢 中的一般方法,但我认为它有一个主要缺点和两个小缺点。

  1. 它不考虑单个视图的动态位置变化

  2. 它可能会为不属于该组的视图注册点击次数

  3. 这不是这个相当普遍的问题的通用解决方案

虽然我不确定第二点的解决方案,但第一点和第三点显然很简单。


1.组内元素会计职位变动

这个其实很简单。可以使用约束布局的工具集来调整透明视图的边缘。 我们简单地使用 Barriers 来接收组中任何视图的最左边、最右边等位置。 然后我们可以将透明视图调整为障碍而不是具体视图。

3。通用解决方案

使用 Kotlin,我们可以扩展 Group-Class 以包含一种将 ClickListener 添加到 View 的方法,如上所述。 此方法只是将 Barriers 添加到布局中,注意组中的每个子项,与障碍对齐的透明视图,并将 ClickListener 注册到后者。

这样我们只需要在组上调用方法,不需要在每次需要这种行为时手动将视图添加到布局中。

为了补充 Kotlin 用户接受的答案,创建一个扩展函数并接受一个 lambda 来感觉更像 API group.addOnClickListener { }.

创建扩展函数:

fun Group.addOnClickListener(listener: (view: View) -> Unit) {
    referencedIds.forEach { id ->
        rootView.findViewById<View>(id).setOnClickListener(listener)
    }
}

用法:

group.addOnClickListener { v ->
    Log.d("GroupExt", v)
}

扩展方法很棒,但您可以通过将其更改为

使其变得更好
fun Group.setAllOnClickListener(listener: (View) -> Unit) {
    referencedIds.forEach { id ->
        rootView.findViewById<View>(id).setOnClickListener(listener)
    }
}

所以调用应该是这样的

group.setAllOnClickListener {
    // code to perform on click event
}

现在不再需要显式定义 View.OnClickListener。

您也可以像这样为 GroupOnClickLitener 定义自己的界面

interface GroupOnClickListener {
    fun onClick(group: Group)
}

然后像这样定义一个扩展方法

fun Group.setAllOnClickListener(listener: GroupOnClickListener) {
    referencedIds.forEach { id ->
        rootView.findViewById<View>(id).setOnClickListener { listener.onClick(this)}
    }
}

并像这样使用它

groupOne.setAllOnClickListener(this)
groupTwo.setAllOnClickListener(this)
groupThree.setAllOnClickListener(this)

override fun onClick(group: Group) {
    when(group.id){
        R.id.group1 -> //code for group1
        R.id.group2 -> //code for group2
        R.id.group3 -> //code for group3
        else -> throw IllegalArgumentException("wrong group id")
    }
}

如果视图数量很大,第二种方法性能更好,因为您只使用一个对象作为所有视图的侦听器!

致 Java 像我这样的人:

public class MyConstraintLayoutGroup extends Group {
    public MyConstraintLayoutGroup(Context context) {
        super(context);
    }

    public MyConstraintLayoutGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyConstraintLayoutGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setOnClickListener(OnClickListener listener) {
        for (int id : getReferencedIds()) {
            getRootView().findViewById(id).setOnClickListener(listener);
        }
    }
}

我不喜欢这没有将点击状态传播到所有其他 children。