うちなー えんじにあ ぶろぐ

開発に関するブログです 沖縄県民 Android好き

ExpandableListViewでセクション表示をキレイに対応!

なにかと使うSection表示して!
iOSっぽいあのUIで!
iOSはデフォルトでSectiontとか対応していますが、、、
Androidは作らんといけんどす。。。
そんなときにはExpandableListView!はい使っていきましょう!!

ExpandableListViewの使い方

  • 概要
  • XML
  • 個人的にわかりやすいデータの持ち方
  • Adapter概要
  • その他の大事なやつ

最終ゴール

f:id:kamiya-kizuku:20180303155002p:plain

環境

AndroidStudio 3
Java
DataBinding

概要

そもそもExpandableListViewはTree構造のList表示を行う時に利用するものだと勝手に思っていますw
Tree構造だと開いたり閉じたりできるイメージがありますが、そもそも閉じなければデータの持ち方とかもSectionと同じ形で実装できるので、良く使ってます。
ListViewでもSectionを実現できますが、ExpandableListViewの方が、複雑にならないのでおすすめです!

XML

それでは早速、xmlどん!

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.example.expandablelistview.MainActivity">

        <ExpandableListView
            android:id="@+id/expandable_listview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:groupIndicator="@null"/>

    </LinearLayout>
</layout>

1点特殊設定が入っています。
> android:groupIndicator="@null"
ですね。
ExpandableListViewって初期でIndicatorがついてくるんです。
Tree表示を行った際に、開いているか閉じているかっていうのを視覚的にわかるようにするための物?だと思っている感じww
今回は使わないので、@nullを設定し消しています!

見たほうが早いので下記にキャプチャ貼っときます。
f:id:kamiya-kizuku:20180303155554p:plain

個人的にわかりやすいデータの持ち方

まずどういったデータの構造になっているのか?
というところですが、

  • Section1
    • Row1
    • Row2
  • Section2
    • Row1
    • Row2
  • ・・・

って感じw
Javaで書くとSectionというObjectをまず配列で保持
そのSection Objectの中にRow Objectの配列を保持しているという構造が良いと思います。
言葉にすると難しい。。。
プログラムどん!!ww

/**
 * Sectionに表示するデータ
 * Created by pdc-k-kamiya on 2018/03/03.
 */

public class Section {
    private String mSectionText;
    private List<Row> mRows;

    public Section() {
        mSectionText = "";
        mRows = new ArrayList<>();
    }

    public String getSectionText() {
        return mSectionText;
    }

    public void setSectionText(String sectionText) {
        mSectionText = sectionText;
    }

    public List<Row> getRows() {
        return mRows;
    }

    public void setRows(List<Row> rows) {
        mRows = rows;
    }
}

/**
 * Row に表示するデータ
 * Created by pdc-k-kamiya on 2018/03/03.
 */
public class Row {
    private String mRowText;

    public Row() {
        mRowText = "";
    }

    public String getRowText() {
        return mRowText;
    }

    public void setRowText(String rowText) {
        mRowText = rowText;
    }
}

伝わりますかね?
SectionのClassがRowのArrayを持っている感じです。
そのSectionを配列で持てばデータ構造としてはOKかと思います。

Adapter概要

今回はBaseExpandableListAdapterというのを継承して作っています。
Adapterの役割は前回ListViewで説明した通りですが、
少し違う点がありますので、その説明を行います。
※前回のAdapter説明
kamiya-kizuku.hatenablog.com

とりあえずプログラムどん!!!

/**
 * Adapter Class
 * Created by pdc-k-kamiya on 2018/03/03.
 */
public class ExpandableAdapter extends BaseExpandableListAdapter {

    private List<Section> mList;

    public ExpandableAdapter() {
        mList = new ArrayList<>();
    }

    public List<Section> getList() {
        return mList;
    }

    public void setList(List<Section> list) {
        mList = list;
    }

    @Override
    public int getGroupCount() {
        return mList.size();
    }

    @Override
    public int getChildrenCount(int groupPosition) {
        return getGroup(groupPosition).getRows().size();
    }

    @Override
    public Section getGroup(int groupPosition) {
        return mList.get(groupPosition);
    }

    @Override
    public Row getChild(int groupPosition, int childPosition) {
        return getGroup(groupPosition).getRows().get(childPosition);
    }

    @Override
    public long getGroupId(int groupPosition) {
        return groupPosition;
    }

    @Override
    public long getChildId(int groupPosition, int childPosition) {
        return childPosition;
    }

    @Override
    public boolean hasStableIds() {
        return false;
    }

    /**
     * Section表示のView
     *
     * @param groupPosition int
     * @param isExpanded    boolean 開いているかどうか
     * @param convertView   View
     * @param parent        ViewGroup
     * @return View
     */
    @Override
    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
        SectionRowBinding binding;
        if (convertView == null) {
            binding = SectionRowBinding.inflate(LayoutInflater.from(parent.getContext()));
            convertView = binding.getRoot();
            convertView.setTag(binding);
        } else {
            binding = (SectionRowBinding) convertView.getTag();
        }

        binding.setModel(getGroup(groupPosition));

        return convertView;
    }

    /**
     * Section表示の中で表示するView
     *
     * @param groupPosition int
     * @param childPosition int
     * @param isLastChild   boolean グループ内の最後の子Viewかどうか
     * @param convertView   View
     * @param parent        ViewGroup
     * @return View
     */
    @Override
    public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
        RowBinding binding;
        if (convertView == null) {
            binding = RowBinding.inflate(LayoutInflater.from(parent.getContext()));
            convertView = binding.getRoot();
            convertView.setTag(binding);
        } else {
            binding = (RowBinding) convertView.getTag();
        }

        binding.setModel(getChild(groupPosition, childPosition));

        return convertView;
    }

    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        // ここをtrueに設定しないと小ビューのClickが反応しない
        return true;
    }
}

特殊な点は2点!

getGroupCount, getChildrenCount, getGroupView, getChildView

これはわかりやすいと思います。
Count系のやつは、単純にSectionの数、Rowの数です。
それに伴い、
表示するViewをgetGroupView,getChildViewで返却します。
ListViewと違って、2個あるという点が違う部分ですね。

isChildSelectable

子Viewのクリックイベントを取得するかどうかのフラグを返却する物です。
ここをtrueにしないとクリックイベントが取得できないので、要注意!!
※以外にハマリポイントですw

とまぁこんな感じですね。

その他の大事なやつ

今回Activity側に処理を書いていますが、大事な3点!

初期表示はListが閉じている

ExpandableListViewに関して、初期表示は閉じています。
なので、開く処理を設定する必要があります。
MainActivityに書いていますが、ExpandableListViewのメソッドexpandGroup(int position)というので、展開する処理を記載しています。

// 初期表示をリスト展開した状態に設定
for(int i = 0; i < mBinding.expandableListview.getExpandableListAdapter().getGroupCount(); i++){
    mBinding.expandableListview.expandGroup(i);
}

onClickListenerでは無い!

ExpnadableListViewでイベントを取得するために、
2つの設定があります。
setOnGroupClickListenerとsetOnChildClickListenerです。
その名の通り、今回でいうSectionクリック処理と子ViewClick処理です。

mBinding.expandableListview.setOnGroupClickListener(this);
mBinding.expandableListview.setOnChildClickListener(this);

セクションタップで閉じないように

最初で説明したとおり、ExpandableListViewはTree構造を展開するようなViewです。
Section表示では閉じないように設定しておいた方が良い場合もあります。
そのため、通常ではクリックすると閉じる処理が入っていますが、
閉じないように設定するため、onGroupClickのreturnでtrueを返却する必要があります。

@Override
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
    // return trueで折りたたまれなくなる、falseで折りたたまれる
    return true;
}

はい!今回はこんな感じで終了です!
必要な方是非ご参考までに!
なにかあればコメントして下さーい!

Github

github.com
あげてますー