みなさんはFlutterアプリでアイコンが必要な時、どうしていますか?Material DesignならIcons
クラス、iOSならCupertinoIcons
クラスでしょうか?Flutterは便利なコンポーネントが豊富に揃っているのが良いですよね。ですが足りないものが1つあります。そう、それはMaterial Symbolsです。残念ながらFlutterはMaterial Symbolsを標準でサポートしていません。そこでfms(flutter-material-symbols) というCLIツールを作りました。fmsを使えばIcons
やCupertinoIcons
と同じ感覚でMaterial Symbolsを使えるようになります。アイコンのリソースを手動でダウンロード・管理する必要もありません。
以下前置きです。本題はココから。
背景
Material Symbols:2022年に登場したMaterial Designの新しいアイコンセット
Material Designの最新版であるMaterial3ではアイコンも刷新され、これまでのMaterial Icons(以下、MIcons)に代わりMaterial Symbols(以下、MSymbols)が新たに導入されました。MIconsとMSymbolsの大きな違いは可変フォントになったことです。そのためMSymbolsのアイコン(以下、シンボル)はスタイルと4つの軸、合わせて5つのパラメータによってカスタマイズすることができます。それぞれのパラメータがどう作用するのかは公式のギャラリーサイトを見るのが早いでしょう。
Flutter SDKはMaterial Symbolsに未対応
現時点ではFlutter SDKの中にMSymbolsは含まれていません。Icons
クラスは?と思う方もいるかもしれませんが、残念ながらIcons
クラスはMIconsしかサポートしていません。
Identifiers for the supported Material Icons.
公式対応はもう少し先になりそう
GitHubではIssueが上がっており、現在対応が進められている状況です。ただ昨年の9月頃からあまり動きが見られず、Stableチャンネルに実装されるのはまだ先になりそうです。
ちなみにですが、Design DocによるとMSymbolsはSDKには含まれず、別パッケージとして提供される予定だそうです。またシンボルのカスタマイズは、Icon
Widgetのコンストラクタに4つの軸に対応した引数をそれぞれ追加することで実現されるようです。実装はまだのはずですが、Icon
クラスのドキュメントにはすでに新しいコンストラクタ引数が追加されています。
Icon(IconData? icon, {Key? key, double? size, double? fill, double? weight, double? grade, double? opticalSize, Color? color, List<[Shadow](https://api.flutter.dev/flutter/dart-ui/Shadow-class.html)>? shadows, String? semanticLabel, TextDirection? textDirection})
Creates an icon.
自前でなんとかする方法はある
じゃあMSymbolsはまだ使えない?いいえ、そんなことはありません。幸いなことにMSymbolsはOSSであり、全てのリソースがGitHubで公開されています。またシンボルを一覧できる公式のギャラリーサイトからは簡単にSVGファイルが手に入りますので、あとはflutter_svgなどのパッケージを使って
child: IconButton(
icon: SvgPicture.asset('path/to/svg/file'),
),
とかすればすぐにプロジェクトにMSymbolsを組み込めます。
とはいえこれだとパスを書かないといけないし、IDEの補完も効きません。できればIcons
クラスみたいにIcons.home
とか書きたいですよね。そのためには拾ってきたSVGを1つのアイコンフォントにまとめる必要があります。それにはnode.jsパッケージのfantasticon が使えます。これはSVGからアイコンフォントを生成してくれるCLIツールです。アイコンフォントができたら、Icons
やCupertinoIcons
のようなラッパークラスを書きましょう。
ラッパークラスなんてどうやって書くんじゃい!安心してください、icon_font_generatorがあります。これはSVGからアイコンフォントの生成、そしてラッパークラスの生成まで全部やってくれるCLIツールです。しかもDart製なのでpub get
でインストールできます。さあ、あとは必要なSVGを拾い集めてくるだけです。
で、fmsが生まれたってわけ
「あとは必要なSVGを拾い集めてくるだけ」の部分も自動化したのがfms(flutter-material-symbols) パッケージです。fmsは内部でfantasticonとicon_font_generatorを利用しているため、リスペクトの意味を込めて上記のパッケージを紹介いたしました。fmsを使えば設定ファイルからSVGの収集、アイコンフォント、ラッパークラスの生成までコマンド一発です。リソースの管理を手動でする必要はありません。
なぜわざわざ「生成」するのか?
MIconsは全アイコンのIconData
がIcons
クラスにstataic変数として定義されています。一方でMSymbolsの場合シンボル自体が2500種類ほどあり、それぞれが5つのパラメータを持つためその組み合わせは途方も無い数になります。そのため各シンボルの全バリエーションをstatic変数として提供する場合、巨大なアイコンフォントをパッケージに含めることになります。そのためfmsでは必要なアイコンだけを含むフォントを生成するという方針をとりました。
fmsの使い方
全て説明すると長くなってしまうので、ここには概要だけ記します。詳しい使い方に興味のある方はREADME.mdをご覧ください。
インストール
pub
コマンドでPub.devからインストールできます。
$ flutter pub add --dev fms
fmsは内部でnode.jsパッケージのfantasticonを利用しています。バージョン11以降のnode.jsが入っていない場合は別途インストールが必要です。
$ node --version
v18.12.1
Getting started
例としてNavigationBarのタブに使うアイコンを用意するケースを考えます。選択状態と未選択状態を区別するために2種類のホームアイコン🏠(アウトラインのみ、塗りつぶし)を作ります。
-
設定ファイルを書く
fmsの設定ファイルはYAMLで書きます。この例では
my_symbols.yaml
という名前でプロジェクトルートに置いていますが、どこに置いても構いません。設定ファイルには生成されるアイコンフォントのファミリー名、アイコンフォントとラッパークラスの出力先、そして使用したいシンボルの情報を記述します。# project_root/my_symbols.yaml family: MySymbols # ファミリー名 output: flutter: lib/src/my_symbols.dart # ラッパークラス font: assets/my_symbols.ttf # アイコンフォント # 使用したいシンボルの情報 symbols: home: Home # 未選択状態のHomeシンボル(アウトラインのみ) home_selected: # 選択状態のHomeシンボル name: Home fill: true # 塗りつぶし
fmsはこの設定ファイルからラッパークラス
MySymbols
を生成し、各アイコンにはMySymbols.home_selected
のようにアクセスできるようになります。 -
アイコンフォントとラッパークラスを生成
以下のコマンドは
assets/my_symbols.ttf
にアイコンフォントが、lib/src/my_symbols.dart
にラッパークラスをそれぞれ生成します。$ flutter pub run fms build my_symbols.yaml
実行すると
lib/src/my_symbols.dart
に下記のようなラッパークラスが生成されます。import 'package:flutter/widgets.dart'; // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: non_constant_identifier_names // ignore_for_file: constant_identifier_names @immutable class _MySymbolsData extends IconData { const _MySymbolsData(int codePoint, this.name) : super( codePoint, fontFamily: 'MySymbols', ); final String name; } @immutable class MySymbols { const MySymbols._(); static const home_focused = _MySymbolsData(0xf102, 'home_focused'); static const home = _MySymbolsData(0xf103, 'home'); static const all = <String, _MySymbolsData>{ 'home_focused': home_focused, 'home': home, }; }
-
アイコンフォントの情報を
pubspec.yaml
に追加MySymbols
を使うためには対応するアイコンフォントをFlutterに認識させる必要があります。pubspec.yaml
のflutter:
セクションに生成されたmy_symbols.ttf
を追加します。またフォントファイルを
lib/
以外の場所(例えばassets/
)に置く場合はassets:
セクションでアセットとして追加することも忘れないでください。... flutter: assets: - assets/ fonts: - family: MySymbols # ファミリー名 fonts: - asset: assets/my_symbols.ttf # フォントファイル
-
生成されたアイコンを使う
あとは
Icons
やCurpetinoIcons
と同じようにMySymbols
クラスから必要なアイコンを利用するだけです。もちろんconst
も使えますし補完も効きます。やったね!import 'package:your_package/src/my_symbols.dart'; import 'package:flutter/material.dart'; Widget homeNaviDest() { return NavigationDestination({ icon: const Icon(MySymbols.home), selectedIcon: const Icon(MySymbols.home_selected), }); }
終わりに
Material Symbolsでググってもあまり情報が出てこないのだけれど、もしかして認知度低いのか、、?