Flutter で CSS ライクにグラデーションを書く方法 🎨


以前書いた記事のコメントFlutter だと CSS のようにはグラデーションを書けない というやり取りをしました。
今回はなるべく CSS に近いかたちでコーディングをしたいというテーマです。

🏹 TL;DR

結局、そのままでは書けなかったのでプラグイン作っちゃいましたw

background: linear-gradient(162.42deg, #FFE5C5 17.61%, #D2B38B 50.22%, #F1DDC3 83.52%);

このような CSS を、

BoxDecoration(
  gradient: CssLike.linearGradient(162.42, ['#FFE5C5 17.61%', '#D2B38B 50.22%', '#F1DDC3 83.52%']),
);

Flutter でこのように書くことができるようになります。

🎨 gradient_like_css

これが作成したプラグインです。
(実は初めて作りましたw)
pub.dev に公開しているので、よかったら使ってみてください。

インストール

pubspec.yaml に依存関係を追加します。

pubspec.yaml
dependencies:
  gradient_like_css: ^0.5.0

Pub get しましょう。

$ flutter pub get

使い方

インポートします。

import 'package:gradient_like_css/gradient_like_css.dart';

あとは BoxDecorationgradientCssLike.linearGradient() の結果を設定するだけ。

BoxDecoration(
  gradient: CssLike.linearGradient(-225, ['#69EACB', '#EACCF8 48%', "#6654F1"]),
);

第一引数の angleOrEndAlignment には角度や Alignment を指定できます。
第二引数の colorStopList にはグラデーションの色や色経由点(開始点や終了点など)を指定できます。
CSS に近いかたちにするために、文字列の List にしています。
Flutter は可変長引数が使えないですからねぇ……)

詳しくはサンプルを見てみてください!

🌈 グラデーションのサンプル

CSSlinear-gradient() と同じグラデーションを作成するようにサンプルを作成しました。
元ネタは こちら です。

角度を指定しないグラデーション

Container(
  height: 300,
  width: 300,
  decoration: BoxDecoration(
    gradient: CssLike.linearGradient(null, ['#e66465', '#9198e5']),
  ),
);

angleOrEndAlignmentnull を指定した場合、 CSSlinear-gradient() にあわせて 180° (つまり下向き)のグラデーションになります。
色は # 始まりのカラーコードを使うことができます。

角度 45° のグラデーション

Container(
  height: 300,
  width: 300,
  decoration: BoxDecoration(
    gradient: CssLike.linearGradient(45, ['red', 'blue']),
  ),
);

角度は double で指定できます。負の数や、 360 を超える数も使うことができます。
色はカラーコード以外にも、 X11/CSS3 color の色名も使うことができます。

開始点が 60% のグラデーション

Container(
  height: 300,
  width: 300,
  decoration: BoxDecoration(
    gradient: CssLike.linearGradient(135, ['orange', 'orange 60%', 'cyan']),
  ),
);

colorStopList には色だけではなく stop 、つまりグラデーションの色経由点を指定できます。
上の例では orange の開始点に 60% を指定しています。
colorStopList は半角スペースで区切った文字列のリストです。

複数の色経由点があるグラデーション

Container(
  height: 300,
  width: 300,
  decoration: BoxDecoration(
    gradient: CssLike.linearGradient(Alignment.centerRight, 
      ['red 20%', 'orange 20% 40%', 'yellow 40% 60%', 'green 60% 80%', 'blue 80%']),
  ),
);

色経由点はふたつ指定することができ、隣り合う色に同じ色経由値を指定することで縞模様を作ることもできます。
(ぼくは使ったことがありませんが)

💡 開発したことによる気づき

Alignment の反転は演算子でできる

LinearGradientbeginendAlignment を指定するのですが、このプラグインでは角度から Alignment を生成しています。
角度で end が決まり、必然的に begin は逆方向になりますので、

final begin = Alignment(x, y);
// begin の逆向き
final end = Alignment(-x, -y)

のように書いていました。

でも実は Alignment は演算子オーバーロードしているようで、

final begin = Alignment(x, y);
// begin の逆向き
final end = -begin;

のように書けるんです!
便利!!

double の誤差には注意が必要

これも角度から Alignment を生成する際に出くわしたのですが、 45° のグラデーションと 405° のグラデーションは同じものになるはずが、ユニットテストの結果 NG になってしまいました。

// 405° は (45° + 360°) で同じ方向のグラデーションになるはずがマッチしない……
expect(
  CssLike.linearGradient(45, ['red', 'blue']),
  CssLike.linearGradient(405, ['red', 'blue']),
);

原因は double の誤差によるものでした。
演算をしている部分で丸め処理をしてやれば OK です。

ただ、丸め処理の書き方がわからず、

double.parse(math.sin(radians).toStringAsPrecision(8))

のような文字列を経由する格好の悪い書き方になっています…… 😇
良い書き方があれば教えて下さい!

🙇 まとめ

FlutterCSS ライクに書けるようになりました!
(なんとも力技w)

プラグイン開発が初めてですし、英語は苦手(定期)、いろいろとボロボロな気がしますので、Qiita のコメントでもよいのでご意見いただけると嬉しいです 😋
(プルリクはもっと嬉しいです!!)