読者です 読者をやめる 読者になる 読者になる

ナカザンドットネット

Android Developer's memo

SimpleExpandableListAdapterで子要素にオブジェクトを持たせる

Android Programming

ExpandableListAdapterをお仕事で使っていて、ちょっと困りました。
ExpandableListViewをメニューとして使って、なおかつ裏でidとか持たせておいて、タップしたときにそのidを使って画面遷移とかしたかったんですよ。
でもなんかExpandableListView周りのサンプルをググってみても、結局リストに渡しているのがStringだけだったりするし、しかもリストに渡したStringを全部表示してたりしてて、「表示されない要素(というかオブジェクト丸ごと)を保持したExpandableListAdapter」というのが全然見つからなかったんですよね。

ということで作ってみようと思ったんですが。
「グループ分けされてて、子要素のリストも色んな属性を保持しているデータ群」
というのを考えた所、魔が差しまして。
某国民的アイドルをサンプルデータに使った頭のおかしいサンプルができてしまいました。*1

MainActivity.java

package net.nkzn.android.sample.expandablelistview;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.nkzn.android.sample.expandablelistview.AKB48.Member;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.SimpleExpandableListAdapter;
import android.widget.Toast;

public class MainActivity extends Activity {
	
	// Adapter作成に使うキー用定数
	private static final String TEAMNAME = "teamname";
	private static final String NAME = "name";
	private static final String NICKNAME = "nickname";
	private static final String OBJECT = "object";
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		
		// とりあえずチームごとに振り分け
		HashMap<String, List<Member>> akb48GroupByTeam = new HashMap<String, List<Member>>(); 
		for(Member member: AKB48.getAkb48Raw()) {
			
			String teamName = member.team;
			
			List<Member> team = akb48GroupByTeam.get(teamName);
			
			if(team == null) {
				team = new ArrayList<Member>();
			}
			
			team.add(member);
			
			akb48GroupByTeam.put(teamName, team);
		}

		// 「チーム名のMap」の全チーム分List
		List<Map<String, String>> teamList = new ArrayList<Map<String,String>>();
		
		// 「「メンバーごとのMap」の1チーム分のList」の全チーム分List
		List<List<Map<String, Object>>> memberList = new ArrayList<List<Map<String,Object>>>();
		
		// ExpandableListAdapter向けに整形
		// リスト1とリスト2はListのIndexで同期してるので、同じ回のループでaddしないとズレます。
		for(String teamName: akb48GroupByTeam.keySet()){
			// ===========チーム表示用データ作成 ここから==========
			Map<String, String> teamData = new HashMap<String, String>();
			teamData.put(TEAMNAME, teamName);
			teamList.add(teamData); // ・・・リスト1
			// ===========チーム表示用データ作成 ここまで==========

			// ===========メンバー表示用データ作成 ここから==========
			List<Map<String, Object>> teamMemberList = new ArrayList<Map<String,Object>>();
			
			for(Member member: akb48GroupByTeam.get(teamName)){
				Map<String, Object> memberData = new HashMap<String, Object>();
				memberData.put(NAME, member.name);
				memberData.put(NICKNAME, member.nickName);
				memberData.put(OBJECT, member); // ←【重要】表示しないけど要素に紐づけて保持する
				teamMemberList.add(memberData);
			}
			
			memberList.add(teamMemberList); // ・・・リスト2
			// ===========メンバー表示用データ作成 ここまで==========
		}
		
		// Adapterを作成(定型です)
		SimpleExpandableListAdapter adapter = new SimpleExpandableListAdapter(
                this,
                // チーム表示登録
                teamList, // チーム表示用データ(メンバ表示用データと要素数が同じはず)
                android.R.layout.simple_expandable_list_item_1, // チーム表示全体のレイアウト
                new String []{TEAMNAME}, // チーム表示用データから実際に使うもののキー
                new int []{android.R.id.text1}, // チーム表示のテキスト部分に振るID
                // メンバー表示登録
                memberList, // メンバー表示用データ(チーム表示用データと要素数が同じはず)
                android.R.layout.simple_expandable_list_item_2, // メンバー表示全体のレイアウト
                new String []{NAME, NICKNAME}, // メンバー表示用データから実際に使うもののキー(OBJECTは表示に使わないので外しました)
                new int []{android.R.id.text1, android.R.id.text2} //  メンバー表示のテキスト部分に振るID
        );
		
		// インスタンス作成
		ExpandableListView elvAkb48 = (ExpandableListView)findViewById(R.id.elv_akb48);

		// Adapter登録
		elvAkb48.setAdapter(adapter);
		
		// =====とりあえずここまでで表示は出来ます=====
		
		// メンバー部分のタップについてListener登録
		elvAkb48.setOnChildClickListener(new OnChildClickListener() {
			
			@Override
			public boolean onChildClick(ExpandableListView parent, View v,
					int groupPosition, int childPosition, long id) {

				// Adapterをもらってくる
				ExpandableListAdapter adapter = parent.getExpandableListAdapter();
				
				// メンバー表示用データ作成時に作ったブツがもらえます
				Map<String, Object> memberData = (Map<String, Object>)adapter.getChild(groupPosition, childPosition);
				
				// OBJECTキーの中身も手に入ります
				Member member = (Member)memberData.get(OBJECT);

				// 表示に使わなかったbirthday要素も表示できます☆-(ノ゚Д゚)八(゚Д゚ )ノイエーイ
				Toast.makeText(MainActivity.this, member.birthDay, Toast.LENGTH_SHORT).show();
				
				return false;
			}
		});
	}
	

	
}

AKB48.java

package net.nkzn.android.sample.expandablelistview;

import java.util.ArrayList;
import java.util.List;

public class AKB48 {

	/**
	 * List作るだけです
	 * @return AKB48正規メンバーのリスト(2011.12現在)
	 */
	public static List<Member> getAkb48Raw() {

		List<Member> akb48Raw = new ArrayList<Member>();

		// チームA
		akb48Raw.add(new Member("岩佐美咲", "わさみん", "1995.1.30", "A"));
		akb48Raw.add(new Member("多田愛佳", "らぶたん", "1994.12.8", "A"));
		akb48Raw.add(new Member("大家志津香", "しいちゃん", "1991.12.28", "A"));
		akb48Raw.add(new Member("片山陽加", "はーちゃん", "1990.5.10", "A"));
		akb48Raw.add(new Member("倉持明日香", "もっちぃ", "1989.9.11", "A"));
		akb48Raw.add(new Member("小嶋陽菜", "こじはる", "1988.4.19", "A"));
		akb48Raw.add(new Member("指原莉乃", "さっしー", "1992.11.21", "A"));
		akb48Raw.add(new Member("篠田麻里子", "麻里子さん", "1986.3.11", "A"));
		akb48Raw.add(new Member("高城亜樹", "あきちゃ", "1991.10.3", "A"));
		akb48Raw.add(new Member("高橋みなみ", "たかみな", "1991.4.8", "A"));
		akb48Raw.add(new Member("仲川遥香", "はるごん", "1992.2.10", "A"));
		akb48Raw.add(new Member("中田ちさと", "ちぃちゃん", "1990.10.8", "A"));
		akb48Raw.add(new Member("仲谷明香", "なかやん", "1991.10.15", "A"));
		akb48Raw.add(new Member("前田敦子", "あっちゃん", "1991.7.10", "A"));
		akb48Raw.add(new Member("前田亜美", "あーみん", "1995.6.1", "A"));
		akb48Raw.add(new Member("松原夏海", "なっつみぃ", "1990.6.19", "A"));

		// チームK
		akb48Raw.add(new Member("秋元才加", "さやか", "1988.7.26", "K"));
		akb48Raw.add(new Member("板野友美", "ともちん", "1991.7.3", "K"));
		akb48Raw.add(new Member("内田眞由美", "ウッチー", "1993.12.27", "K"));
		akb48Raw.add(new Member("梅田彩佳", "うめちゃん", "1989.1.3", "K"));
		akb48Raw.add(new Member("大島優子", "ゆうこ", "1988.10.17", "K"));
		akb48Raw.add(new Member("菊地あやか", "あやりん", "1993.6.30", "K"));
		akb48Raw.add(new Member("田名部生来", "たなみん", "1992.12.2", "K"));
		akb48Raw.add(new Member("中塚智実", "ともちゃん", "1993.6.18", "K"));
		akb48Raw.add(new Member("仁藤萌乃", "もえの", "1992.7.22", "K"));
		akb48Raw.add(new Member("野中美郷", "みい", "1991.4.20", "K"));
		akb48Raw.add(new Member("藤江れいな", "れいにゃん", "1994.2.1", "K"));
		akb48Raw.add(new Member("松井咲子", "さきっぺ", "1990.12.10", "K"));
		akb48Raw.add(new Member("峯岸みなみ", "みぃちゃん", "1992.11.15", "K"));
		akb48Raw.add(new Member("宮澤佐江", "さえ", "1990.8.13", "K"));
		akb48Raw.add(new Member("横山由依", "ゆい", "1992.12.8", "K"));
		akb48Raw.add(new Member("米沢瑠美", "よねちゃん", "1991.6.6", "K"));

		// チームB
		akb48Raw.add(new Member("石田晴香", "はるきゃん", "1993.12.2", "B"));
		akb48Raw.add(new Member("河西智美", "とも〜みちゃん", "1991.11.16", "B"));
		akb48Raw.add(new Member("柏木由紀", "ゆきりん", "1991.7.15", "B"));
		akb48Raw.add(new Member("北原里英", "きたりえ", "1991.6.24", "B"));
		akb48Raw.add(new Member("小林香菜", "かな", "1991.5.17", "B"));
		akb48Raw.add(new Member("小森美果", "みかぽん", "1994.7.19", "B"));
		akb48Raw.add(new Member("佐藤亜美菜", "あみな", "1990.10.16", "B"));
		akb48Raw.add(new Member("佐藤すみれ", "すーちゃん", "1993.11.20", "B"));
		akb48Raw.add(new Member("佐藤夏希", "Nなっち", "1990.7.1", "B"));
		akb48Raw.add(new Member("鈴木紫帆里", "しほりん", "1994.2.17", "B"));
		akb48Raw.add(new Member("鈴木まりや", "まりやんぬ", "1991.4.29", "B"));
		akb48Raw.add(new Member("近野莉菜", "チカリーナ", "1993.4.23", "B"));
		akb48Raw.add(new Member("平嶋夏海", "なっちゃん", "1992.5.28", "B"));
		akb48Raw.add(new Member("増田有華", "ゆか", "1991.8.3", "B"));
		akb48Raw.add(new Member("宮崎美穂", "みゃお", "1993.7.30", "B"));
		akb48Raw.add(new Member("渡辺麻友", "まゆゆ", "1994.3.26", "B"));

		// チーム4
		akb48Raw.add(new Member("阿部マリア", "まりあ", "1995.11.29", "4"));
		akb48Raw.add(new Member("市川美織", "みおりん", "1994.2.12", "4"));
		akb48Raw.add(new Member("入山杏奈", "あんにん", "1995.12.3", "4"));
		akb48Raw.add(new Member("大場美奈", "みなるん", "1992.4.3", "4"));
		akb48Raw.add(new Member("島崎遥香", "ぱるる", "1994.3.30", "4"));
		akb48Raw.add(new Member("島田晴香", "はるぅ", "1992.12.16", "4"));
		akb48Raw.add(new Member("竹内美宥", "みゆみゆ", "1996.1.12", "4"));
		akb48Raw.add(new Member("永尾まりや", "まりや", "1994.3.10", "4"));
		akb48Raw.add(new Member("仲俣汐里", "しおりん", "1992.7.25", "4"));
		akb48Raw.add(new Member("中村麻里子", "こまり", "1993.12.16", "4"));
		akb48Raw.add(new Member("山内鈴蘭", "らんらん", "1994.12.8", "4"));

		// 先生
		akb48Raw.add(new Member("秋元康 ", "やすす", "1956.5.2", "先生"));

		return akb48Raw;
	}
	
	/**
	 * メンバー1人分を表す内部クラス
	 */
	static class Member {
		/**
		 * 氏名
		 */
		public String name;
		
		/**
		 * あだ名
		 */
		public String nickName;
		
		/**
		 * 誕生日
		 */
		public String birthDay;
		
		/**
		 * 所属チーム
		 */
		public String team;
		
		public Member(String name, String nickName, String birthDay, String team) {
			this.name = name;
			this.nickName = nickName;
			this.birthDay = birthDay;
			this.team = team;
		}
	}

}

main.xml

<?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" >

	<ExpandableListView 
	    android:id="@+id/elv_akb48"
	    android:layout_width="match_parent"
	    android:layout_height="match_parent">
	</ExpandableListView>

</LinearLayout>

実行してみた

f:id:Nkzn:20120121005239p:image


うん。・・・うん。
何かを得た代わりに何かを失った気持ちになった。

まとめのようななにか

一番下の階層の子要素には表示用のStringも、保持用のオブジェクトも入れておかないといけないため、どうしても型がMapになってしまうのが気持ち悪いといえば気持ち悪い。。。
ExpandableListに上手いことオブジェクトを持たせるいい方法が他にあったら誰か教えて下さいまし。

あと親要素と子要素のListの同期を自前で取らないといけないのもどうなんだろう・・・。

*1:深夜のテンションなんてそんなもんだよね!!