🎉

【Flutter】ドロップダウンはExpansionPanelではなくExpansionTileを使おう!

2021/11/03に公開

はじめに

以下のドロップダウンはモバイルアプリではよく見る動きだと思います。(アコーディオンメニューと呼ばれる動き)
リストのドロップダウン

ExpansionTileというWidgetを用いることでこの動きを実装することができます。

ExpansionTileと似たExpansionPanelListもあるのですが、こちらはかなり使いづらいです。こちらの不便さについても紹介します。

ExpansionTile

ExpansionTile(
	    onExpansionChanged: (bool changed) {
              //開いた時の処理を書ける
            },
            title: Text('title'),
            children: <Widget>[
              //ここにドロップダウンで出したい部分を書く
            ],
          ),

ExpansionTileで囲むだけでシンプルにドロップダウンの動きを実装できます。
他にもcolorやiconをカスタマイズすることができます。最後にサンプルコードを置いておきますのでよろしければお使いください。
詳しくは以下のドキュメントを参照してみてください!
https://api.flutter.dev/flutter/material/ExpansionTile-class.html

ExpansionTileサンプルコード
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool cute = false;
  bool beautiful = false;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ExpansionTile'),
      ),
      body: Column(
        children: <Widget>[
          ExpansionTile(
            title: Text('好きなタイプは?'),
            onExpansionChanged: (bool changed) {
              setState(() {
                cute = false;
                beautiful = changed;
              });
            },
            children: <Widget>[
              CheckboxListTile(
                value: cute,
                onChanged: (checked) {
                  setState(() {
                    cute = checked!;
                  });
                },
                title: Text('可愛い系'),
              ),
              CheckboxListTile(
                value: beautiful,
                onChanged: (checked) {
                  setState(() {
                    beautiful = checked!;
                  });
                },
                title: Text('美人系'),
              ),
            ],
          ),
          ExpansionTile(
            title: Text('説明'),
            children: <Widget>[
              Container(
                height: 50,
                child: Text('好きなタイプにチェックを入れよう!'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

ExpansionPanelList

https://api.flutter.dev/flutter/material/ExpansionPanel-class.html
こちらのドキュメントによるとExpansionPanelとは”ExpansionPanelListの子として使用される拡張パネルを作成”するものだそうです。ExpansionPanelListの子要素でしか使われないものと思われる。

ExpansionPanelList
ExpansionPanelListの動きはこのようになります。
一見良い感じに動いていますが、1番と2番の間に空白ができてしまい、空白を調整することができないため使い勝手悪いなという印象です。

最小構成が以下のコードになりそうです。(もっと良い方法あるかもしれないですが、ExpansionTileを使う方がシンプルにわかりやすく実装できると思います!)

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'expansionPanelList',
      home: Scaffold(
        appBar: AppBar(),
        body: const MyStatefulWidget(),
      ),
    );
  }
}

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

//拡大されているかどうかのbool値を持つクラスを作るのが良いっぽい
class Item {
  Item({
    required this.index,
    this.isExpanded = false,
  });

  String index;
  bool isExpanded;
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  final List<Item> _data = [
    Item(index: '1番'),
    Item(index: '2番'),
  ];

  
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Container(
        child: ExpansionPanelList(
          expansionCallback: (int index, bool isExpanded) {
	  //タップした時の挙動を書く
	  //どのタイルを開いたか書く
            setState(() {
              _data[index].isExpanded = !isExpanded;
            });
          },
	  //childrenの中はExpansionPanelのみ
          children: _data.map<ExpansionPanel>(
            (Item item) {
              return ExpansionPanel(
                headerBuilder: (BuildContext context, bool isExpanded) {
                  return ListTile(title: Text(item.index));
                },
                body: ListTile(title: Text(item.index)),
		//isExpandedがtrueだとpanelが開いた状態になる
                isExpanded: item.isExpanded,
              );
            },
          ).toList(),
        ),
      ),
    );
  }
}

う〜む、わかりづらい!

比較

ExpansionTile ExpansionPanel
リストのドロップダウン ExpansionPanelList

わかりやすさ、カスタマイズのしやすさでExpansionTileが圧倒的おすすめ!

Discussion