今年1月からFlutterのWebアプリケーションを開発している孫と申します。
Flutter 3.0から公開されたThemeExtensionはあなたのコードをもっと綺麗に整えるのができます。なぜThemeExtensionシステムを使うべきなのか、どのように使ったらいいのかを確認してみましょう!
※ Youtubeの「ThemeExtension | Decoding Flutter」の内容を元に編集しました。
WEBではセマンティック情報とスタイル情報を分けた方が良いという話があります。
例えば
<p style="color: red;">どうする?GOする!</p>
上記のように同じところに置くことより
<!-- index.html --> <p>どうする?GOする!</p>
/* style.css */ p { color: red; }
セマンティックをHTMLファイルへ、スタイルをCSSに置くのがいいという話です。
Text(
'どうする?GOする!',
style: TextStyle(color: Colors.red),
)
Flutterで上記のようにText Widgetの文言とスタイルをベタ書きすることができますが、前述したWEBでのセマンティックとスタイルの関係を考えると、スタイルを汎用的に利用できるようにしたほうが良いことがわかります。
Flutterでは、スタイルを汎用的に定義するために、ThemeData
とThemeExtensions
を利用する方法があります。
ThemeData
ThemeData
ではインスタンスを作ってMaterialApp
に指定すると、下位階層のWidgetのデフォルトスタイルになります。
ThemeDataの実装方法を見てみましょう。
MaterialApp(
theme: ThemeData(
textTheme: TextTheme(
bodyText2: TextStyle(
color: Colors.red
)
)
),
home: Text('どうする?GOする!'),
);
上記のようにMaterialApp
でThemeData
を指定した場合、デフォルトのテキスト色が赤色になり、MaterialApp
より下位階層にあるText Widgetのテキスト色は赤色になります。
このように。ThemeData
を定義することで、テキストとスタイル情報を分離することができます。
ThemeDataの課題点
ThemeData
を適用している状態で、一部のUIパーツのスタイルを変更したい時がしばしばあります。
ThemeDataを指定している時、TextButton Widgetに一部カスタムなスタイルを指定することを考えていきましょう。
Flutter 2.0までは、ThemeのtextButtonTheme
が持つstyle
をコピーし、必要な部分を変更した上で、そのstyle
をTextButton Widgetに設定することができました。
例えば、以下のように書くことができます。
TextButton( style: Theme.of( context, ).textButtonTheme.style.copyWith( backgroundColor: Colors.red, ), )
個々のUIパーツごとに、style
を毎回指定することのは微妙ですよね。
Flutter 3.0では、上記のことを解決できるThemeExtensionsという新しいシステムがリリースされました。
ThemeExtensions
ThemeExtensionsは、ThemeDataのオブジェクトにカスタムスタイルを追加することができるクラスで、個々のスタイルごとにsubclassを作成して利用することになります。
それでは、ThemeExtensionsを実際に使って見ましょう。
class GoTextButtonStyle extends ThemeExtension<GoTextButtonStyle> { const GoTextButtonStyle({ required this.backgroundColor, )}; final Color backgroundColor; }
backgroundColor
持つ、新しいカスタムスタイルを作ります。
ThemeExtensionのsubclassでカスタムスタイルを指定する場合には、以下の2つのメソッドをoverrideする必要があります。
copyWith
lerp
最初はcopyWith
について解説していきます。
@override
GoTextButtonStyle copyWith({
Color? backgroundColor,
}) => GoTextButtonStyle(
backgroundColor: backgroundColor ?? this.backgroundColor,
);
これは、ThemeData
クラスのスタイルと同じく、一般的なcopy
の実装をしていきます。
次にlerp
について解説していきます。
@override GoTextButtonStyle lerp(ThemeExtension<GoTextButtonStyle>? other, double t) { if (other is! GoTextButtonStyle) { return this; } return GoTextButtonStyle( backgroundColor: Color.lerp(backgroundColor, other.backgroundColor, t), ); }
複数のテーマが線形補間によって滑らかに変化するためのものです。
カスタムスタイルを作成したので、テーマシステムを拡張して適用していきましょう。
ThemeData(
extensions: ThemeExtension<dynamic>{
GoTextButtonStyle(
backgroundColor: Colors.red,
),
},
)
MaterialApp
に指定するThmeData
のextensions
にGoTextButtonStyle
を含めてください。
ThemeExtensionのスタイル作成が完了したので、早速使ってみましょう。
TextButton( style: Theme.of(context).extension<GoTextButtonStyle>().style, )
上記のようにすると既存のTextButtonStyle
を利用せず拡張されたGoTextButtonStyle
を利用することになります。
上記のようにGoTextButtonStyle
を作ったら様々な形のTextButton
を作ることも可能ですが、普段にはColorリストやTextStyleを持つThemeExtensionを作ることが多いかと思います。
綺麗なレイアウト作成に役に立ったら幸いです。