Flutter on the webでGoogle Fontsを使ってみよう
最近、自身のこれまでのキャリアの整理や、副業において自身の経歴や実績などのアピールのために、Flutter on the webを用いてポートフォリオを作成しています。
FlutterのWebアプリをデフォルトの設定で作成すると、テキストの漢字表記が一部中華系フォントのような見た目になってしまうため、なにか外部のフォントを取り入れてみようと思い、 google_fonts パッケージを導入しました。
その際、Webならではの問題に当たったこともあり、自身の備忘録も兼ねて共有します。
導入
Flutterにはデフォルトでttfやotfなどのフォントファイルをアプリに同梱する方法があります。ただし、今回の google_fonts パッケージを導入する場合、基本は追加設定は不要でGoogle Fontsで公開されているフォントを利用することができます。
自身のアプリへの導入は、以下のコマンドを実行するだけです。
$ flutter pub add google_fonts
使い方
GoogleFonts
クラス直下に様々なフォントの TextStyle
と TextTheme
が存在しているので、Google Fontsのページを見ながら自身が作っているアプリに導入したいフォントを選びましょう。
上記のスクリーンショットを見るとわかるのですが、同じフォントで違う名称のメソッドが2つ並んでいます。
- フォント名のみ:
TextStyle
を返すメソッド - フォント名 +
textTheme
:TextTheme
を返すメソッド
そのため、 Text()
クラスなどに直接フォントを反映させる場合は前者を、アプリ全体のフォントとして ThemeData
に反映させたい場合は後者のメソッドを指定するとよいでしょう。
以下のスクリーンショットは、 ThemeData
クラスに「Noto Sans Japanese」を設定した前と後になります。
Google Fonts 適用前
Google Fonts 適用後
微妙な差でもありますが、馴染みのある日本語漢字のグリフで表示されています。
Flutter on the web特有の問題
初回ページ表示時、フォントが反映されるまで全角文字が豆腐になってしまう
これは、対応するフォントをインターネットから取得し、反映されるまでに時間がかかるからのようです。内部的な処理はわかっていませんが、それまでは全角文字が表示できないフォールバックされたASCII系のフォントが使われているのかもしれません。
その場合、フォントが利用可能となるまで待つためのメソッド GoogleFonts.pendingFonts()
を使うことでこの問題を解消できます。
Returns a Future which resolves when requested fonts have finished loading and are ready to be rendered on screen.
戻り値は Future<List<void>>
となっているため、 async/await
もしくは FutureBuilder
を使ってフォントのレンダリングが可能となるまで待つことができます。
私はローディング中にProgressIndicatorを表示したいため、 FutureBuilder
を使い、フォントがレンダリング可能となるまでポートフォリオのページを表示しないようにしました。
class PortfolioApp extends StatelessWidget {
const PortfolioApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Portfolio',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
textTheme: GoogleFonts.notoSansJpTextTheme(), // Google Fonts内の「Noto Sans Japanese」を利用
),
home: FutureBuilder(
future: GoogleFonts.pendingFonts(), // Google Fontsの読み込み待ち
builder: (context, asyncSnapshot) {
if (asyncSnapshot.connectionState != ConnectionState.done) {
// 読み込み中はProgress Indicatorを表示
return const Center(child: CircularProgressIndicator());
} else {
// 読み込み完了後、ページを表示する
return SelectionArea(child: const RootPage());
}
},
),
);
}
}
※グルグルのアニメーションが発生しない詳細な原因は不明ですが、バックグラウンドでフォントの読み込み処理が行われていることが起因していそうです
IntrinsicHeight
+ Row
を使っても適切な高さにならない
先述した最初だけ全角文字が豆腐になってしまう問題の弊害がもう一つあります。ポートフォリオのページ中程まで行くとコンテンツを2列で表示している部分があり、それぞれの列にあるコンテンツの1行分の高さを揃えるために IntrinsicHeight
+ Row
を使っている部分があります。
この部分のテキスト表示について、最初に全角文字が豆腐となってしまうことでその段階でレイアウト計算が行われて1行の高さが決定してしまい、その後にNoto Sans Japaneseのフォントに置き換わるとテキストコンテンツが1行の中に収まらなくなってしまう問題も起きていました。
この問題についても、先ほどの GoogleFonts.pendingFonts()
を用いてフォントがレンダリング可能となるまで待つことにより、解消することができます。
ページが表示されるまでの時間が長い
レイアウト上の問題は解消したのですが、今度はページが表示されるまで時間がかかってしまう問題が発生しました。
これは、デフォルトでは必要なフォントをインターネットから取得していることが原因となります。
google_fonts パッケージでは、アプリのassetsとしてフォントファイルを同梱することで、アプリ内にあるフォントファイルを利用し、最初のロード時間がグッと速くなります。
1. Google Fontsのページからフォントをダウンロード
上記から利用するフォントを検索し、フォントファイルをダウンロードします。
今回は「Noto Sans Japanese」を例に進めます。
ページ右上にある「Get font」ボタンをクリックし、遷移先のページで「Download all」をクリックするとフォントファイルが格納されたzipファイルをダウンロードできます。
2. アプリのassetsとしてフォントファイルを同梱する
ダウンロードしたzipファイルを解凍すると、以下のようにファイルが格納されていました。
Noto_Sans_JP
├── NotoSansJP-VariableFont_wght.ttf
├── OFL.txt
├── README.txt
└── static
├── NotoSansJP-Black.ttf
├── NotoSansJP-Bold.ttf
├── NotoSansJP-ExtraBold.ttf
├── NotoSansJP-ExtraLight.ttf
├── NotoSansJP-Light.ttf
├── NotoSansJP-Medium.ttf
├── NotoSansJP-Regular.ttf
├── NotoSansJP-SemiBold.ttf
└── NotoSansJP-Thin.ttf
中にはVariable FontとFontWeight毎のフォントファイルが用意されています。今回は static
配下にあるフォントを使用します。
static
配下にある NotoSansJP-*.ttf をすべてアプリプロジェクトの assets/fonts
配下へコピーし、pubspec.yaml
に以下の記載を追加します。
flutter:
assets:
- assets/fonts/
※ assets/fonts
というディレクトリは好きな名称にすることができます
あとはアプリを実行してみましょう。ロードがとても速くなっていることが分かります。
Flutter on the webも進化している
これまでのスクリーンショットを見てみると、テキストも選択可能状態となっており、ボタンをタップしたらちゃんと別タブでリンク先が開くなど、通常のHTMLでレンダリングされたWebページとあまり大差なく表示ができています。
もちろん、Flutter on the webが苦手な領域もありますので、適材適所だとは思いますが、SPA(Single Page Application)が向いているWebサービスの場合、プロダクションレベルでも十分に活用できるレベルではないかと感じています。私も自身のポートフォリオの作成に利用しております。
これからもどんどんFlutterを活用し、様々なサービスを開発していきたいなと思います。
参考
Discussion