ListViewの中のボタンのクリックイベントをActivityに通知する

ちょいとはまったのでメモ。

ListViewの中でどうしてもボタンを並べて、ボタンが押されたらActivityに通知させたかったのですが、なかなかスマートな方法が見つからなかった…。

結局OnClickListenerを実装させたListViewのサブクラスを作って、adapter#getViweの中でリスナーの登録をさせることによって実現しました。

具体的なソースは下記のような感じ。

まずレイアウト。サンプルなんで適当。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
>
  <TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="sample" />

  <FrameLayout
    android:layout_width="fill_parent"
    android:layout_height="0dip"
    android:layout_weight="1"
  >
    <com.sample.MultiButtonListView
      android:id="@+id/list"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent" />
  </FrameLayout>
</LinearLayout>

続いてリストの各行のレイアウト

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="vertical"
>
  <TextView
    android:id="@+id/titleText"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" />
  <TextView
    android:id="@+id/authorText"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" />
    
  <LinearLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
  >
    <Button
      android:id="@+id/titleBtn"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="title" />
    <Button
      android:id="@+id/authorBtn"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="author" />
  </LinearLayout>
</LinearLayout>

で、問題のListViewのサブクラス。

/**
 * リスト内にボタンを配置してそれが押されたとき通知してくれるListView
 * @author Tom
 *
 */
public class MultiButtonListView extends ListView implements OnClickListener {
  
  /**
   * コンストラクタ
   * @param ctx
   */
  public MultiButtonListView(Context ctx){
    super(ctx);
  }
  
  /**
   * コンストラクタ
   * @param ctx
   * @param attrs
   */
  public MultiButtonListView(Context ctx , AttributeSet attrs){
    super(ctx , attrs);
  }
  
  /**
   * リスト内のボタンがクリックされたら呼ばれる
   */
  public void onClick(View view) {
    int pos = (Integer)view.getTag();
    this.performItemClick(view, pos, view.getId());//idって普通なに渡すの?
  }

}

単にOnClickListenerを実装して、その中でpeformItemClickを呼んでるだけ。
viewのgetTagでpositionを取ってるけど、それはAdapterの中で設定してあげてます。ところで引数の3つ目のidってなに渡すの?使ったこと無いんだけど。
adapterはArrayAdapterのサブクラスで下記みたいな感じ。

public class BookAdapter extends ArrayAdapter<Book> {
  private LayoutInflater inflater;
  private int resId;
  
  public BookAdapter(Context ctx , int resId , List<Book> items){
    super(ctx, resId , items);
    this.resId = resId;
    this.inflater = (LayoutInflater)ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  }
  
  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    MultiButtonListView list = null;
    try{
      list = (MultiButtonListView)parent;
    }catch(Exception e){
      e.printStackTrace();
    }
    if(convertView == null){
      convertView = inflater.inflate(resId, null);
      holder = new ViewHolder();
      holder.titleView = (TextView)convertView.findViewById(R.id.titleText);
      holder.authorView = (TextView)convertView.findViewById(R.id.authorText);
      holder.button1 = (Button)convertView.findViewById(R.id.titleBtn);
      holder.button2 = (Button)convertView.findViewById(R.id.authorBtn);
      holder.button1.setOnClickListener(list);
      holder.button2.setOnClickListener(list);
      
      convertView.setTag(holder);
    }
    else{
      holder = (ViewHolder)convertView.getTag();
    }
    Book book = this.getItem(position);
    holder.titleView.setText(book.getTitle());
    holder.authorView.setText(book.getAuthor());
    holder.button1.setTag(position);
    holder.button2.setTag(position);
    
    return convertView;
  }
  
  private static class ViewHolder{
    public TextView titleView;
    public TextView authorView;
    public Button button1;
    public Button button2;
  }
}

parentをキャストしているのと、buttonにリスナー登録しているところ以外は特に変わったことはしていない。

で、最後にActivity

public class MultiSample extends Activity implements OnItemClickListener {
    private static List<Book> books;
    static{
      books = new ArrayList<Book>();
      books.add(new Book("なれる!SE3 失敗しない?提案活動 (電撃文庫)" , "夏海 公司"));
      books.add(new Book("はじめてのAndroid2プログラミング", "柴田 文彦"));
      books.add(new Book("魔術はささやく (新潮文庫)" , "宮部 みゆき"));
      books.add(new Book("幽霊船が消えるまで―痛快本格推理 (祥伝社文庫―天才・竜之介がゆく!)" , "柄刀 一"));
      books.add(new Book("イニシエーション・ラブ (ミステリー・リーグ)" , "乾 くるみ"));
      books.add(new Book("GOTH 僕の章 (角川文庫)", "乙一 "));
      books.add(new Book("ネガティブハッピー・チェーンソーエッヂ (角川文庫)" , "滝本 竜彦"));
      books.add(new Book("アメリカ銃の謎 (創元推理文庫 104-10)" , "エラリー・クイーン"));
    }
    
    private ListView list;
    private BookAdapter adapter;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        list = (ListView)findViewById(R.id.list);
        adapter = new BookAdapter(this, R.layout.list_row, books);
        list.setAdapter(adapter);
        list.setOnItemClickListener(this);
    }
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
      Book book = adapter.getItem(pos);
      switch(view.getId()){
      case R.id.titleBtn :
        Toast.makeText(this, book.getTitle() , Toast.LENGTH_SHORT).show();
        break;
      case R.id.authorBtn :
        Toast.makeText(this, book.getAuthor() , Toast.LENGTH_SHORT).show();
        break;
      }
    }
    
}

Bookオブジェクトは単純にtitleとauthorを持っているだけ。

これで、リストの中のボタンイベントを普通にonItemClickで拾えるようになった!
サンプル画像は下記のような感じに。
f:id:tomstay:20110421220113j:image

そもそもリストの中にボタンなんぞ並べずにコンテキストメニューとか使えって話かもしれないけど。ただ、今考えてるのはボタンを押したらダイアログが出るような処理を考えているので2連続ダイアログっぽいのが出るのは微妙じゃね?って思ったのでボタン並べるという所業におよんだのでありました。

おかしなところがあったらそっと教えてください。