Использовать группу в ConstraintLayout для прослушивания событий кликов на нескольких представлениях

В принципе, я хотел бы привязать один элемент OnClickListener к нескольким представлениям внутри ConstraintLayout.

Перед переносом на ConstraintLayout просмотры, в которых внутри одного макета, на который я мог бы добавить слушателя. Теперь они находятся на одном уровне с другими видами прямо под ConstraintLayout.

Я попытался добавить представления к android.support.constraint.Group и программно добавил к нему OnClickListener.

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

Однако это не похоже на версию ConstraintLayout 1.1.0-beta2

Я сделал что-то неправильно, есть ли способ достичь такого поведения или мне нужно приложить слушателя к каждому из отдельных представлений?

Ответ 1

Group в ConstraintLayout - это просто слабая ассоциация взглядов AFAIK. Это не ViewGroup, поэтому вы не сможете использовать прослушиватель с одним щелчком, как вы делали, когда представления были в ViewGroup.

В качестве альтернативы, вы можете получить список идентификаторов, которые являются членами вашей Group в вашем коде и явно установить прослушиватель кликов. (Я не нашел официальной документации по этой функции, но я считаю, что она просто отстает от выпуска кода.) Смотрите документацию по getReferencedIds здесь.

Джава:

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

Обновить

Указанные идентификаторы не сразу доступны в 2.0.0-бета2, хотя они есть в 2.0.0-бета1 и ранее. "Разместите" приведенный выше код, чтобы получить ссылочные идентификаторы после размещения. Примерно так будет работать.

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([email protected], text, Toast.LENGTH_SHORT).show()
                }
            })
        }
    }
}

Это должно работать для выпусков до 2.0.0-бета2, так что вы можете просто сделать это и не делать никаких проверок версий.

Ответ 2

Чтобы дополнить принятый ответ для пользователей Kotlin, создайте функцию расширения и примите лямбду, чтобы больше походить на 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)
}

Ответ 3

Лучший способ прослушать события клика из нескольких представлений - добавить прозрачный вид в качестве контейнера поверх всех необходимых представлений. Это представление должно находиться в конце (т.е. Сверху) всех представлений, по которым нужно выполнить щелчок.

Пример контейнера:

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

Вышеприведенный пример содержит все четыре границы ограничения, которые мы можем добавить к представлениям для совместного прослушивания, и, поскольку это представление, мы можем делать все, что захотим, например, эффект ряби.

Ответ 4

Метод расширения хорош, но вы можете сделать его еще лучше, изменив его на

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")
    }
}

Второй подход имеет лучшую производительность, если количество представлений велико, поскольку вы используете только один объект в качестве слушателя для всех представлений!

Ответ 5

В то время как мне нравится общий подход в Ответ Vitthalk, я думаю, что у него есть один главный недостаток и два незначительных.

  • В нем не учитываются динамические изменения позиции отдельных представлений

  • Он может регистрировать клики для просмотров, которые не входят в группу

  • Это не общее решение этой довольно распространенной проблемы.

В то время как я не уверен в решении второго пункта, очевидно, что они очень легкие для первого и третьего.


1. Изменение позиции позиции в группе

Это на самом деле довольно просто. Для настройки краев прозрачного представления можно использовать набор инструментов макета ограничения. Мы просто используем Barriers, чтобы получить самые левые, самые правые и т.д. Позиции любого вида в группе. Затем мы можем настроить прозрачный вид на барьеры вместо конкретных видов.

3. Общее решение

Используя Kotlin, мы можем расширить Group-Class, чтобы включить метод, который добавляет ClickListener в представление, как описано выше. Этот метод просто добавляет Барьеры к макету, обращая внимание на каждого дочернего элемента группы, прозрачное представление, которое выровнено с барьерами и регистрирует ClickListener для последнего.

Таким образом, нам просто нужно вызвать метод в группе и не нужно добавлять представления в макет вручную каждый раз, когда нам нужно это поведение.