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

[以前書いた記事のコメント](https://qiita.com/azukisiromochi/items/bedf81bae8f0470c58a3#comments) で **`Flutter` だと `CSS` のようにはグラデーションを書けない** というやり取りをしました。
今回はなるべく `CSS` に近いかたちでコーディングをしたいというテーマです。 

## 🏹 TL;DR

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

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

このような `CSS` を、

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

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

## 🎨 [gradient_like_css](https://pub.dev/packages/gradient_like_css)

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

### インストール

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

```yaml:pubspec.yaml
dependencies:
  gradient_like_css: ^1.0.0
```

`Pub get` しましょう。

```shell
$ flutter pub get
```

### 使い方

インポートします。

```dart
import 'package:gradient_like_css/gradient_like_css.dart';
```

あとは `BoxDecoration` の `gradient` に `linearGradient()` の結果を設定するだけ。

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

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

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

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

`CSS` の `linear-gradient()` と同じグラデーションを作成するようにサンプルを作成しました。
元ネタは [こちら](https://developer.mozilla.org/ja/docs/Web/CSS/linear-gradient()) です。

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

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

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

<img src="https://raw.githubusercontent.com/azukisiromochi/gradient_like_css/main/display/CssLike_linearGradient__example1.png" height="300px" width="300px">

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

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

角度は `double` で指定できます。負の数や、 `360` を超える数も使うことができます。
色はカラーコード以外にも、 [X11/CSS3 color](https://en.wikipedia.org/wiki/Web_colors#X11_color_names) の色名も使うことができます。

<img src="https://raw.githubusercontent.com/azukisiromochi/gradient_like_css/main/display/CssLike_linearGradient__example2.png" height="300px" width="300px">

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

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

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

<img src="https://raw.githubusercontent.com/azukisiromochi/gradient_like_css/main/display/CssLike_linearGradient__example3.png" height="300px" width="300px">

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

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

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

<img src="https://raw.githubusercontent.com/azukisiromochi/gradient_like_css/main/display/CssLike_linearGradient__example4.png" height="300px" width="300px">

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

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

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

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

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

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

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

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

### `double` の誤差には注意が必要

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

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

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

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

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

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

## 🙇 まとめ

`Flutter` で `CSS` ライクに書けるようになりました！
（なんとも力技ｗ）

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