Java
Android
AndroidStudio
android開発

[Android] ListViewの中にあるボタンからリスナーを取得したい

More than 1 year has passed since last update.

はじめに

Androidアプリ開発をしていれば、必ず出会う
「ListViewにカスタムAdapterを噛ませる地獄」

一度実装できてしまえばなんてことないんですけどねー。
今回はさらに踏み込んで、ListViewの中にButtonをぶっ込もうかと。

意外とこういう場面は多そうじゃないですかね?
checkBoxであったり、編集用もしくは削除用Buttonとか。

今回は下のUIを頑張って作りたいと思います。個人的なメモ代わりですが。

click.gif

とりあえず呼び出し元を作る

MainActivity.java
public class MainActivity extends AppCompatActivity {
  private static final String[] countries = {
    "America",
    "Japan",
    "China",
    "Korea",
    "British",
    "German"
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    ListView listView = (ListView) findViewById(R.id.listView);
    CustomAdapter adapter = new CustomAdapter(getApplicationContext(), R.layout.row_item, countries);
    listView.setAdapter(adapter);
    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        switch (view.getId()) {
          case R.id.edit:
            Toast.makeText(MainActivity.this, countries[position] + "の編集ボタンが押されました", Toast.LENGTH_SHORT).show();
            break;
          case R.id.delete:
            Toast.makeText(MainActivity.this, countries[position] + "の削除ボタンが押されました", Toast.LENGTH_SHORT).show();
            break;
          }
       }
    });
  }
}
main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              tools:context="hiroto.myapplication.MainActivity">

  <ListView
    android:id="@+id/listView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:divider="@color/gray"
    android:dividerHeight="1dp"/>

</LinearLayout>

今回はMainActivityにListViewのみを用意しています。
そのListViewに次に紹介するカスタムAdapterを噛ませ、ボタン2つテキストを表示するカスタムListViewを構成しています。

ここで注意することは、Adapter側からのクリックイベントを取得するために、ListViewにSetOnItemClickListenerを与えることです。忘れずに...

取得したイベントがどのボタンか判別するには、switch文でid指定してあげることで実装しています。

カスタムAdapterを用意する

CustomAdapter.java
class CustomAdapter extends BaseAdapter {
  private LayoutInflater inflater;
  private int resourcedId;
  private String[] items;

  static class ViewHolder {
    Button editButton;
    TextView textView;
    Button deleteButton;
  }

  CustomAdapter(Context context, int resourcedId, String[] items) {
    this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    this.resourcedId = resourcedId;
    this.items = items;
  }

  @Override
  public View getView(final int position, View convertView, final ViewGroup parent) {
    ViewHolder holder;
    if (convertView == null) {
      convertView = inflater.inflate(resourcedId, parent, false);

      holder = new ViewHolder();
      holder.editButton = convertView.findViewById(R.id.edit);
      holder.textView = convertView.findViewById(R.id.text);
      holder.deleteButton = convertView.findViewById(R.id.delete);
      convertView.setTag(holder);
   } else {
      holder = (ViewHolder) convertView.getTag();
   }

    holder.textView.setText(items[position]);

    holder.editButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View view) {
        ((ListView) parent).performItemClick(view, position, R.id.edit);
      }
    });
    holder.deleteButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View view) {
        ((ListView) parent).performItemClick(view, position, R.id.delete);
      }
    });

    return convertView;
  }

  @Override
  public int getCount() {
    return items.length;
  }

  @Override
  public Object getItem(int position) {
    return position;
  }

  @Override
  public long getItemId(int position) {
    return position;
  }
}
row_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">

  <Button
    android:id="@+id/edit"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentStart="true"
    android:text="編集"/>

  <TextView
    android:id="@+id/text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerVertical="true"
    android:layout_marginStart="30dp"
    android:layout_toEndOf="@+id/edit"/>

  <Button
    android:id="@+id/delete"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentEnd="true"
    android:text="削除"/>

</RelativeLayout>

カスタムAdapterでは各行に表示する部品をfindByIdで取得し、テキストはsetText()で国名を、ボタンはSetOnClickListenerperformeItemClickを被せ、親ListViewにイベントを送っています。比較的シンプルで、他のコードとも干渉しないので、この方法が一番おすすめです。

終わりに

いかがでしたでしょうか?
ListView内のイベントを取得する方法は色々ありますが、中でも今回ご紹介した方法が最もシンプルで、元のコードからそこまで改変せずに済むので、導入しやすいと思います。
またそこまで複雑なフローではないので、理解もしやすかったかと思います。

皆様の参考になれば幸いです。