ThemeExtension | Decoding Flutterの訳
When I started my software career as a web developer, one of the first things I learned was not to mix my semantic information with my presentation rules.
私がweb developerとしてソフトウェアキャリアをスタートした時、初めに学んだことの一つとして、意味的な情報(HTML)とプレゼンテーションルール(CSS)を混合させないことでした。
On the web, this concept comes to life by storing your semantics in HTML and your cosmetics in CSS.
Webでは、この概念は、セマンティクスをHTMLに、コスメティックをCSSに保存することで実現します。
Of course, you can inline your styles directly into your HTML.
もちろんスタイル部分をHTMLの中に含めることも可能ですが、
But if you do, you shall not pass code review.
しかしそれをするとコードレビューをパスできないでしょう。
A decade later, when I found Flutter, I knew I liked what I saw, but the common mixing of my raw information with its presentation was unsettling.
10年後、Flutterに出会ったとき、自分が好きなものであることはわかったのですが、自分の生の情報とその表現が共通に混ざり合っていることに違和感を覚えました。
I looked up how to stop doing this and was, of course, pointed at Flutter’s theme system.
これをやめるにはどうしたらいいかと調べたら、もちろんFlutterのテーマシステムを示されました。
I thought it’s neat and then I dug into it a little bit and thought. “What?”
すてきだなぁと思って、ちょっと掘り下げてみたら、「え?何?」となりました。
And so today we’re going to talk about a few tricks and a new API to squeeze the most juice out of your apps theme and make sure that your custom widgets rarely require inline styling.
そこで本日は、アプリのテーマから最大限の効果を引き出し、カスタムウィジェットでインラインスタイルが必要になることはめったにないようにするためのいくつかのトリックと新しいAPIについて説明します。
Hello, widget’s department, who’s this?
はい、ウィジェット部です、どちら様ですか?
Raw styling code? I told you to stop calling.
直書きスタイリングコード? 電話してくるなと言ったでしょ!
To start, let’s explore how material widgets use Flutter’s theming system to figure out how to dress themselves.
まず、マテリアルウィジェットがFlutterのテーマシステムを使用して自分自身をドレスアップする(飾る)方法を理解する方法を調べてみましょう。
Put simply, most material widgets have three places they look when resolving each of their cosmetic attributes.
簡単に言うと、ほとんどのマテリアルウィジェットには、それぞれの外観の属性を解決するときに見る場所が3つあります。
The first is any raw parameters exposed in their constructor.
第一は、それぞれのコンストラクタで指定できる、直接のパラメーターです。
Sometimes this is in top-level color or padding parameters, and sometimes it’s in dedicated style parameters that wall off all the details.
これは、トップレベルのカラーまたはパディングパラメータである場合もあれば、すべての詳細を覆い隠す専用のスタイルパラメータである場合もあります。
Card(color: Colors.blue) Text('Hello', style: TextStyle())
Either way, these are all those random styling parameters we all use every day.
どちらにせよ、これらの多様なスタイリング用パラメーターは私たちが毎日使っているものです。
At least for me, the most common situation is this, where I start form my existing theme but modify it in some way, because my current purposes were hard to capture in the actual theme itself.
少なくとも私の場合は、現在の目的が実際の(既存の)Themeそのものでは捉えにくいので、既存のテーマから出発して何らかの形で修正する、このような状況が最も多いのです。
The second place material widgets look is up the tree to an inherited Theme widget and its associated ThemeData instance.
二番目にマテリアルウィジェットが探す場所は、ウィジェットツリーの上方のinherited Theme widgetと関連するThemeDataインスタンスです。
This is what Count Dracula taught us in his guest widget of the week appearance back in October of 2021.
これは、Count Draculaが2021年10月に登場したWidget of the weekで私たちに教えてくれたことです。
Pray that you remember it.
覚えているでしょうか?
Note that this is what happens when you supply a theme or a dark theme to your material app.
なお、MaterialAppにthemeやdarkThemeをセットすると、このような現象が発生します。
Internally, it sticks those in a theme widget.
内部的には、Themeウィジェットに貼り付けています。
The third option, if no one has picked up the phone yet, is the default theme, which can be found at the ThemeData.light or ThemeData.dark factory constructors.
3番目のオプションは、まだ誰も電話を受け取っていない場合、デフォルトのテーマです。これは、ThemeData.lightまたはThemeData.darkファクトリコンストラクターで取得できます。
And this is what produces the infamous blue-white color of the counter app.
そして、これがカウンターアプリの悪名高い青白の色を生み出すものです。
Within a given material widget, the code that looks in these three places will often look like this.
与えられたMaterialAppの中で、これらの3つの場所を見るコードは、多くの場合、次のようになります。
Step1– the widget checks any of its own parameters.
ステップ1 : ウィジェットは自分のパラメーターを確認します。
This part is optional because not every widget has such parameters for every detail.
全てのウィジェットがその全ての詳細に関してそれ用のパラメーターを持っているわけではありませんので、このステップはオプショナルです。(ある場合のみ実行される)
Step2– the widget asks the theming system.
ステップ2: ウィジェットはテーマシステムに問い合わせます。
And step3 is sneakily wrapped up in step2, because the material widget app adds a default theme if you don’t.
そしてstep3はstep2にこっそり包まれています。なぜなら、マテリアルウィジェットを使ったアプリはあなた自身がtheme/darkThemeをセットしない場合、デフォルトのThemeを(自動的に)追加するからです。
So this will return something.
And this all happens on a per-attribute basis, which is why you can use the convenient TextButton.styleFrom method that we talked about in the last episode.
そして、これはすべて属性ごとに発生します。そのため、前回のエピソードで説明した便利なTextButton.styleFromメソッドを使用できます。
TextButton( style: TextButton.styleFrom( backgroundColor: Colors.red, ), )
In this scenario, our TextButton will resolve its background in step one, and for everything else, continue on to steps two and three.
このシナリオでは、TextButtonはステップ1でその背景を解決し、それ以外ウィジェットは、ステップ2と3に進みます。
Now, this is all good and fun, but here’s the rub.
さて、ここまではいいのですが、一つの問題点として、
Material widgets are hooked into this pattern directly, so anything they care about just so happens to appear in one of the theme variables.
マテリアルウィジェットはこのパターンに直接フックされているため、マテリアルウィジェットが気にするものはすべて、偶然にもテーマ変数のいずれかに表示されることになります。
How convenient.
非常に便利なことです。
It’s almost as if the same people wrote sides of that code.
まるで同じ人がそのコードの側面を書いたかのようです。
But how does that help your widgets?
しかしどのようにあなたのウィジェットを助けるでしょう?
Flutterフレームワークを開発している開発主体だから、Themeクラスの中にTextStyle,ButtunStyleなどを定義することができたわけだが、それ以外の人が定義したカスタムのクラスのスタイル用クラスをThemeクラスに含めることは(ThemeExtensionの仕組みができるまでは)できないですね、ということを言いたいのだと思われる。
If you’re designing heavily custom control and want this same ordered approach, which allows most or all of your styling code to stay out of your raw widgets, you’re out of luck, until that new API I mentioned earlier.
もしあなたが大規模なカスタムコントロールを設計していて、これと同じ順序のアプローチ、つまりスタイリングコードのほとんどまたはすべてを生のウィジェットに含まないようにしたい場合、先に述べた新しい API (ThemeExtensions)が出てくるまでは運が悪かったとしか言いようがありません。
Coming in the next stable release of Flutter is a new system called ThemeExtensions.
Flutterの次の安定版では、ThemeExtensionsという新しいシステムが導入されます。
(動画は2022年5月4日公開)
To start, use the extensions parameter and either a brand new theme or a copy of an existing theme.
まず、extensionsパラメーターと、新しいテーマまたは既存のテーマのコピーを使用します。
Into that variable, put an iterable of ThemeExtensions and dynamic type.
extensionsパラメータにThemeExtension<dynamic>{ }をセットする。
Next, instantiate a class of your own creation that contains whatever cosmetic details are important to you.
次に、あなたにとって重要なスタイルの詳細を含む、あなた自身の作ったクラスをインスタンス化します。
Imagine you’re working on an all new widget, which is kind of like a card/hero image hybrid.
カードとヒーローの画像のハイブリッドのような、まったく新しいウィジェットで作業していると想像してみてください。
4:18
To support its build method, you decide to introduce a HeroCardStyle class.
そのbuildメソッドをサポートするために、HeroCardStyleクラスを導入することにしました。
This means you can use the same tricks above in your HeroCard build method and check for all the variables incrementally.
つまり、HeroCardのビルドメソッドに上記と同じトリックを使い、すべての変数を段階的にチェックすることができるのです。
The only the problem is Flutter’s ThemeData class doesn’t know about your custom HeroCardStyle class, so would have no idea what you’re talking about, which is where extensions come in.
唯一の問題は、FlutterのThemeDataクラスがカスタムHeroCardStyleクラスを認識していないため、何について話しているのかわからないことです。
ここで拡張機能(Theme extensions)が登場します。
Back to our Theme class, register this HeroCardStyle as an extension.
Themeクラスに戻り、このHeroCardStyleをエクステンションとして登録します。
Of course, the class doesn’t exist yet, so let’s write it.
また私たちはHeroCardStyleクラスを定義していませんので、今から定義しましょう。
To be valid, our class should subclass ThemeExtension and use itself as its own generic.
有効であるためには、HeroCardStyleクラスをThemeExtensionのサブクラスにして、それ自体(HeroCardStyle)を独自のジェネリックとして使用する必要があります。
Trippy, I know.
不思議な感じがしますね、わかります。
Obviously, it needs to know about all of its relevant variables.
当然、HeroCardStyleクラスの全てのフィールドを認識する必要があります。
class HeroCardStyle extends ThemeExtension<HeroCardStyle> { const HeroCardStyle( required this.backgroundColor, required this.borderRadius, ); final Color backgroundColor; final double borderRadius; }
And beyond that, there are two methods we must override.
そして二つのメソッドをオーバーライドする必要があります。
The first is copyWith, so that our extension can play along with the ThemeData class’s normal copying mechanisms.
ひとつは copyWithメソッドです。ThemeData クラスの通常のコピー機構を利用できるようにします。
@override HeroCardStyle copyWith({ Color? backgroundColor, double? borderRadius }) => HeroCardStyle( backgroundColor : backgroundColor ?? this.backgroundColor, borderRadius: borderRadius ?? this.borderRadius, );
And second is lerp, because all theme things need to know how to linearly interpolate themselves.
そして2つ目がlerpです。なぜなら、すべてのテーマに関するものは、自分自身を線形に補間する方法を知っている必要があるからです。
@override HeroCardStyle lerp(ThemeExtension<HeroCardStyle>? other, double t){ if(other is! HeroCardStyle){ return this; } return HeroCardStyle( backgroundColor: Color.lerp(backgroundColor, other.backgroundColor, t), borderRadius: lerpDouble(borderRadius, other.borderRadius, t), ); }
Finally, everything is in order to finish our HeroCard widget.
最後に、HeroCardウィジェットの定義を完成させます。
In its build method, check the parameters you exposed first, then fall back to the ThemeExtenstion by way of Theme.of(context).extension, using our type as the generic.
そのビルドメソッドでは、まず公開したパラメータをチェックし、その後、我々の型をジェネリックとして、Theme.of(context).extensionによってThemeExtenstionにフォールバックしてください。
@override Widget build(BuildContext context) { HeroCardStyle defaultStyle = Theme.of(context).extension<HeroCardStyle>(); /// Grab each style incrementally to allow yourself to supply /// bare-minimum [HeroCardStyle] values for 'style'. Color backgroundColor = style?.backgroundColor ?? defaultStyle?.backgroundColor; BorderRadius borderRadius = style?.borderRadius ?? defaultStyle?.borderRadius; return Widgets( backgroundColor: backgroundColor, borderRadius: borderRadius, child: ..., ); }
まずコンストラクタで値を受け取ったかをチェックし、受け取っていない場合はTheme.of(context).extensionによって取得した値を使用する、ということ。表示されているコードの通り。
Finally, we have a HeroCardStyle instance, and we can get to assembling whatever widgets make up a HeroCard.
ということで、HeroCardStyleインスタンスにより、HeroCardを構成するウィジェットを組み立てることができるようになりました。
And it’s worth nothing that your ThemeExtensions don’t have to be this fancy and serve a specific widget.
また、ThemeExtensionsは、このように派手で、特定のウィジェットを提供する必要がないことは言うまでもありません。
They could just hold some extra colors and expand your app’s color palette.
色数を増やして、アプリのカラーパレットを拡張するだけでもいい。
Before ThemeExtensions, it wasn’t easy to keep your presentation rules out of your custom widget.
ThemeExtensionsが登場する前は、プレゼンテーションルールをカスタムウィジェットから除外するのは簡単ではありませんでした。
But now, it can be done.
しかし現在では簡単になりました。
Hopefully, these tricks help you write cleaner, more predictable layout.
これらのテクニックが、よりきれいで予測可能なレイアウトを書くのに役立つことを願っています。
参考