My blog

Add intelligent tagline here

[Android]ExpandableListViewとSimpleExpandableListAdapterの構造

ExpandableListViewはタップすると子要素を開く、ListViewの一種です。これを使うことで、一段階までの要素を表現できます。

ListViewと同じくこれもAdapterを使って表示内容のデータを保存します。Adapterにはいくつかあるのですが、一番手軽に使えそうなSimpleExpandableListAdapterで、意外にはまってしまったのでここに記します。

階層構造では、親と子の対応関係が必要なわけですが、SimpleExpandableListAdapterでは、親と子の対応関係を明示的に設定することはしません。親リストと子リストの中で何番目なのかということで、親子関係を設定します。

つまり、こうなります。

  • 親ノードリスト (Mapのリスト) * [0]: 1番目のグループ (A) * [1]: 2番目のグループ (B) * [2]: 3番目のグループ (C)

  • 子ノードリスト (Mapのリストのリスト) * [0]: 1番目のグループに入れるリスト (Mapのリスト)

    • [0]: 1番目のグループに入れる1つ目のデータ(Map) (a)
    • [1]: 1番目のグループに入れる2つ目のデータ (b)
    • [2]: 1番目のグループに入れる3つ目のデータ (c)
    • [1]: 2番目のグループに入れるリスト * [0]: 2番目のグループに入れる1つ目のデータ (d)
    • [2]: 3番目のグループに入れるリスト * [0]: 3番目のグループに入れる1つ目のデータ (e) * [1]: 3番目のグループに入れる2つ目のデータ (f)

こうすることで、

  • A * a * b * c
  • B * d
  • C * f

という親子関係を持ったExpandableListViewを実現できます。分かりにくいですかね…

サンプルコード

以下にサンプルコードを示します。このコードでは、IDと日付を持っているHogeDataクラスのリストを日付ごとに整理して表示します。日付を親とし、その日付を持っているHogeDataクラスを子として表示するわけです。

なお、元のコードからコピペと改変をしたので、実際に動くかどうかは確認していません。インデントも崩れてしまっています。心を読んでください ;-)

ちなみにライセンスは参考にしたGoogleのサンプルコードと同じくApache Licenseとさせてください。

public void setExpandableListView(ArrayList<HogeData> hogeList){
    ExpandableListView listView = new ExpandableListView(getApplicationContext());

    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
    List<Map<String, String>> groupData = new ArrayList<Map<String,String>>(); // 親ノードリスト
    List<List<Map<String, String>>> childData = new  ArrayList<List<Map<String,String>>>(); // 子ノードリスト

    for(Iterator<HogeData> iter = hogeList.iterator(); iter.hasNext();){
       HogeData ho = iter.next();
       List<Map<String, String>> children; // 子要素を宣言

       String date = format.format(ho.getDate());
       int location = getParentGroup(date, groupData); // 親リストの場所を取得
       if (location == -1){
           Map<String, String>groupMap = new HashMap<String, String>();
           groupMap.put("date", date);
           groupData.add(groupMap); // 親リストに追加
           children = new ArrayList<Map<String, String>>(); // 対応する子要素を作り、追加
           childData.add(children);
           location = 0;
       }else{
           children = childData.get(location);
       }
       // データを作成・追加
       Map<String, String> curChildMap = new HashMap<String, String>();
       curChildMap.put("id", ho.getId());
       curChildMap.put("date", ho.getDate());
       children.add(curChildMap);
    }

    // アダプタ設定
    ExpandableListAdapter mAdapter = new SimpleExpandableListAdapter(
                             getApplication(),
                             groupData,
                             android.R.layout.simple_expandable_list_item_1,
                             new String[] {"date"}, // 親のMapで表示するデータを設定
                             new int[] { android.R.id.text1},
                             childData,
                             android.R.layout.simple_expandable_list_item_2,
                             new String[] { "id", "date"}, // 子の表示データ
                             new int[] { android.R.id.text1, android.R.id.text2}
                             );

    listView.setAdapter(mAdapter);

    // クリックした時の動作を設定
    listView.setOnChildClickListener(new ExpandableListView.OnChildClickListener(){
     @SuppressWarnings("unchecked") // チェックなしキャストの警告を抑制
         public boolean onChildClick(ExpandableListView parent,
                                     View v, int groupPosition, int childPosition,
                                     long id) {
         ExpandableListAdapter adapter = parent.getExpandableListAdapter();
         Map<String, String> childMap = (Map<String, String>)adapter.getChild(
                                                                              groupPosition,
                                                                              childPosition
                                                                              );

         Log.d("ExtView", "id:" + childMap.get("id") + "/date:" + childMap.get("date"));
     }
        });

    layout.addView(listView);

    return layout;
}

/**
 * 引数keyをkeyとして持っているMapが
 * 引数listのリスト内にあるかどうか調べ、あった場合にはその番号を、ない場合は-1を返す。
 * @param key 調べるkey
 * @param list 調べるリスト
 * @return list内の場所
*/
public int getParentGroup(String key, List<Map<String, String>> list){
     int location = 0;
     for(Iterator<Map<String, String>> iter = list.iterator(); iter.hasNext();){
             Map<String, String> map = iter.next();
             if (map.containsValue(key)){
                     return location;
             }
             location++;
     }
     return -1;
    }