Use group in ConstraintLayout to listen for click events on multiple views
The Group
in ConstraintLayout
is just a loose association of views AFAIK. It is not a ViewGroup
, so you will not be able to use a single click listener like you did when the views were in a ViewGroup
.
As an alternative, you can get a list of ids that are members of your Group
in your code and explicitly set the click listener. (I have not found official documentation on this feature, but I believe that it is just lagging the code release.) See documentation on 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.
}
});
}
In Kotlin you can build an extension function for that.
Kotlin:
fun Group.setAllOnClickListener(listener: View.OnClickListener?) {
referencedIds.forEach { id ->
rootView.findViewById<View>(id).setOnClickListener(listener)
}
}
Then call the function on the group:
group.setAllOnClickListener(View.OnClickListener {
// code to perform on click event
})
Update
The referenced ids are not immediately available in 2.0.0-beta2 although they are in 2.0.0-beta1 and before. "Post" the code above to grab the reference ids after layout. Something like this will work.
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()
}
})
}
}
}
This should work for releases prior to 2.0.0-beta2, so you can just do this and not have to do any version checks.
The extension method is great but you can make it even better by changing it to
fun Group.setAllOnClickListener(listener: (View) -> Unit) {
referencedIds.forEach { id ->
rootView.findViewById<View>(id).setOnClickListener(listener)
}
}
So the calling would be like this
group.setAllOnClickListener {
// code to perform on click event
}
Now the need for explicitly defining View.OnClickListener is now gone.
You can also define your own interface for GroupOnClickLitener like this
interface GroupOnClickListener {
fun onClick(group: Group)
}
and then define an extension method like this
fun Group.setAllOnClickListener(listener: GroupOnClickListener) {
referencedIds.forEach { id ->
rootView.findViewById<View>(id).setOnClickListener { listener.onClick(this)}
}
}
and use it like 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")
}
}
The second approach has a better performance if the number of views is large since you only use one object as a listener for all the views!
To complement the accepted answer for Kotlin users create an extension function and accept a lambda to feel more like the API group.addOnClickListener { }
.
Create the extension function:
fun Group.addOnClickListener(listener: (view: View) -> Unit) {
referencedIds.forEach { id ->
rootView.findViewById<View>(id).setOnClickListener(listener)
}
}
usage:
group.addOnClickListener { v ->
Log.d("GroupExt", v)
}
The better way to listen to click events from multiple views is to add a transparent view as a container on top of all required views. This view has to be at the end (i.e on top) of all the views you need to perform a click on.
Sample container view :
<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"/>
Above sample contains all four constraint boundaries within that, we can add views that to listen together and as it is a view, we can do whatever we want, such as ripple effect.