[読書会]画面に引き出し(Drawer)を追加する

 マテリアルデザインを採用したアプリでは、ナビゲーションにタブドロワーという2つの主要な選択肢がある。
タブを支えるスペースが不十分な場合は、ドロワーが便利な代替手段となります。
 Flutterの公式ドキュメント(URL: https://docs.flutter.dev/cookbook/design/drawer)で紹介されている、”Add a drawer to a screen” に関する説明を解説します。

 Drawer ウィジェットScaffold 組み合わせて使い、Material Design のドロワーを持つレイアウトを作成します。

1.足場( Scaffold )の配置

 アプリに drawer を追加するには、Scaffold ウィジェットでラップします。Scaffold ウィジェットは、マテリアルデザインガイドラインに従ったアプリに一貫したビジュアル構造を提供します。また、DrawersAppBarsSnackBars といったマテリアルデザインの特別なコンポーネントもサポートしている。

アプリに drawer を追加するには、Scaffold ウィジェットでラップします。
Scaffold(
  appBar: AppBar(
    title: const Text('AppBar without hamburger button'),
  ),
  drawer: // Add a Drawer here in the next step.
);

2.引き出し( drawer )の追加

 次は、足場( Scaffold )に引き出し( drawer )を追加します。ドロワーはどのようなウィジェットでもかまいませんが、マテリアルデザイン仕様に準拠したマテリアルライブラリのドロワーウィジェットを使用するのがベストです。

足場( Scaffold )に引き出し( drawer )を追加します。
Scaffold(
  appBar: AppBar(
    title: const Text('AppBar with hamburger button'),
  ),
  drawer: Drawer(
    child: // Populate the Drawer in the next step.
  ),
);

3.引き出し( drawer )へのアイテム( items )配置

 これで Drawer が配置できたので、そこにコンテンツ追加します。
(この例では、ListView を使用します。 Columnウィジェットを使用することもできますが、ListViewは、コンテンツがスクリーンがサポートする以上のスペースを取る場合、ユーザーがドロワーをスクロールすることができるので便利です。)

 ListView に DrawerHeader 2つの ListTile ウィジェットを入力します。
(リストに関する詳しい情報は、リストのレシピを参照して下さい。)

ListView に DrawerHeader と 2つの ListTile ウィジェットを入力します。
Drawer(
        //ListViewでDrawer内にスクロール可能なリストを作成
        child: ListView(
          //ListViewのデフォルトパディングを削除
          padding: EdgeInsets.zero,
          children: [
            //ドロワーヘッダーを追加
            const DrawerHeader(
              decoration: BoxDecoration(
                color: Colors.blue,
              ),
              child: Text('Drawer Header'),
            ),
            //ホームタブのListTile
            ListTile(
              title: const Text('Home'),
              //選択状態の時にハイライト
              selected: _selectedIndex == 0,
              onTap: () {
                //ホームタブが選択された時の処理
                _onItemTapped(0);
                //ドロワーを閉じる
                Navigator.pop(context);
              },
            ),
            //ビジネスタブのListTile
            ListTile(
              title: const Text('Business'),
              //選択状態の時にハイライト
              selected: _selectedIndex == 1,
              onTap: () {
                //ビジネスタブが選択された時の処理
                _onItemTapped(1);
                //ドロワーを閉じる
                Navigator.pop(context);
              },
            ),
            //スクールタブのListTile
            ListTile(
              title: const Text('School'),
              //選択状態の時にハイライト
              selected: _selectedIndex == 2,
              onTap: () {
                //スクールタブが選択された時の処理
                _onItemTapped(2);
                //ドロワーを閉じる
                Navigator.pop(context);
              },
            ),
          ],
        ),
      ),

4.プログラムで引き出し( drawer )を開く

 通常、AppBar(アプリの上部に表示されるツールバー)には、ドロワーを開くためのハンバーガーアイコン」が自動的に追加されます。これは、leading プロパティが null であれば、AppBar は自動的にDrawerButtonというデフォルトのボタンを表示するからです。しかし、特定のタイミングでドロワーを開きたい場合や、カスタマイズしたい場合には、Scaffold.of(context).openDrawer() を使って、プログラム的にドロワーを開くことができます。

 以下では、AppBarにあるメニューアイコン(ハンバーガーアイコン)をカスタムで定義して、そのアイコンをタップするとドロワーが開く仕組みを実装しています。

AppBarにあるメニューアイコン(ハンバーガーアイコン)をカスタムで定義して、そのアイコンをタップするとドロワーが開く仕組みを実装(例)
Scaffold(
  appBar: AppBar(
    title: const Text('AppBar with hamburger button'),
    leading: Builder( // Builderを使って独自のcontextを作成
      //Builderウィジェットは、新しいcontextを生成するために使用されています。
      //これは、Scaffold.of(context)が親Scaffoldを見つけるために、
      //contextがScaffoldの子である必要があるからです。
      builder: (context) {
        return IconButton(
          icon: const Icon(Icons.menu), // ハンバーガーメニューアイコン
          onPressed: () {
            // Scaffold.of(context).openDrawer()でドロワーを開く
            Scaffold.of(context).openDrawer();
          },
        );
      },
    ),
  ),
  drawer: Drawer(
    child: // Populate the Drawer in the last step.
  ),
);

5.プログラムで引き出し( drawer )を閉じる

 ユーザがドロワーを開くと、ドロワーをナビゲーションスタックに追加します。これにより、通常の画面遷移と同様に、ドロワーもナビゲーションスタックに含まれるため、ドロワーを閉じる際には Navigator.pop(context) を使用してスタックからドロワーをポップ(取り除く)することで閉じることができます

リストの項目をタップしたときに、状態を更新した後にドロワーを閉じる(例):
ListTile(
  title: const Text('Item 1'),
  onTap: () {
    //アプリの状態を更新
    //...
    //ドロワーを閉じる
    Navigator.pop(context);
  },
),

6.インタラクティブな例

画面に引き出し(Drawer)を追加する(例):
import 'package:flutter/material.dart';

//アプリのエントリーポイント
void main() => runApp(const MyApp());

//StatelessWidgetでアプリ全体を定義
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  //アプリのタイトルを定数として定義
  static const appTitle = 'Drawer Demo';

  @override
  Widget build(BuildContext context) {
    //MaterialAppでアプリ全体をラップし、MyHomePageをホーム画面に設定
    return const MaterialApp(
      title: appTitle,
      home: MyHomePage(title: appTitle),
    );
  }
}

//StatefulWidgetでホームページを定義
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  //タイトルを受け取るプロパティを定義
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

//Stateクラスでホームページの状態を管理
class _MyHomePageState extends State<MyHomePage> {
  int _selectedIndex = 0;  // 現在選択されているインデックスを保存

  //各タブに表示するウィジェットのリスト
  static const TextStyle optionStyle =
      TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
  static const List<Widget> _widgetOptions = <Widget>[
    Text(
      'Index 0: Home',
      style: optionStyle,
    ),
    Text(
      'Index 1: Business',
      style: optionStyle,
    ),
    Text(
      'Index 2: School',
      style: optionStyle,
    ),
  ];

  //インデックスを変更する関数。setStateで再ビルドをトリガー
  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        //ウィジェットのタイトルをAppBarに表示
        title: Text(widget.title),
        //AppBarにメニューアイコンを追加し、ドロワーを開けるようにする
        leading: Builder(
          builder: (context) {
            return IconButton(
              icon: const Icon(Icons.menu),  //メニューアイコン
              onPressed: () {
                Scaffold.of(context).openDrawer();  //ドロワーを開く
              },
            );
          },
        ),
      ),
      //メインコンテンツ部分
      body: Center(
        //選択されたインデックスのウィジェットを表示
        child: _widgetOptions[_selectedIndex],
      ),
      //Drawerの定義
      drawer: Drawer(
        //ListViewでDrawer内にスクロール可能なリストを作成
        child: ListView(
          //ListViewのデフォルトパディングを削除
          padding: EdgeInsets.zero,
          children: [
            //ドロワーヘッダーを追加
            const DrawerHeader(
              decoration: BoxDecoration(
                color: Colors.blue,
              ),
              child: Text('Drawer Header'),
            ),
            //ホームタブのListTile
            ListTile(
              title: const Text('Home'),
              //選択状態の時にハイライト
              selected: _selectedIndex == 0,
              onTap: () {
                //ホームタブが選択された時の処理
                _onItemTapped(0);
                //ドロワーを閉じる
                Navigator.pop(context);
              },
            ),
            //ビジネスタブのListTile
            ListTile(
              title: const Text('Business'),
              //選択状態の時にハイライト
              selected: _selectedIndex == 1,
              onTap: () {
                //ビジネスタブが選択された時の処理
                _onItemTapped(1);
                //ドロワーを閉じる
                Navigator.pop(context);
              },
            ),
            //スクールタブのListTile
            ListTile(
              title: const Text('School'),
              //選択状態の時にハイライト
              selected: _selectedIndex == 2,
              onTap: () {
                //スクールタブが選択された時の処理
                _onItemTapped(2);
                //ドロワーを閉じる
                Navigator.pop(context);
              },
            ),
          ],
        ),
      ),
    );
  }
}

コメントを残す