Handling click events within AdapterView such as ListView and GridView

Problem – Events within row/cell

We want to have an AdapterView, which is extended by ListView and GridView, with clickable buttons inside its row or cell while still being able to tap on the row/cell itself.

When an AdapterView contains clickable views such as buttons, the listener registered to the clickable view will take over the click event disallowing onItemClick on AdapterView to fire.

As a quick fix, we could just define OnClickListener for the buttons in the adapter class, but this would separate the code for responding to events into 2 places, for instance, activity and adapter. To keep the code clean, we want to write the listener callback in one place.




Solution

Below is a screenshot of a ListView with a TextView and 2 Button views each row. So, there are 3 touchable(2 Buttons and the rest of the space in a row) views that will react to clicking events. This will be our sample.

dvhj4tde

In order to let the rest of the space in a row respond to an event, we have set focusable property of the buttons to false. Otherwise, the buttons will be the only views that react to the event and nothing will happen when the other part of the row is touched.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >
 
    <Button 
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:focusable="false" />
 
    <Button 
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:focusable="false" />
 
    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
 
</LinearLayout>

Then, in getView method in your adapter class, set OnClickListener to each button. The trick is to call performItemClick and pass this view. We will be able to catch this view in onItemClickListener’s onItemClick method. According to the official doc, performItemClick method calls the OnItemClickListener if it is defined.

1
2
3
4
5
6
7
8
viewHolder.button1 = (Button) convertView.findViewById(R.id.button1);
viewHolder.button1.setOnClickListener(new OnClickListener() {
 
    @Override
    public void onClick(View v) {
        ((ListView) parent).performItemClick(v, position, 0); // Let the event be handled in onItemClick()
    }
});

In activity class with OnItemClickListener interface implemented, we can simply refer to the view by following.

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    long viewId = view.getId();
 
    if (viewId == R.id.button1) {
        Toast.makeText(this, "Button 1 clicked", Toast.LENGTH_SHORT).show();
    } else if (viewId == R.id.button2) {
        Toast.makeText(this, "Button 2 clicked", Toast.LENGTH_SHORT).show();
    } else {
        Toast.makeText(this, "ListView clicked" + id, Toast.LENGTH_SHORT).show();
    }
}

Comments are closed.