マテリアルデザインを採用したアプリでは、ナビゲーションにタブとドロワーという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 ウィジェットは、マテリアルデザインガイドラインに従ったアプリに一貫したビジュアル構造を提供します。また、Drawers、AppBars、SnackBars といったマテリアルデザインの特別なコンポーネントもサポートしている。
Scaffold(
appBar: AppBar(
title: const Text('AppBar without hamburger button'),
),
drawer: // Add a Drawer here in the next step.
);
2.引き出し( 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 ウィジェットを入力します。
(リストに関する詳しい情報は、リストのレシピを参照して下さい。)
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にあるメニューアイコン(ハンバーガーアイコン)をカスタムで定義して、そのアイコンをタップするとドロワーが開く仕組みを実装しています。
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.インタラクティブな例
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);
},
),
],
),
),
);
}
}