🟰

【Flutter】様々なListTileを理解する

2024/05/11に公開

ListTileとは

FlutterでのListTileとは、iOSでいうところのTableViewCell、AndroidでいうところのListViewのlist itemに相当します。
アプリでの使われることの多いウィジェットの一つで、設定などの画面で段組みに項目を作成する際などに使われます。

class MainApp extends StatelessWidget {
  MainApp({super.key});

  final animals = [
    (animal: 'Dog', emoji: '🐶', icon: Icons.pets),
    (animal: 'Cat', emoji: '🐱', icon: Icons.abc),
    (animal: 'Bird', emoji: '🐤', icon: Icons.ac_unit)
  ];

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: ListView(
            children: animals
                .expand((e) => [
                      ListTile(
                        title: Text(e.animal),
                        leading: Icon(e.icon),
                        subtitle: Text(
                            '${e.emoji} This is a ${e.animal.toLowerCase()}'),
                      ),
                      const Divider(),
                    ])
                .toList(),
          ),
        ),
      ),
    );
  }
}

詳しくはこちらを参照してください
https://youtu.be/l8dj0yPBvgQ

ListTileの種類

FlutterでのListTileは一般的なListTileから、CheckBoxListTile、RadioListTile、SwitchListTileと、ネイティブよりも、細かくウィジェットの種類が用意されています。それぞれの挙動、仕様の違いについて理解していきます。

ListTile

ListTileの基本となります。タイルのアイコンは先頭と末尾のパラメータで定義されます。

  • isThreeLine = trueの場合は、subtitleのスペースが2行割当られます。subtitleのテキストがそれ以上であれば、isThreeLineの値に限らず、自動的に割り当てられます。デフォルトではfalseになっています。
isThreeLine = true isThreeLine = false
  • dense = trueの場合、このタイルの全体の高さと、タイトルとラブタイトルウィジェットをラップするDefaultTextStylesのサイズは縮小します。
  • このウィジェットには、それ自体を描画するツリー内のマテリアルウィジェットの祖先が必要になってきます。これは、アプリのScaffoldによって提供されることが一般的です。
  • ListTileのColorプロパティ(tileColor, selectedColor, focusColor, hoverColor)はListTile自体ではなく、祖先にあるマテリアルウィジェットによって描画されます。そのため以下のような書き方ができます。
const ColoredBox(
  color: Colors.green,
  child: Material(
    child: ListTile(
      title: Text('ListTile with red background'),
      tileColor: Colors.red,
    ),
  ),
)

しかし、多数のListTileを個別にMaterialでラッピングするのはコストがかかるため、必要なListTilesに対してラッピングするか、共通のMaterialを祖先とすることが望ましいです。なお、他のCheckBoxListTile,RadioListTile,SwitchListTileも同様に必要となります。

アイコン

  • タップ可能な先頭と末尾のウィジェットサイズは少なくとも48x48である必要があります。しかし、Material仕様に準拠するために、1行のListTileの末尾と先頭のウィジェットは視覚的に高さが最大32(dense: true) or 40(dense: false)であるべきで、アクセシビリティの要件とコンフリクトする可能性があります。
  • このため、1行のListTileでは、先頭と末尾のウィジェットの高さをListTileの高さに制約することができます。これにより、十分な大きさのタップ可能な先頭と末尾のウィジェットを作成できます。

ListTileは便利ですが、実現したいものが当てはまらないときは、自分でオリジナルのウィジェットを作ることも検討してください。

CheckBoxListTile

https://youtu.be/RkSqPAn9szs

class MainApp extends StatefulWidget {
  
  State<StatefulWidget> createState() => _MainApp();
}

class _MainApp extends State<MainApp> {
  final animals = [
    Animal(animal: 'Dog', emoji: '🐶', icon: Icons.pets, checked: false),
    Animal(animal: 'Cat', emoji: '🐱', icon: Icons.abc, checked: false),
    Animal(animal: 'Bird', emoji: '🐤', icon: Icons.ac_unit, checked: false)
  ];

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: ListView(
            children: animals
                .expand((e) => [
                      CheckboxListTile(
                        value: e.checked,
                        onChanged: (v) {
                          setState(() {
                            e.checked = v!;
                          });
                        },
                        title: Text(e.animal),
                        subtitle: Text(
                            '${e.emoji} This is a ${e.animal.toLowerCase()}'),
                      ),
                      const Divider(),
                    ])
                .toList(),
          ),
        ),
      ),
    );
  }
}

class Animal {
  String animal;
  String emoji;
  IconData icon;
  bool checked;

  Animal(
      {required this.animal,
      required this.emoji,
      required this.icon,
      required this.checked});
}

  • チェックボックスは日本語、英語のように左から右に読む言語では、デフォルトで右に表示されます。これはcontrolAffinityを使って変更することができます。

RadioListTile

  • ラジオボタンつきのListTileです。
class MainApp extends StatefulWidget {
  
  State<StatefulWidget> createState() => _MainApp();
}

class _MainApp extends State<MainApp> {
  final animals = [
    Animal(animal: 'Dog', emoji: '🐶', icon: Icons.pets, checked: false),
    Animal(animal: 'Cat', emoji: '🐱', icon: Icons.abc, checked: false),
    Animal(animal: 'Bird', emoji: '🐤', icon: Icons.ac_unit, checked: false)
  ];
  int selectedAnimal = 0;

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: ListView.builder(
            itemCount: animals.length,
            itemBuilder: (context, index) {
              final e = animals[index];
              return RadioListTile(
                value: index,
                groupValue: selectedAnimal,
                onChanged: (v) {
                  setState(() {
                    selectedAnimal = v!;
                  });
                },
                title: Text(e.animal),
                subtitle:
                    Text('${e.emoji} This is a ${e.animal.toLowerCase()}'),
                secondary: Icon(e.icon),
              );
            },
          ),
        ),
      ),
    );
  }
}

class Animal {
  String animal;
  String emoji;
  IconData icon;
  bool checked;

  Animal(
      {required this.animal,
      required this.emoji,
      required this.icon,
      required this.checked});
}

  • ラジオボタンは日本語、英語のように左から右に読む言語では、デフォルトで左に表示されます。これはcontrolAffinityを使って変更することができます。
  • 他のListTileと違うのは、groupValueという引数があり、これによって、排他的な選択を実現しています。

SwitchListTile

https://youtu.be/0igIjvtEWNU

  • スイッチボタン(Androidではトグルスイッチ)つきのListTileです。
class MainApp extends StatefulWidget {
  
  State<StatefulWidget> createState() => _MainApp();
}

class _MainApp extends State<MainApp> {
  final animals = [
    Animal(animal: 'Dog', emoji: '🐶', icon: Icons.pets, checked: false),
    Animal(animal: 'Cat', emoji: '🐱', icon: Icons.abc, checked: false),
    Animal(animal: 'Bird', emoji: '🐤', icon: Icons.ac_unit, checked: false)
  ];
  int selectedAnimal = 0;

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: ListView.builder(
            itemCount: animals.length,
            itemBuilder: (context, index) {
              final e = animals[index];
              return SwitchListTile(
                value: e.checked,
                onChanged: (v) {
                  setState(() {
                    e.checked = v;
                  });
                },
                title: Text(e.animal),
                subtitle:
                    Text('${e.emoji} This is a ${e.animal.toLowerCase()}'),
                secondary: Icon(e.icon),
              );
            },
          ),
        ),
      ),
    );
  }
}

class Animal {
  String animal;
  String emoji;
  IconData icon;
  bool checked;

  Animal(
      {required this.animal,
      required this.emoji,
      required this.icon,
      required this.checked});
}

  • スイッチボタンは日本語、英語のように左から右に読む言語では、デフォルトで右に表示されます。

おわりに

これまで見てきたListTileの種類をまとめると、次のようになります。

ListTile CheckBoxListTile RadioListTile SwitchListTile

FlutterではListTileのウィジェットが様々あり、シンプルなリストを作成するのがネイティブより簡単な場合があります。
それぞれの使い方に応じて適切なListTileを選択し、場合によってはオリジナルのウィジェットでリストを表現することができると良さそうです。

参考にさせていただいた記事とか

https://api.flutter.dev/flutter/material/ListTile-class.html

GitHubで編集を提案

Discussion