Android
 Computer >> コンピューター >  >> プログラミング >> Android

【Android】ExpandableListViewでマルチレベル(階層型)リストを作成する方法

Androidアプリ開発では、カテゴリごとに項目を整理したマルチレベル(階層型)リストを実装したいケースが多くあります。本記事では、ExpandableListViewを使用して、親項目(グループ)をタップすると子項目が展開される2階層リストを作成する方法を、サンプルコードとともにステップごとに解説します。

ExpandableListViewとは?

ExpandableListViewは、親項目(グループ)と子項目からなる2階層構造のリストを表示できるAndroid標準ウィジェットです。設定画面やFAQなど、階層的なデータをわかりやすく見せたい場面で活用できます。今回はスポーツ選手の一覧を例に、実装手順を紹介します。

実装手順

ステップ1:新規プロジェクトの作成

Android Studioを起動し、メニューから「File」→「New Project」を選択します。必要な情報を入力して、新しいプロジェクトを作成してください。

ステップ2:activity_main.xmlにコードを追加

res/layout/activity_main.xml を以下のように編集し、ExpandableListViewを配置します。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:padding="4dp"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ExpandableListView
        android:id="@+id/expendableList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="@android:color/background_light"
        android:dividerHeight="0.5dp"/>

</RelativeLayout>

ステップ3:MainActivity.javaにコードを追加

src/MainActivity.java に以下のコードを記述します。ここでは、グループの展開・折りたたみ、子項目のクリック時にToastメッセージを表示する処理を実装しています。

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    ExpandableListView expandableListView;
    ExpandableListAdapter expandableListAdapter;
    List<String> expandableListTitle;
    HashMap<String, List<String>> expandableListDetail;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        expandableListView = findViewById(R.id.expendableList);
        expandableListDetail = ExpandableListData.getData();
        expandableListTitle = new ArrayList<>(expandableListDetail.keySet());
        expandableListAdapter = new CustomExpandableListAdapter(this, expandableListTitle, expandableListDetail);
        expandableListView.setAdapter(expandableListAdapter);

        // グループ展開時の処理
        expandableListView.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
            @Override
            public void onGroupExpand(int groupPosition) {
                Toast.makeText(getApplicationContext(),
                        expandableListTitle.get(groupPosition) + " List Expanded.",
                        Toast.LENGTH_SHORT).show();
            }
        });

        // グループ折りたたみ時の処理
        expandableListView.setOnGroupCollapseListener(new ExpandableListView.OnGroupCollapseListener() {
            @Override
            public void onGroupCollapse(int groupPosition) {
                Toast.makeText(getApplicationContext(),
                        expandableListTitle.get(groupPosition) + " List Collapsed.",
                        Toast.LENGTH_SHORT).show();
            }
        });

        // 子項目クリック時の処理
        expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
            @Override
            public boolean onChildClick(ExpandableListView parent, View v,
                                        int groupPosition, int childPosition, long id) {
                Toast.makeText(getApplicationContext(),
                        expandableListTitle.get(groupPosition)
                                + " -> "
                                + expandableListDetail.get(expandableListTitle.get(groupPosition)).get(childPosition),
                        Toast.LENGTH_SHORT).show();
                return false;
            }
        });
    }
}

ステップ4:データクラス(ExpandableListData.java)の作成

Javaクラス「ExpandableListData.java」を新規作成し、以下のコードを記述します。グループ名をキー、子項目のリストを値としてHashMapに格納しています。

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

class ExpandableListData {
    static HashMap<String, List<String>> getData() {
        HashMap<String, List<String>> expandableListDetail = new HashMap<>();

        List<String> myFavCricketPlayers = new ArrayList<>();
        myFavCricketPlayers.add("MS.Dhoni");
        myFavCricketPlayers.add("Sehwag");
        myFavCricketPlayers.add("Shane Watson");
        myFavCricketPlayers.add("Ricky Ponting");
        myFavCricketPlayers.add("Shahid Afridi");

        List<String> myFavFootballPlayers = new ArrayList<String>();
        myFavFootballPlayers.add("Cristiano Ronaldo");
        myFavFootballPlayers.add("Lionel Messi");
        myFavFootballPlayers.add("Gareth Bale");
        myFavFootballPlayers.add("Neymar JR");
        myFavFootballPlayers.add("David de Gea");

        List<String> myFavTennisPlayers = new ArrayList<String>();
        myFavTennisPlayers.add("Roger Federer");
        myFavTennisPlayers.add("Rafael Nadal");
        myFavTennisPlayers.add("Andy Murray");
        myFavTennisPlayers.add("Novak Jokovic");
        myFavTennisPlayers.add("Sania Mirza");

        expandableListDetail.put("CRICKET PLAYERS", myFavCricketPlayers);
        expandableListDetail.put("FOOTBALL PLAYERS", myFavFootballPlayers);
        expandableListDetail.put("TENNIS PLAYERS", myFavTennisPlayers);

        return expandableListDetail;
    }
}

ステップ5:カスタムアダプター(CustomExpandableListAdapter.java)の作成

Javaクラス「CustomExpandableListAdapter.java」を新規作成し、BaseExpandableListAdapterを継承した以下のコードを記述します。このアダプターが、グループと子項目それぞれのビュー生成を担当します。

package app.com.sample;

import android.content.Context;
import android.graphics.Typeface;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.TextView;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;

class CustomExpandableListAdapter extends BaseExpandableListAdapter {

    private Context context;
    private List<String> expandableListTitle;
    private HashMap<String, List<String>> expandableListDetail;

    CustomExpandableListAdapter(Context context, List<String> expandableListTitle,
                                HashMap<String, List<String>> expandableListDetail) {
        this.context = context;
        this.expandableListTitle = expandableListTitle;
        this.expandableListDetail = expandableListDetail;
    }

    @Override
    public Object getChild(int listPosition, int expandedListPosition) {
        return Objects.requireNonNull(
                this.expandableListDetail.get(this.expandableListTitle.get(listPosition)))
                .get(expandedListPosition);
    }

    @Override
    public long getChildId(int listPosition, int expandedListPosition) {
        return expandedListPosition;
    }

    @Override
    public View getChildView(int listPosition, final int expandedListPosition,
                             boolean isLastChild, View convertView, ViewGroup parent) {
        final String expandedListText = (String) getChild(listPosition, expandedListPosition);
        if (convertView == null) {
            LayoutInflater layoutInflater = (LayoutInflater) this.context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = Objects.requireNonNull(layoutInflater)
                    .inflate(R.layout.list_row, null);
        }
        TextView textView = convertView.findViewById(R.id.listTitle);
        textView.setText(expandedListText);
        return convertView;
    }

    @Override
    public int getChildrenCount(int listPosition) {
        return this.expandableListDetail
                .get(this.expandableListTitle.get(listPosition)).size();
    }

    @Override
    public Object getGroup(int listPosition) {
        return this.expandableListTitle.get(listPosition);
    }

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

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

    @Override
    public View getGroupView(int listPosition, boolean isExpanded,
                             View convertView, ViewGroup parent) {
        String listTitle = (String) getGroup(listPosition);
        if (convertView == null) {
            LayoutInflater layoutInflater = (LayoutInflater) this.context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = Objects.requireNonNull(layoutInflater)
                    .inflate(R.layout.list_row, null);
        }
        TextView listTitleTextView = convertView.findViewById(R.id.listTitle);
        // グループタイトルは太字で表示
        listTitleTextView.setTypeface(null, Typeface.BOLD);
        listTitleTextView.setText(listTitle);
        return convertView;
    }

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

    @Override
    public boolean isChildSelectable(int listPosition, int expandedListPosition) {
        return true;
    }
}

ステップ6:行レイアウト(list_row.xml)の作成

レイアウトリソースファイル「list_row.xml」を新規作成し、以下のコードを追加します。グループ・子項目どちらの行にも使用されるシンプルなTextViewです。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/listTitle"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:textColor="@android:color/black" />

</LinearLayout>

ステップ7:AndroidManifest.xmlの確認

androidManifest.xml にMainActivityの宣言が含まれていることを確認します。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="app.com.sample">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

アプリの実行と動作確認

それではアプリを実行してみましょう。実機のAndroid端末をパソコンに接続していることを前提とします。Android Studioでプロジェクト内の任意のアクティビティファイルを開き、ツールバーの「Run」アイコンをクリックします。デバイス選択ダイアログで自分のモバイル端末を選択すると、端末の画面に以下のようなマルチレベルリストが表示されます。

【Android】ExpandableListViewでマルチレベル(階層型)リストを作成する方法

グループ(CRICKET PLAYERS、FOOTBALL PLAYERS、TENNIS PLAYERS)をタップすると該当カテゴリの子項目が展開され、展開・折りたたみ・子項目クリックのそれぞれのタイミングでToastメッセージが表示されます。

【Android】ExpandableListViewでマルチレベル(階層型)リストを作成する方法

まとめ

本記事では、ExpandableListViewとBaseExpandableListAdapterを組み合わせることで、Androidアプリに2階層のマルチレベルリストを簡単に実装できることを確認しました。データ部分(ExpandableListData)と表示部分(アダプター)を分離した構成にしておくと、JSONやデータベースから取得したデータへの差し替えも容易になります。ぜひ自身のアプリにも応用してみてください。

  1. AndroidアプリでTextToSpeech(音声読み上げ)機能を実装する方法をわかりやすく解説

    このチュートリアルでは、AndroidアプリにTextToSpeech(音声合成・TTS)機能を実装し、入力したテキストを音声で読み上げる方法を解説します。サンプルでは、シークバーを使って音声のピッチ(声の高さ)とスピード(話す速さ)を調整できる、実用的な構成になっています。 手順1:Android Studioで新規プロジェクトを作成 Android Studioを起動し、メニューから File ⇒ New Project を選択します。必要な項目をすべて入力して、新しいプロジェクトを作成しましょう。 手順2:レイアウトファイル(activity_main.xml)を編集 res/layou

  2. FacebookでAndroidアプリを作成する方法|開発者サイトでの設定手順を徹底解説

    この記事では、FacebookでAndroidアプリを作成する方法について詳しく解説します。AndroidアプリとFacebookを連携させるには、Facebook開発者サイトでFacebookアプリを作成し、Facebook App IDを取得する必要があります。以下の手順に従って、順番に進めていきましょう。 準備:Facebook開発者サイトで新しいアプリを追加する まず、https://developers.facebook.com/ にアクセスし、「新しいアプリを追加」をクリックしてアプリの作成を開始します。 ステップ1:アプリ名とメールアドレスを入力する 指定されたフィールドに、作