Material componentsとFlutter

#はじめに
Flutter Advent calendar 2, 1日目の12月1日です！ (monoさん、始めていただきありがとうございます！）

今日は、デザイナーさんとの仕事を効率よく進めていく上で、Material Componentsをもとに共通言語を定義して、Flutterで効率よくアプリを作っていく方法について述べていきたいと思います。

なお、筆者はFlutter/Dartが、30歳超えしてからまともに学んだ初めての言語（SDK）だったりするので、その他の言語やフレームワーク（Web系, iOS, Android)と厳密に比較することができません。簡単かどうか、は今までの他の皆様の議論を見ながら勝手に解釈しているものですのであしからず。。。

さて、この文章全体の構成は以下のようになっています。

- Flutterとコンポーネントデザイン、Material component
- Material componentの簡単な始め方と構成要素の確認
- Theme WidgetとThemeData()で色とTextstyle、ついでにコーナー、ボーダー、影を定義する
- 独自Widgetを作成してコンポーネントをつくる
- まとめ

なお、今回の内容は12月11日に行われるFlutter Interactの内容を見てからアップデートされる可能性が多分にありますのでご了承ください！（Flutter Interactに思いっきり[この名前のセッション](https://developers.google.com/events/flutter-interact/schedule)がありましたね。。）

#Flutterとコンポーネントデザイン、Material component
デザイナーの皆さんの中では、コンポーネントデザイン（アトミックデザイン、デザインシステム)が定着しつつあるように感じています。（会社によっては公開しているところもありますね。有名なのは[ShopifyのPolaris](https://polaris.shopify.com/)とか、[AirBnB](https://airbnb.design/building-a-visual-language/), [メルカリさんの例](https://tech.mercari.com/entry/2019/08/26/090000), [SmartHRさんはInvisionで公開中](https://smarthr.invisionapp.com/share/ADUDJ8BW74C#/screens)）

このような**UIのコンポーネントを予めきちんと定義されて、それぞれのページ上では必要なコンポーネントを組み合わせるだけに留める**という方向は、UIも含めたすべてのアプリの要素をWidget, Codeで表現するFlutterととても相性がいいように感じています。

さて、みなさんは[Material components](https://material.io/components/)をご存知ですか？ Googleが[Material Design](https://material.io/)の要素をそれぞれComponentに落とし込んで、Web, Android, iOS、そしてFlutter向けにコードも公開しているものです。2018年に[Material Theming](https://material.io/design/material-theming/#material-theming)が公開され、Materialの良さを世襲しながら、**よりアプリのブランドを反映しやすいように**高いカスタマイズ性も併せ持つようになりました。Material ComponentsはThemingを利用するためのコードサンプルみたいな形ですね。

もともとFlutterでは[MaterialApp](https://api.flutter.dev/flutter/material/MaterialApp-class.html)をベースにしてアプリを作成しますし、Flutterの[Material Componentsのサポート](https://material.io/components/)を見てもFlutterとMaterial componentsの相性がとてもよいことがわかります。
Material ThemingとComponentsは、簡単に始めやすいように豊富な情報がまとめられています。ドキュメントも（英語ですが）充実していますし、それを利用したサンプルも、テイストや用途の違ったアプリについていくつか種類が用意されています。(Shrine([コード](https://github.com/flutter/samples/tree/master/material_studies/shrine)と[記事](https://material.io/design/material-studies/shrine.html)), Rally([コード](https://github.com/flutter/samples/tree/master/material_studies/rally), [記事](https://material.io/design/material-studies/rally.html)))

デザインツールのSketchとFigmaのためのテンプレートが予め用意され、これを利用するだけで簡単にオリジナルのMaterial designを利用したコンポーネントが作成できます。

Sketchでは[Material Theme Editorというプラグイン](https://material.io/resources/theme-editor/theme-editor-home.html)をインストールし、そこで色やテキストなどのパラメータを入れるだけで大量のコンポーネントやサンプルが作成されます。
Figmaでは、色やテキストがきちんと適用された状態の[テンプレート](https://material.io/resources/)が公開されています。これらの変数を変更するだけで求めているような独自のコンポーネントが作成されます。

このような背景がありますので、ここからはデザイナーさんにまず、**Material componentsをベースとした独自のコンポーネントを定義**してもらい、それをFlutter上に落とし込む、という流れを紹介していきます。


#コンポーネント間で共有される要素
コンポーネント間で共有される要素は主に色、テキストのスタイル、それぞれの要素のコーナー、ボーダー、影かなと思います。そこでまずそれらをデザインの検討が開始される前に、デザイナーさんにきちんと定義してもらいましょう。
では、それぞれについて見ていきましょう

###色
**ブランドの色を決める大切な要素**ですね。企業やアプリによってカラーパレットが定義されていると思います。最近ではDark modeを利用する人も増えてきているので、Dark mode用のカラーパレットを用意している方も増えてきているのかもしれません。ちなみにMaterialでは[色の組み合わせを考える上で利用できるツール](https://material.io/design/color/the-color-system.html#tools-for-picking-colors)が公開されています。読み難さを軽減するための色の組み合わせも含めて提案してくれるので検討してみてはいかがでしょうか。

###テキストスタイル
色と合わせてアプリの雰囲気を決める重要な要素の一つがフォントを始めとしたテキストスタイルです。最近メルカリさんが[独自のフォントを作成された](https://design.mercari.com/mercari-sans/)用に、自社のブランドを表現するTypefaceを導入する企業も増えていますね。
これらは使用する場所に合わせて**ヘッダー、本文など何種類かの組み合わせ**を予め作成しておくケースが多いかとおもいます。前章であげたデザインツールのテンプレートでもテキストスタイルが予め定義されています。

###要素のコーナー、ボーダーと影
上記の2要素と比べたら地味かもしれませんが、さりげなくアプリ全体の雰囲気に実は重要な影響を与えるのが要素の**コーナー**、**ボーダー**と**影**です。ボタンの形が丸いのか、四角いのか、それとも切り落としたような形をしているのか。また要素も強調したい部分がどの程度その他より浮いているように見せるのか、などからアプリの雰囲気も大きく違ってきますね。（Dribbleで上がるデザインの例ではこれらの要素でいろいろと工夫がされているケースが増えている用に感じます）



#ThemeData()で色とTextstyle、ついでにコーナー、ボーダー、影を定義する
Flutterでは[ThemeData()](https://api.flutter.dev/flutter/material/ThemeData-class.html)を[Theme Widget](https://api.flutter.dev/flutter/material/Theme-class.html)にわたすことで、かなり多くの値を予め定義し、それぞれのアプリのUI要素に継承することができます。(Flutterの[Cookbook](https://flutter.dev/docs/cookbook/design/themes)も参照ください。) 今回はこれに加えて、その他の要素のコーナーや影、余白のデータも加えて、アプリ内で共通の要素はすべてここにしまい込んでしまおうと考えています。
では、前章の共有される要素をどのように設定していくか見ていきましょう。

###色
`ThemeData()`にはアプリ内で利用する色を定義するためのプロパティが数多く揃っています。デフォルトの値をそのまま利用してもいいケースもありますので（Dividerなど)、必要なところだけカラーパレットに合わせて適用させてしまうといいかと思います。なおデフォルトで利用される色は[ColorScheme](https://api.flutter.dev/flutter/material/ColorScheme-class.html)に定義されています。`ThemeData()`では必要なところだけ上書きしてしまえばいいかなと思います。

なお、デバイスのダークモードの適用については[こちらの記事](https://medium.com/@pmutisya/dark-mode-in-flutter-3742062f9f59)やFlutterの[Flutter Gallery](https://github.com/flutter/flutter/blob/master/examples/flutter_gallery/lib/gallery/themes.dart)を参考にするといいでしょう。

上記の`ColorScheme`ですでにlightとdarkが指定されているので、ここでもやはり必要なところだけ上書きするといいと思います。ここで定義したパラメータをMaterialAppをBuildするときに`Theme`でthemeおよびdarkThemeプロパティを指定してください。


###テキストスタイル
`ThemeData()`の中の[TextTheme](https://api.flutter.dev/flutter/material/TextTheme-class.html)のプロパティをご覧いただき必要なところを上書きしてしまうのがいいと思います。なお、このTextThemeのテキストはMaterialが2018に更新される前の名前になっているので、現行のMaterialにあったものにする場合は[2018のテキストテーマ](https://api.flutter.dev/flutter/material/TextTheme-class.html)を引っ張ってきて適用してしまうか、こちらの対応表を使ってデザインツールでの名前を変えてもらうようにしましょう。

###要素のコーナー、ボーダーと影
`ThemeData()`のいくつかの要素ではclipやshape、elevationといった要素をそれぞれのパーツthemeの中で上書きすることができます(ButtonThemeやCardThemeなど）。そこで角のデザインを上書きするためclip、ボーダーを定義するshape、影を設定するためのelevationをそれぞれ設定していきましょう。
なおこれら`ThemeData()`で指定したパラメータも、それぞれのWidgetを定義するときに簡単に上書きをすることができるので、気軽に設定してしまえばいいのかなと思います。


#独自Widgetを作成して基礎となるコンポーネントをつくる
前章ではデザインツールで指定した共有要素を`ThemeData()`を利用してFlutter上で再現するという作業をしました。
この章では、これらの要素を利用しながらアプリの中で使う基礎となるコンポーネントを作っていこうと思います。

個人的には、Flutterで以下のようにUIパーツをつくっています。

1. ページ間で共有するWidget (common_widgets)
2. (1)を加工してページ専用のWidget (ボタンとかシートとか)をつくる
3. (2)を組み合わせてページをつくる

これを利用してメンテのしやすいUIを作ることができるかなと思っています。僕がとても勉強させてもらったUdemyのコースを作っているAndreaの[フォルダ構成](https://github.com/bizz84/time_tracker_flutter_course/tree/master/lib)も以下のようになります。(興味持たれた方は[彼のYouTubeチャンネル](https://www.youtube.com/channel/UCrTnsT4OYZ53l0QGKqLeD5Q)の動画に割引コードが貼ってありますので探してみてください)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222297/0cca98e5-39c8-3220-21dd-3b55f797b6e6.png)


Common_widgetsはデザイナーさんが作成するコンポーネントのうち以下のようなものについて実装していくのがいいかと考えています。

- MaterialAppのものにないオリジナルのものを作るとき
- よく使う、テキストだけが変わるだけの単純なbuttonなど
- 異なるページでも共通するロジックを元にした要素など（例えばboolの値によって見た目が変わるようなボタン。(注)ロジック自体は持たない。)
- 異なるページで利用される、複数のWidgetを組み合わせた要素（`listTile`のようなもの）
- iOS/Androidで極端にUIが異なる`date_time_picker`や`AlertDialog`など

Common_widgetsとしてこれらを定義することで、UIの全体の更新とその適用が簡単に終わったり、それぞれのページで異なる実装が起こってしまったりしなくていいかなと思っています。

#まとめ
Flutterは**MaterialComponentの存在ありきで作られているんじゃないかと思うぐらい相性がいい**です。ですので、ぜひ

1. Material componentsをもとにしてデザイナさんにデザインを作ってもらい、
2. デザインツール上の共有要素（パラメータとコンポーネント）と
3. Flutterの中でのThemeDataや共有widgetを作成してみてください。

できればお互いの**パーツの名前も共通化してしまう**といいかもしれませんね。
そうすることで、新しいページや要素を作るときでも、元にしているコンポーネントがわかってすぐに対応できるんじゃないかなと思います。

ここまでお読みいただいてありがとうございました！
明日は[regonn](https://qiita.com/regonn)さんです！