【Android】ExpandableListViewの各データにイベントを設定する(案)

ListViewの方でやったものの、ExpandableListViewでも同じことをしたくなってしまったので、やってみます。

ただ、私はExpandableListViewをほとんど使ったことがなく、AdapterもSimpleExpandableListAdapterしか使ったことがありません。なのでいつものように「大体こんな感じでしょ?」と適当に書くことが出来ません。(いつもはそのぐらいの気持ちでしか書いてないので誤字脱字がひどいんですが)

と言うわけで、BaseExpandableListAdapterを継承したAdapterの作り方も考えつつ、どうにかやっていきたいと思います。

やりたいことと懸念事項

  • 親要素は子要素もしくはイベントのどちらかを必ず所持している。
  • クリック時に親要素が子要素を所持していたらリストを展開し、イベントを所持していたらそれを実行する。
  • 子要素が含まれていない場合、ExpandableListViewとExpandableListAdapterの何のメソッドが呼ばれるかわからない。
  • ExpandableListAdapter#getChildViewExpandableListAdapter#onGroupExpandedは呼ばれるのか、とか。
  • 小要素は必ずイベントを所持しており、クリック時にそのイベントを実行する。
  • 小要素が何らかの親要素になることはない。(入れ子構造の禁止)
  • 親要素が子要素を所持していない場合はIndicatorを表示しない。
  • ExpandableListView側で制御するのか、ExpandableListAdapterで制御するのかわからない。
  • そもそもどこまで制御可能なのか?
  • ExpandableListView#setGroupIndicatorには、小要素がない場合state_emptyがセットされるとのことだが、これを変更する方法があるのか?
  • って言うか、デフォルトだと表示されるの?

イベントを保持するクラスを作成する

とりあえずイベントを持っているクラスを作成しましょう。基本的な考え方は以前と同じです。

イベントもしくは子要素のリストを保持するParentと、イベントを保持するChildを作ります。

public class ExpandableParent<EventArg> {
    private String displayName;
    private List<ExpandableChild<EventArg>> children = new ArrayList<ExpandableChild<EventArg>>();
    private V1<EventArg> event;
    
    public Parent(String displayName) {
        this.displayName = displayName;
    }
    
    public Parent(String displayName, List<ExpandableChild<EventArg>> children) {
        this.displayName = displayName;
        this.children = children;
    }
    
    public Parent(String displayName, V1<EventArg> event) {
        this.displayName = displayName;
        this.event = event;
    }
    
    public String getDisplayName() {
        return this.getDisplayName;
    }
    
    public List<ExpandableChild<EventArg>> getChildren() {
        return this.children;
    }
    
    public void addChild(ExpandableChild<EventArg> child) {
        children.add(child);
    }
    
    public boolean hasChild() {
        return !children.isEmpty();
    }
    
    public void setEvent(V1<EventArg> event) {
        this.event = event;
    }
    
    public boolean hasEvent() {
        return event != null;
    }
    
    public V1<EventArg> getEvent() {
        return this.event;
    }
    
    public void callEvent(EventArg arg) {
        this.event.call(arg);
    }
    
    @Override
    public String toString() {
        return this.displayName;
    }
}

public class ExpandableChild<EventArg> {
    private String displayName;
    private V1<EventArg> event;
    
    public Child(String displayName, V1<EventArg> event) {
        this.displayName = displayName;
        this.event = event;
    }
    
    public String getDisplayName() {
        return this.displayName;
    }
    
    public V1<EventArg> getEvent() {
        return this.event;
    }
    
    public void callEvent(EventArg arg) {
        this.event.call(arg);
    }
    
    @Override
    public String toString() {
        return this.displayName;
    }
}

BaseExpandableListAdapterを継承したAdapterを作る

ぶっちゃけ何がどこまで必要なのか全然わかってないので、書きかけです。

ExpandableListAdapter#getGroupViewは親要素の表示用Viewを、ExpandableListAdapter#getChildViewは子要素の表示用Viewを作って返します。きっとAdapter#getViewと同じように設定してやればいいんでしょう。

public EventExpandableAdapater<EventArg> extends BaseExpandableListAdapter {
    
    private List<ExpandableParent<EventArg>> _parents;
    private Context _cont;
    
    public EventExpandableAdapater(List<ExpandableParent<EventArg>> parents, Context cont) {
        _parents = parent;
        _cont = cont;
    }
    
    @Override
    public Object getChild(int groupPosition, int childPosition) {
        if(_parents.hasChild()) {
            return _parents.get(groupPosition).getChildren().get(childPosition);
        } else {
            null;
        }
    }
    
    @Override
    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
        //TODO:親の表示用View作成
        ExpandableParent<EventArg> parent = _parents.get(groupPosition);
    }
    
    @Override
    public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
        //TODO:子の表示用View作成
        //ParentがChildを持ってないとぬるぽが出てしまうね…。
        ExpandableChild<EventArg> child = _parents.get(groupPosition).get(childPosition);
        
    }
    
    public boolean hasChild(int groupPosition) {
        return _parents.get(groupPosition).hasChild();
    }
    
    public ExpandableParent<EventArg> getParent(int groupPosition) {
        return _parents.get(groupPosition);
    }
}

ExpandableListViewにAdapterをセットしてみる

この辺は適当に…。

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    ExpandableListView exListView = (ExpandableListView)findViewById(R.id.ex_listview);
    
    List<ExpandableParent<String>> parents = new ArrayList<ExpandableParent<String>>();
    
    parents.add(new ExpandableParent<String>("親要素だけ", new V1<String>() {
        @Override
        public void call(String arg) {
            Toast.makeText(getApplicationContext, arg, Toast.LENGTH_SHORT).show();
        }
    });
    
    ExpandableParent<String> parent = new ExpandableParent<String>("子要素もあるやつ");
    parent.addChild(new ExpandableChild<String>("こども",new V1<String>() {
        @Override
        public void call(String arg) {
            Toast.makeText(getApplicationContext, arg, Toast.LENGTH_SHORT).show();
        }
    });
    
    parents.add(parent);
    
    exListView.setAdapter(new EventExpandableAdapater<String>(parents, getApplicationContext());
    
    exListView.setOnGroupClickListener(new OnGroupClickListener() {
        @Override
        public boolean onGroupClick(ExpandableListView parentListView, View v, int groupPosition, long id) {
            EventExpandableAdapater<String> adapter = (EventExpandableAdapater<String>)parentListView.getAdapter();
            
            if(adapter.hasChild(groupPosition)) return false;
            
            ExpandableParent<EventArg> parent = adapter.getParent(groupPosition);
            parent.callEvent(parent.getDisplayName());
            
            return true;
        }
    });
    
    exListView.setOnChildClickListener(new OnChildClickListener() {
        @Override
        public boolean onChildClick(ExpandableListView parentListView, View v, int groupPosition, int childPosition, long id) {
            EventExpandableAdapater<String> adapter = (EventExpandableAdapater<String>)parentListView.getAdapter();
            ExpandableChild<String> child = (ExpandableChild<String>)adapter.getChild(groupPosition, childPosition);
            
            if(child != null) child.callEvent(child.getDisplayName());
            
            return false;
        }
    });
    
}

うーん、ジェネリックな部分がダサいですね…。

EventExpandableAdapaterの_parentsの型パラメータをワイルドカードにすればもうちょっとマシになりそうですが、型安全もクソもなくなるのでなんとも。

子要素がない場合のIndicatorの制御

これが参考になりそうです。

まとめ

実際にやってみないことにはなんとも。年末年始にやってみますかね…。

[2014/02/08追記]完成版はこちら。

参考