サイドメニューをDrawerで作る:基本と実践

サイドメニューとは、スマホ画面の左側から中央に向かってスライド表示されるメニューのこと。

ハンバーガーに例えられる三本線をクリックすると出てくるので、ハンバーガーメニューとも呼ばれます。

Flutterでサイドメニューを作るには「Drawerウィジェット」が一般的です。

この記事では「Drawer」の基本から、「Drawer」をより実践的に便利に使えるヘッダーの作成方法や、画像表示や色、アイコンをクリックせずにプログラム内部から開閉する方法など、盛りだくさんで解説します。

動作確認バージョン
  • Flutter : 3.27.1
  • Dart : 3.6.0
目次

基本編:Drawerで作るサイドメニュー

アプリ画面の右上にあるサイドメニューアイコン(画面左)が押されたあと、画面右のスマホ画面でサイドメニューが表示された状態
サイドメニューはDrawer()で簡単に作成できる

サイドメニューは、アプリの機能にアクセスするショートカットや、よく使う設定の変更項目などを配置することで、ユーザーの利便性向上に役立ちます。

Drawerの土台

FlutterのDrawerで作るサイドメニューのアイコンを、ScaffoldのappBarプロパティでAppBar()を使って表示したアプリ画面
FlutterではAppBar()Drawer()のアイコンを表示する

サイドメニューを表示するにはDrawer()ウィジェットを使いますが、その土台としてScaffold()が必要です。

その理由は、サイドメニューを表示する「三本線のハンバーガー」は、Scaffold()appBar:プロパティに指定するAppBar()の中に表示されるためです。

Drawerの土台
  • Scaffold()appBar:drawer:
    class _MyHomePageState extends State<MyHomePage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(),
          drawer: Drawer(...)
          ...

    実際にDrawer()を作成する場所は、drawer:プロパティです。(上記行番号6)

    もし仮に、drawer:Drawer()を作ったとしても、AppBar()がなければクリックする三本線アイコンが表示されません。

    AppBar()にはたくさんのプロパティがありますが、何も指定しなければ、画面上部にスペースだけが確保されます。

    サンプルコードでは、Drawer(...)表示のためだけにAppBar()を入れています。

    メニュー項目の作成

    Flutterのサイドメニューに表示されたメニュー項目2つ。テキスト表示とそれに付随するアイコンもつけられている
    サイドメニューのメニュー項目はListTile()を使う

    Drawer()の中にメニュー項目を表示するには、Column()の中でListTile()ウィジェットを使います。(下記行番号4〜)

          drawer: Drawer(
            child: Column(
              children: [
                ListTile(
                  leading: Icon(Icons.restaurant),
                  title: Text("レストラン"),
                  onTap: () {},
                ),
                ListTile(...
    ListTileウィジェットのプロパティ
      • leading::先頭にアイコンマークを表示
      • title::メニュー項目としてテキスト表示
      • onTap::項目をタップしたときの処理

      onTap:(上記行番号7)では、たとえば下記のようにNavigatorでジャンプ先のスクリーンへの移動処理を書いて、アクションをつけます。

                    onTap: () {
                      Navigator.of(context).push(MaterialPageRoute(
                          builder: (context) => RestaurantScreen()));
                    },

      Drawer()は、Padding()SizedBox()でスペースを確保しないと、画面の上部から表示されて時刻表示などと重なってしまいます。

      上記のスクショ画面は、ListTile()の前に、SizedBox(height: 100.0),を入れて作成しています。

      スペースを作らないとこんな感じ↓

      FlutterのDrawerで作成したサイドメニューが、スマホ画面の最上部から表示されている例
      スペースを開けないと画面上部から表示されてしまう

      ヘッダーをつける

      FlutterのDrawerで作成したヘッダー領域が表示されているスマホ画面
      DrawerHeader()でヘッダー領域を確保

      サイドメニューのメニュー項目の上には、アプリアイコンやアプリ名などを表示するとアクセントになります。

      ヘッダーを作るには、Drawer()の中でDrawerHeader()ウィジェットを使うと、自動でヘッダー領域が確保されます。(下記行番号4)

            drawer: Drawer(
              child: Column(
                children: [
                  DrawerHeader(
                    child: Row(
                      children: [
                        Icon(Icons.fastfood),
                        SizedBox(width: 18),
                        Text("サイドメニュー"),
                      ],
                    ),
                  ),
                  ListTile(...

      DrawerHeader()の領域の中には、Row()Column()を使ってアイコンやテキストなどを自由に配置できます。

      FlutterのDrawerで作成したヘッダー内に、大きめのアイコンと、その横に少し小さめのテキスト表示した例
      サイドメニューの中はRow()Column()で自由に作れる
                  DrawerHeader(
                    child: Padding(
                      padding: const EdgeInsets.all(14.0),
                      child: Row(
                        crossAxisAlignment: CrossAxisAlignment.end,
                        children: [
                          Icon(Icons.fastfood, size: 80),
                          SizedBox(width: 12),
                          Text("サイドメニュー"),
                        ],
                      ),
                    ),
                  ),

      実践編:Drawerのカスタマイズ

      FlutterのDrawerで作成したヘッダー内に、ユーザー情報(ユーザー名、メールアドレス)を表示し、メニュー項目を2つ作成した例
      少し凝ったDrawer()を作ってみる

      カスタムウィジェットを作成

      Flutterのプロジェクト内に、カスタムウィジェットのdartファイルとしてmain_drawer.dartを作成した例
      Drawer()ウィジェットは別のDartファイルにする

      Scaffold()drawer:Drawer()ウィジェットを直接書くと、コードの見通しが悪くなるので、カスタムウィジェットとして別ファイルを作成します。

      ここでは、MainDrawer()というウィジェット名でDrawer()を作成し、冒頭のimport文で[main_drawer.dart]をインポートします。

      import 'package:(パッケージ名)/main_drawer.dart';

      Scaffold()drawer:では、作成したカスタムウィジェットを指定します。

          return Scaffold(
            appBar: AppBar(elevation: 16),
            drawer: MainDrawer(),

      この[main_drawer.dart]のreturn文でDrawer()ウィジェットを返します。

      [main_drawer.dart]の全文

      import 'package:flutter/material.dart';
      import 'restaurant_screen.dart';
      import 'shopping_screen.dart';
      
      class MainDrawer extends StatelessWidget {
        const MainDrawer({super.key});
      
        @override
        Widget build(BuildContext context) {
          return Drawer(
            child: Column(
              children: [
                DrawerHeader(
                  padding: const EdgeInsets.all(20),
                  decoration: BoxDecoration(color: Theme.of(context).colorScheme.primaryContainer),
                  child: Row(
                    crossAxisAlignment: CrossAxisAlignment.end,
                    children: [
                      Icon(Icons.fastfood, size: 80),
                      SizedBox(width: 8.0),
                      Text("サイドメニュー",style: TextStyle(fontSize: 14.0)),
                    ],
                  ),
                ),
                ListTile(
                  leading: Icon(Icons.restaurant),
                  title: Text("レストラン"),
                  onTap: () {
                    Navigator.of(context).push(MaterialPageRoute(
                        builder: (context) => RestaurantScreen()));
                  },
                ),
                ListTile(
                  leading: Icon(Icons.shopping_cart),
                  title: Text("ショッピング"),
                  onTap: () {
                    Navigator.of(context).push(MaterialPageRoute(
                        builder: (context) => ShoppingScreen()));
                  },
                ),
              ],
            ),
          );
        }
      }

      上記サンプルコードでは、メニューからのリンク先をNavigator.of(context).push()で直接指定しています。

      メニュー項目が限定的で少ないなら、これで十分と思います。

      ヘッダーにユーザー情報を表示

      FlutterのDrawerで作成したユーザー情報のヘッダー領域の背景に、紫色を表示した例
      UserAccountsDrawerHeader()でユーザー情報を表示

      DrawerHeader()の代わりにUserAccountsDrawerHeader()を使うと、名前・メールアドレス・アイコンとなる写真をプロパティに指定するだけで、いい感じのヘッダーが作れます。

      UserAccountsDrawerHeader()という名前のウィジェットということもあって、アプリにログイン中のアカウント名・メールアドレス・アイコン画像を表示するのに使います。

                UserAccountsDrawerHeader(
                  accountName: Text("Fluttoco"),
                  accountEmail: Text("example@example.com"),
                  currentAccountPicture: CircleAvatar(
                    backgroundImage: NetworkImage("https://~~~~"),
                  ),
                  onDetailsPressed: (){
                    Navigator.of(context).push(MaterialPageRoute(
                        builder: (_) => AccountScreen()));
                  },
                ),

      currentAccountPicture:には、CircleAvatar()を使うのが一般的です。(行番号4)

      イメージ画像がネットワークを介さないと表示できない場合(例:Googleアカウントのアイコンなど)は、NetworkImage("https://~~~~"),を使えばOK。(行番号5)

      onDetailsPressed:Navigator.を使えば、アカウントの詳細情報を表示する画面へとリンクさせるのも簡単です。(行番号7-10)

      onDetailsPressed:を入れると、画面上には「▼」が表示されます。

      FlutterのDrawerでユーザー情報を表示したヘッダー部分に、詳細情報へのリンクを示す下向きの▼が表示された画面
      デフォルトの「▼」の色は白

      ヘッダー部分の装飾

      FlutterのDrawerでユーザー情報を表示したヘッダー部分の背景色を薄紫に変更し、表示されているテキストと▼マークの色をindigoにして表示した画面
      decoration:でデザインをカスタマイズ

      背景色の指定

      UserAccountsDrawerHeader()decoration:を使って色味を変えてみます。

      ここでは、BoxDecoration()gradient:を使って、グラデーションにしました。(行番号16)

      指定した色は、Color()で直接色指定することもできますが、アプリのテーマに指定されているprimaryContainerを使って左上から右下へ向かってグラデーションをかけています。(行番号17〜23)

                UserAccountsDrawerHeader(
                  accountName:
                      Text("Fluttoco", style: TextStyle(color: Colors.indigo)),
                  accountEmail: Text(
                    "example@example.com",
                    style: TextStyle(color: Colors.indigo),
                  ),
                  currentAccountPicture: CircleAvatar(
                    backgroundImage: NetworkImage("https://~~~~"),
                  ),
                  onDetailsPressed: () {
                    Navigator.of(context)
                        .push(MaterialPageRoute(builder: (_) => AccountScreen()));
                  },
                  arrowColor: Colors.indigo,
                  decoration: BoxDecoration(
                    gradient: LinearGradient(
                      colors: [
                        Theme.of(context).colorScheme.primaryContainer,
                        Theme.of(context).colorScheme.primaryContainer.withAlpha(128),
                      ],
                      begin: Alignment.topLeft,
                      end: Alignment.bottomRight,
                    ),
                  ),
                ),

      onDetailsPressed:を指定したときに表示される下向き三角の色は、デフォフトでは白なのでarrowColor:indigoにしています。(上記行番号15)

      アカウント名などのテキスト色も、TextStyle(color: Colors.indigo)にしました。(上記行番号3, 6)

      背景画像の指定

      FlutterのDrawerで作成したサイドメニューのヘッダー部分いっぱいに、画像を表示した画面の例。スマホ画面上部の時間が表示されているエリアの下(セーフエリア)で表示している。
      サイドメニューの幅いっぱいに画像を表示

      decoration:では、背景画像を表示することもできます。

      UserAccountsDrawerHeader()でも背景画像を表示できますが、サンプルコードではDrawerHeader()decoration:を使っています。

      ここで表示した画像は、プロジェクト内の[assets]フォルダに入れた画像を読み込んでいるので、その場合は[pubspec.yaml]への記述とPub getは必須。

        assets:
          - assets/ec-main.jpg

      画像をヘッダー領域全体に表示するために、child:Expanded()を指定して幅いっぱいの領域を確保しています。

                  DrawerHeader(
                    decoration: BoxDecoration(
                      image: DecorationImage(
                        image: AssetImage('assets/ec-main.jpg'),
                      ),
                    ),
                    child: Expanded(child: Flexible(child: Container())),
                  ),

      背景画像はchild:に合わせて表示されるので、下記のようにchild:でテキスト表示(下記コードの行番号8)した場合は、そのテキストの背景になります。

      下記ではfit: BoxFit.scaleDown,として、テキストの長さにフィットさせました(下記コードの行番号5)。

      FlutterのDrawerで作成したサイドメニューのヘッダー部分に表示される背景画像が、テキストの幅の背景として配置されている例
      テキストの長さにフィットさせても、画像はそれより少し大きめ?
                  DrawerHeader(
                    decoration: BoxDecoration(
                      image: DecorationImage(
                        image: AssetImage('assets/ec-main.jpg'),
                        fit: BoxFit.scaleDown,
                      ),
                    ),
                    child: Text("テキストテキストテキスト"),
                  ),

      領域いっぱいに画像表示すると、画面上部の時間表示の部分にかぶってしまうので、Drawer()全体をSafeArea()で囲むと、サイドメニュー全体を下に下げることができます。(下記行番号2)

        Widget build(BuildContext context) {
          return SafeArea(
            child: Drawer(
              child: Column(
                children: [
                  DrawerHeader(...
      FlutterのDrawerで作成したサイドメニューのヘッダー部分いっぱいに、画像を表示した画面の例。スマホ画面上部の時間が表示されているエリアの下(セーフエリア)で表示している。
      SafeArea()あり
      FlutterのDrawerで作成したサイドメニューのヘッダー部分いっぱいに、画像を表示した画面の例。スマホ画面上部の時間が表示されているエリアを考慮しないと、時間と画像が重なってしまう。
      SafeArea()なし

      メニュー項目の装飾

      FlutterのDrawerで作成したサイドメニューのメニュー項目の末尾に、[trailing:]プロパティを使って矢印アイコンを表示した例
      [trailing:]で、末尾に右矢印を入れてみた

      ListTile()には、先頭につけるleading:の他に、末尾を指定するtrailing:もあるので、ここにもアイコンをつけました。(下記行番号3)

                ListTile(
                  leading: Icon(Icons.restaurant),
                  trailing: Icon(Icons.arrow_circle_right_outlined, size: 20.0),
                  title: Text("レストラン"),
                  onTap: () {
                    Navigator.of(context).push(
                        MaterialPageRoute(builder: (context) => RestaurantScreen()));
                  },
                ),

      サイドメニュー全体の色

      FlutterのDrawerで作成したサイドメニュー全体に、背景色を表示した例。 Drawer自体は背景色属性を持たないので、Containerを使って表示した例
      Drawer()の装飾機能はとても柔軟

      Drawer()自体には色を指定するプロパティがありませんが、Drawer()の直下でContainer()を使えば、全体に背景色をつけることができます。

      下記のサンプルコードでは、withAlpha()で透過させました。

            child: Drawer(
              child: Container(
                color: Colors.teal.withAlpha(50),
                child: Column(
                  children: [
                    DrawerHeader(

      左開き・右開き

      FlutterのDrawerで、左右両側に表示されたサイドメニューアイコンうち、左側を押してサイドメニューを開いた画面。Flutterでは左右別々のサイドメニューを同時に配置することができる。
      左のサイドメニュー
      FlutterのDrawerで、左右の両側にサイドメニューのアイコンを表示した例。 左アイコンの表示は[drawer:]、右アイコンの表示には[endDrawer:]プロパティを使う。
      左右に別々のサイドメニューを作る
      FlutterのDrawerで、左右両側に表示されたサイドメニューアイコンうち、右側を押してサイドメニューを開いた画面。Flutterでは左右別々のサイドメニューを同時に配置することができる。
      右のサイドメニュー

      サイドメニューは一般的に画面の左から開きますが、Drawer()では右側から表示することもできます。

      また、左と右の両方に別のサイドメニューを同時に指定することも可能です。

      左から開くときはdrawer:、右から開くときはendDrawer:です。

          return Scaffold(
            appBar: AppBar(),
            drawer: MainDrawerHeaderImage(),
            endDrawer: MainDrawer(),
          );

      AppBarのアイコンを使わずにDrawer表示

      FlutterのDrawer表示に、アイコンを使わずにボタンを押して開くよう、プログラム内部から実行するプログラミングコードで作成したスマホ画面。
      ボタンからDrawer()を開く

      Drawer()表示は、AppBar()内のアイコンをクリックするのが一般的ですが、Scaffold()key:を使うと、プログラム内からメソッドを実行して開くことができるようになります。

      ユーザーが何かの操作を完了したときなどに、Drawer()を自動表示したい場合など便利です。

      STEP
      GlobalKeyの作成

      Scaffold()key:プロパティに指定するGlobalKeyを作成します。

      GlobalKeyは、<ScaffoldState>_keyという名前の変数を作成しました。
      (下記行番号4)

      class MyHomePage extends StatelessWidget {
        MyHomePage({super.key});
      
        final GlobalKey<ScaffoldState> _key = GlobalKey<ScaffoldState>();
      
        @override
        Widget build(BuildContext context) {
          return Scaffold(...
      STEP
      key:プロパティの指定

      Scaffold()key:プロパティに、いま作成した_keyを設定します。

      appBar: AppBar(),がなくても、Drawer()の表示そのものには影響しません。
      下記のサンプルコードでは、わかりやすいように削除しました。

      drawer:は、必要です。(下記行番号3)

          return Scaffold(
            key: _key,
            drawer: MainDrawer(),
            body: ...
      STEP
      メソッドでDrawerを開く

      Drawer()を開くには、.openDrawer()を使います。

      下記のサンプルでは、ElevatedButton()を押したときの処理に埋め込みました。

              ElevatedButton(
                child: Text("サイドメニューを表示"),
                onPressed: () {
                  _key.currentState!.openDrawer();
                },
              ),

      Drawer()の開閉

      • 開く:.openDrawer()
      • 閉じる:.closeDrawer()

      表示されたDrawer()は、サイドメニュー以外の画面をタップすると自動で閉じますが、Navigator.pop()を使って閉じることもできます。

      下記コードでは、Drawer()内のListTile()で表示したメニュー項目をタップして閉じます。

      ListTile(
        title: Text("閉じる"),
        onTap: () {
          Navigator.pop(context);
        },
      ),
      STEP
      完成
      「ボタンを押して」開く
      「項目を押して」 閉じる

      これで、AppBar()を配置しなくても、サイドメニューが表示できるようになりました。

      class MyHomePage extends StatelessWidget {
        MyHomePage({super.key});
      
        final GlobalKey<ScaffoldState> _key = GlobalKey<ScaffoldState>();
      
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            key: _key,
            drawer: MainDrawer(),
            body: Center(
              child: ElevatedButton(
                child: Text("サイドメニューを表示"),
                onPressed: () {
                  _key.currentState!.openDrawer();
                },
              ),
            ),
          );
        }

      参考書・サイトなど

      書籍:「Flutter 3 入門」

      Drawerについては3ページ分ほどで書かれていますが、要点を押さえた解説で、構造を理解するにはわかりやすいです。

      「さくしん」さんのサイト

      UdemyにもFlutter講座を持っている「さくしん」さんのサイト。
      文字が多めで、画像少ないですが、詳しく解説しています。

      「さくしん」さんのUdemy講座さく しん @Flutter修行中 icon

      Udemy講座「Flutter & Dart – The Complete Guide [2025 Edition]」

      おしゃれなサンプルアプリを作りながら、Flutterの基本から解説。

      icon icon
      よかったらシェアしてね!
      • URLをコピーしました!

      コメント

      コメントする

      目次