【Flutter】魅力的なサイドメニューを、Drawerで作ろう!

対象者

  • Flutterに関心があり、特にDrawerの実装について学びたいと考えている方
  • フロントエンド開発に従事していて、新たな技術を習得することに積極的な方
  • 実際のコードを見ながら学習を進めたい、実践的な知識を得たい方

はじめに

Flutterを使ってカスタムDrawerを作りたいと思っていませんか? Drawerはアプリケーションの操作性とユーザーエクスペリエンスを向上させるための非常に重要な要素です。しかし、単にDrawerを作るだけでなく、その開閉制御やデザイン、そして機能の追加などをマスターすることで、あなたのアプリケーションは一段と使いやすく、見た目にも魅力的なものになるでしょう。

Drawer というのは、日本語で「引き出し」という意味です。プログラムの世界では一般的に「ナビゲーションメニュー」 ということを示します。Flutterにおいては「サイドメニューを提供する」ウィジェットです。そのため、ナビゲーションの選択肢を一覧表示し、ユーザーが目的のページへ容易に移動することができます。
実際のアプリとしては、メールアプリやSNSアプリと行ったケースに「ユーザープロフィール」「設定」「ログアウト」などというような機能をサイドメニューとして実現することができます。

この記事では、基本的なDrawerの作成から、カスタマイズ、開閉制御、そしてデザインまで、FlutterでのDrawerの全てを詳しく解説します。コードの実例も交えながら説明するので、Flutterを使った開発に即して活用できる知識とスキルを得ることができます。

Drawerとは

FlutterにおけるDrawerとは、基本的にはサイドナビゲーションメニューのことを指します。主にモバイルアプリケーションにおいてよく用いられ、画面の端からスワイプすることで表示させることができます。

Drawerの基本的な概念

Drawerは、Flutterアプリケーションにおける一つのWidgetです。これは、アプリケーションの主要なナビゲーション要素を格納します。Scaffoldのdrawerプロパティに設定することで、左側からスライドするDrawerを実装できます。

Drawerの用途

Drawerは、アプリケーションの主要なナビゲーション要素を格納し、ユーザーがアプリケーション内の主要な場所に簡単に移動できるようにするために使用されます。特に画面が小さいモバイルデバイスにおいては、画面上部のナビゲーションバーにすべての要素を置くスペースが不足してしまうことが多いため、このような形式のナビゲーションメニューが用いられます。これにより、UIをスッキリさせることができ、ユーザーエクスペリエンスを向上させることが可能となります。

FlutterでのDrawerの作成

FlutterにおけるDrawerの作成は非常にシンプルで、わずかなコードで実装することが可能です。その基盤となるのが、ScaffoldAppBarという二つの重要なWidgetです。

ScaffoldとAppBarの使用

Flutterでは、ScaffoldというWidgetを用いることで、Material Designの基本的なビジュアルレイアウト構造を簡単に実装することができます。特に、ScaffolddrawerプロパティにDrawerを設定することで、左側からスライドするDrawerを実装することが可能となります。

また、AppBarは、Scaffoldの一部として、アプリケーションの上部に表示されるバーのことを指します。これにより、アプリケーションのタイトルや、アクションボタン、Drawerを開くためのハンバーガーメニューなどを表示することができます。

Scaffold(
  appBar: AppBar(
    title: Text('Flutter Drawer Demo'),
  ),
);

このように記述することで、アプリケーションの上部にAppBarを表示し、その中にタイトルを設定することができます。

Drawerの基本的な実装

FlutterでDrawerを作成するためには、ScaffolddrawerプロパティにDrawer Widgetを指定します。Drawerの中身は通常、リスト形式(ListView)で表示され、その中には個々のアイテム(ListTile)が含まれます。以下にその基本的な実装を示します。

Scaffold(
  appBar: AppBar(
    title: Text('Flutter Drawer Demo'),
  ),
  drawer: Drawer(
    child: ListView(
      children: <Widget>[
        DrawerHeader(
          child: Text('Drawer Header'),
          decoration: BoxDecoration(
            color: Colors.blue,
          ),
        ),
        ListTile(
          title: Text('Item 1'),
          onTap: () {
            // Do something
            Navigator.pop(context);
          },
        ),
        ListTile(
          title: Text('Item 2'),
          onTap: () {
            // Do something
            Navigator.pop(context);
          },
        ),
      ],
    ),
  ),
);

このコードにより、AppBarにハンバーガーメニュー(三線メニュー)が表示され、それをタップするか、画面をスワイプすることでDrawerが表示されます。そして、Drawer内にはヘッダーと、それに続くアイテムのリストが表示されます。このように、Flutterを用いてDrawerを作成するには、ScaffoldとAppBarの使用と、その中にDrawerを設定することが基本的なステップとなります。

Drawerのカスタマイズ

FlutterにおけるDrawerの魅力の一つは、その柔軟性とカスタマイズ可能性にあります。DrawerHeaderやListTileの活用、メニューアイテムの追加、さらにはユーザープロファイルの組み込みなど、多彩なカスタマイズオプションが用意されています。

DrawerHeaderとListTileの使用

Drawerの最上部に表示される領域を指定するために、DrawerHeader Widgetが用意されています。DrawerHeaderは、色、テキスト、デコレーションなどを自由にカスタマイズすることができます。また、Drawerの各項目を示すためにはListTile Widgetを使用します。

DrawerHeader(
  child: Text('Drawer Header'),
  decoration: BoxDecoration(
    color: Colors.blue,
  ),
),
ListTile(
  title: Text('Item 1'),
  onTap: () {
    // Do something
    Navigator.pop(context);
  },
),

このように、DrawerHeaderを使用してDrawerのヘッダー部分を、ListTileを使用して各項目を作成します。

メニューアイテムの追加

ListTileを使用することで、簡単にDrawerのメニューアイテムを追加することができます。各ListTileは、タイトル、アイコン、タップ時の動作などを設定することが可能です。以下にその一例を示します。

ListTile(
  leading: Icon(Icons.home),
  title: Text('Home'),
  onTap: () {
    // Navigate to Home
    Navigator.pop(context);
  },
),

このように、leadingプロパティにアイコンを設定し、titleに項目名を設定し、onTapにタップ時の動作を設定することで、メニューアイテムを追加することができます。

ユーザープロファイルの組み込み

Flutterでは、Drawerにユーザープロファイルを組み込むことも可能です。特に、UserAccountsDrawerHeader Widgetを使用すると、ユーザーの名前やメールアドレス、アカウント画像などを簡単に表示することができます。

UserAccountsDrawerHeader(
  accountName: Text('John Doe'),
  accountEmail: Text('john.doe@example.com'),
  currentAccountPicture: CircleAvatar(
    backgroundImage: NetworkImage('https://example.com/john_doe.jpg'),
  ),
),

このコードでは、UserAccountsDrawerHeaderにユーザーの名前、メールアドレス、プロフィール画像を設定しています。このように、FlutterのDrawerは、自由度が高く、多彩なカスタマイズオプションを提供しています。これにより、ユーザーのニーズに合わせた、個性的なDrawerを作成することが可能です。

Drawerの開閉制御

アプリケーションの操作性を向上させるためには、Drawerの開閉制御は非常に重要です。Flutterでは、NavigatorGlobalKeyを使って、その制御を簡単に実装することができます。

FlutterのNavigatorは画面遷移を管理するクラスで、Drawerの開閉もその一部として制御します。具体的には、Navigator.pop(context)を使用してDrawerを閉じることができます。これは、通常、Drawerの各項目がタップされたときに呼び出されます。

ListTile(
  title: Text('Item 1'),
  onTap: () {
    Navigator.pop(context);  // Close the drawer
  },
),

上記のコードでは、ListTileがタップされたときに、何らかのアクションを行った後でDrawerを閉じています。

GlobalKeyの使用

一方、Drawerをプログラム的に開くためには、GlobalKey<ScaffoldState>を使用します。ScaffoldStateopenDrawerメソッドを呼び出すことで、Drawerを開くことができます。

final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

Scaffold(
  key: _scaffoldKey,
),

_scaffoldKey.currentState.openDrawer();
_scaffoldKey.currentState.closeDrawer();

上記のコードでは、Scaffoldのキーとして_scaffoldKeyを設定し、そのcurrentStateを使用してopenDrawerメソッドを呼び出すことでDrawerを開いています。

以上のように、FlutterではNavigatorGlobalKeyを使って、Drawerの開閉制御を容易に行うことができます。これにより、ユーザーのアクションに応じて、柔軟にDrawerを開閉することが可能となり、アプリケーションの操作性を向上させることができます。

Drawerのデザイン

Flutterでは、サイドメニューのデザイン、ヘッダーや背景画像のデザイン、アイコンやテキスト、画像の追加等、Drawerのデザインをカスタマイズすることが容易にできます。

サイドメニューのデザイン

サイドメニューのデザインは、Drawerの全体的な見た目に直接影響を与えます。Flutterでは、Drawerの色や大きさ、影などを自由に調整できます。これらは、Drawerの背景色としてcolorプロパティを設定することで可能です。

Drawer(
  elevation: 16.0,  // Shadow
  child: Container(
    color: Colors.blue,  // Background color
    child: ListView(),
  ),
),

ヘッダーと背景画像のデザイン

ヘッダーと背景画像は、Drawerの一番上部に表示され、ユーザーの注目を引く重要な要素です。これらは、DrawerHeaderやUserAccountsDrawerHeaderウィジェットを使用することで設定できます。

Drawer(
  child: ListView(
    children: <Widget>[
      DrawerHeader(
        decoration: BoxDecoration(
          image: DecorationImage(
            image: AssetImage('assets/images/drawer_bg.jpg'),
            fit: BoxFit.cover,
          ),
        ),
        child: Text('Drawer Header'),
      ),
      ListTile(
        title: Text('Item 1'),
      ),
      // More ListTiles...
    ],
  ),
),

アイコン、テキスト、画像の追加

Drawer内には、各項目を表すアイコンやテキスト、さらには画像なども追加できます。これらは、ListTileのleadingプロパティにアイコンを設定したり、titleプロパティにテキストを設定することで可能です。

Drawer(
  child: ListView(
    children: <Widget>[
      ListTile(
        leading: Icon(Icons.home),
        title: Text('Home'),
      ),
      ListTile(
        leading: Icon(Icons.settings),
        title: Text('Settings'),
      ),
      // More ListTiles...
    ],
  ),
),

以上のように、FlutterのDrawerでは、色々な要素のデザインを自由にカスタマイズすることが可能です。これにより、アプリケーションのブランドイメージに合わせたデザインや、ユーザーの使いやすいデザインを実現することができます。

Q&A

Q1: FlutterでDrawerを作成する際に、ScaffoldとAppBarはどのように使用するのですか?

A: Flutterでは、Scaffoldは基本的なビジュアルレイアウト構造を提供し、AppBarはアプリケーションの上部に表示されるアプリバー(ヘッダー)を提供します。これらを組み合わせることで、基本的なDrawerを作成することができます。以下にその簡単な実装例を示します。

Scaffold(
  appBar: AppBar(
    title: Text('Drawer Example'),
  ),
  drawer: Drawer(
    // Insert your Drawer child widget here.
  ),
)

Q2: NavigatorとGlobalKeyを使用してDrawerの開閉制御を行う方法は?

A: Navigatorはアプリケーションの画面遷移を管理します。特にDrawerの場合、画面の開閉に使用します。また、GlobalKeyは特定のWidgetを一意に識別し、その状態を管理するために使用します。これらを組み合わせることで、Drawerの開閉制御を行うことができます。以下にその実装例を示します。

final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

Scaffold(
  key: _scaffoldKey,
  appBar: AppBar(
    title: Text('Drawer Example'),
  ),
  drawer: Drawer(
    // Insert your Drawer child widget here.
  ),
  body: Center(
    child: FilledButton(
      onPressed: () => _scaffoldKey.currentState?.openDrawer(),
      child: Text('Open drawer'),
    ),
  ),
)

Q3: FlutterのDrawerで、ヘッダーと背景画像のデザインはどのように行えますか?

A: FlutterのDrawerでは、DrawerHeaderを使用してヘッダー部分を作成します。そして、decorationプロパティを使用して、背景色や背景画像を設定できます。以下にその実装例を示します。

DrawerHeader(
  decoration: BoxDecoration(
    color: Colors.blue,
  ),
  child: Text(
    'Drawer Header',
    style: TextStyle(
      color: Colors.white,
      fontSize: 24,
    ),
  ),
)

Q4: FlutterのDrawerが閉じない

サンプルを作成しているときに「Navigator operation requested with a context that does not include a Navigator.The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget」という例外が発生し、Drawerが閉じないという状態になりました。
原因としては、MaterialApp()を返すクラス内にDrawerのあるScaffoldを返すコードが同じ場所にあることでした。この問題を解決するため、MaterialAppとScaffoldを返す二つのクラスにしました。これにより、Drawerが閉じるようになりました。

まとめ

FlutterのDrawerを学び、その作成、カスタマイズ、開閉制御、デザイン、そして実例とチュートリアルを通じて理解を深めました。まず、Drawerは便利なナビゲーションツールで、ScaffoldとAppBarを用いて基本的な実装が可能です。これにより、メニューを見易く、またユーザーフレンドリーに構築することができます。

次に、DrawerHeaderとListTileを使用し、メニューアイテムを追加することで、Drawerをカスタマイズしました。これは、アプリケーションの個性を引き立てるだけでなく、ユーザープロファイルの組み込みを可能にし、更にパーソナライズされた体験を提供します。

また、NavigatorとGlobalKeyを使用してDrawerの開閉制御を学びました。これらの機能は、Drawerの状態を管理し、それに応じて適切な応答を生成することを可能にします。

さらに、サイドメニューのデザイン、ヘッダーと背景画像のデザイン、そしてアイコン、テキスト、画像の追加について学びました。これら全てが組み合わさることで、アプリケーションは美観性と使いやすさを両立します。

参考

ソース(main.dartにコピペして動作確認用)

import 'package:flutter/material.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Drawer Demo',
        theme: ThemeData(
          useMaterial3: true,
        ),
        home: HomeWidget());
  }
}

class HomeWidget extends StatelessWidget {
  HomeWidget({
    super.key,
  });
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: Text('Flutter Drawer Demo'),
      ),
      drawer: Drawer(
        elevation: 16.0,
        child: Container(
          color: Colors.green,
          child: ListView(
            children: <Widget>[
              DrawerHeader(
                child: Text('Drawer Header'),
                decoration: BoxDecoration(
                  // color: Colors.blue, // 背景色
                  image: DecorationImage(
                    image: NetworkImage(
                        'https://flutter.salon/wp-content/uploads/2021/12/IMGP4097.jpg'),
                    fit: BoxFit.cover,
                  ),
                ),
              ),
              ListTile(
                title: Text('メニューで開いたとき'),
                onTap: () {
                  Navigator.pop(context);
                },
              ),
              ListTile(
                leading: Icon(Icons.home),
                title: Text('ボタンで開いたとき'),
                onTap: () {
                  _scaffoldKey.currentState?.closeDrawer();
                },
              ),
              UserAccountsDrawerHeader(
                accountName: Text('sakushin'),
                accountEmail: Text('sakushin@flutter.salon'),
                currentAccountPicture: CircleAvatar(
                  backgroundImage: NetworkImage(
                      'https://flutter.salon/wp-content/uploads/2021/11/IMGP7872.jpg'),
                ),
              ),
            ],
          ),
        ),
      ),
      body: Center(
        child: FilledButton(
          onPressed: () => _scaffoldKey.currentState?.openDrawer(),
          child: Text('Open drawer'),
        ),
      ),
    );
  }
}