FlutterのWidgetをコードを動かしながら学ぶ: Drawer編

Flutterでドロワーメニューを実装できるDrawerクラスについて扱っています。
2020.07.08

Flutter は動画コンテンツも充実していて週毎にウィジェットを紹介している Flutter Widget of the Week では短い動画でウィジェットの概要を掴むことができる動画を視聴できます。

Flutter Widget of the Week を見るだけでも勉強になるのですが、記憶力が良くないので、視聴によるインプットだけでなく手元でコードを動かしてそれをブログ記事にする所までやって後から思い出しやすいようにしてみるシリーズです。

これまでの記事は以下のリンクから参照できます。

今回取り上げるのは Drawer です。

※ この記事では序盤にcodepenの埋め込みを使用します。codepenの動作がSafariだと安定しない所がありました(iOSのSafariで確認)。Chromeでの閲覧・利用推奨です。

Drawerの基本

Flutterではスマホアプリで使われがちなUIを簡単に実装できます。場合によっては1から作らなければいけないこともありますが、compostional layoutっぽく小さな部品を組み合わせて作れば良いのでそういう時はiOSアプリ開発とやることはそう変わらないでせう。ただ、後発のUIフレームワークということもあり思い描いたUIを実現するためのクラスが大抵は提供されています。

今回はその一つドロワーメニューを実装するために提供されているDrawerというWidgetを記事に取り上げます。基本的な使い方は単純ですが希望のインターフェースを実装するのに少し時間がかかったのでコードとあわせて紹介できればと思います。

Flutter Widget of the Weekにも取り上げられています。概要メモです。

  • ページ遷移のためのナビゲーションを表すのに使う
  • Scaffoldのdrawerパラメータに渡す
  • ScaffoldにappBarパラメータにAppBarのインスタンスが渡されている場合自動でAppBarにハンバーガーアイコンが表示される
  • endDrawerパラメータを使って右側にDrawerを表示させられる
  • Scaffoldのステートをof(BuildContext context)で取得してopenDrawer()、openEndDrawer()をコールすることでプログラム側からドロワーメニューを呼び出せる
  • pop()で閉じることができる
  • 一般的にDrawerのchildにはDrawerHeaderとListView、その中にListTileを使う

最低限のコードで実装したcodepenが以下です。

See the Pen DrawerSample1 by Nobuyuki Tanabe (@nabeatsu) on CodePen.

動画にある通りListviewとListTileを使って実装しています。

DrawerHeaderというWidgetをListViewに追加することでドロワーメニューにヘッダーを設定できます。

動画の通り右側に表示させたい時にはdrawerではなくendDrawerパラメータに値を渡します。

See the Pen DrawerSample2 by Nobuyuki Tanabe (@nabeatsu) on CodePen.

Drawerヘッダーの他によくあるユースケースとしてユーザーの情報がドロワーメニューのヘッダ部分にあるUIのためにUserAccountsDrawerHeaderが提供されています。

これも最低限のサンプルをcodepenに用意しました。NetworkImageを使って Twitterのアイコン画像を取得しています。

See the Pen UserAccontDrawerSmaple1 by Nobuyuki Tanabe (@nabeatsu) on CodePen.

実際に使う時に

最低限の実装は動画やドキュメントを見て実装すれば終わりですが思い通りのレイアウトを作るためには他のウィジェットを活用する必要があります。

drawerパラメータの型を見るとWidgetとなっています。DrawerではなくWidgetなので、Drawer向けに特別なWidgetで包んだりパラメータに値を渡したりするのではなく、通常通りのFlutterでWidgetを組んでいく時のようなレイアウトの自由が効きます。

final Widget drawer;

ステータスバーを考慮したくない時

画像のようにドロワーがステータスバーの上に乗らないようにしたい時にはSafeAreaを使います。

SafeAreaについては過去の記事で取り上げているので知らない人は参照してください。

以下のようにDrawerをSafeAreaでラップします。

drawer: SafeArea(
  child: Drawer(
      child: ....
    )
  )
)

ドロワーを角丸にする

ClipRRectを使います。ClipRRectを使うことで四角いコンテンツの四隅の角を切り取ることができます。類似のWidgetにClipOvalやClipPathなどがあります。

ドキュメントは以下です。

ドロワーメニューをスクロール可能にする場合

DrawerのchildパラメータにColumnを渡していた所をSingleChildScrollViewでラップしてスクロールできるようにしました。SingleChildScrollViewは1つのウィジェットがスクロールできるできるようにレイアウトを作ってくれるWidgetです。

ドキュメントは以下になります。

ドロワーメニューの幅を調整する場合

FlutterのWidgetツリーにおいては親のWidgetが子のウィジェットにどのような制約を与えているか考慮に入れてレイアウトを組む必要があります。反対に親のWidgetから制限を与えれば横幅もコントロールできるはずです。実際にContainerでラップしてwidthを明示的に使用することで横幅を指定できます。

drawer: Container(
  width: 200,
  child: Drawer(
      child: ....
    )
  ),
)

実際に今紹介したようなWidgetを使って作ったのが以下のようなレイアウトになります。

自作中のアプリのコードからコードを引っ張ってきて説明に不要なコードを削除したものが以下です。ステータスバーの所はSafeArea、角丸はClipRRect、ヘッダーの高さ指定にはSizedBoxを使用してレイアウトを調整しています。

import 'package:flutter/material.dart';

class DefaultDrawer extends StatelessWidget {

  const DefaultDrawer({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      bottom: false,
      child: ClipRRect( // 角丸のためにラップ
        borderRadius: BorderRadius.only(
            topRight: Radius.circular(20), bottomRight: Radius.circular(20)),
        child: Drawer(
          child: SingleChildScrollView(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                header(context),
                // ListTileの列挙が一般的
                )
              ],
            ),
          ),
        ),
      ),
    );
  }

  Widget header(BuildContext context) {
    return SizedBox( // SizedBoxを使わないとテキストと比べてヘッダが高すぎるのでSizedBoxで調整
      height: 60,
      child: DrawerHeader(
        child: Text(
          'カテゴリ一覧',
          style: TextStyle(
            fontSize: 16.0,
            color: Colors.white,
          ),
          textAlign: TextAlign.center,
        ),
        decoration: BoxDecoration(color: Colors.blue[300]),
      ),
    );
  }
}

まとめ

ドロワーメニュー自体は簡単でしたがより込み入った仕様を実現する際には他のWidgetも使用する必要があると思っていますが今回実装した上で必要になったWidgetは以上になります。 Flutterの学習・開発は始めたばかりで説明やその裏にある認識に誤りがあるかもしれません。なにかお気づきの際はコメントなどでお知らせください。