【Flutter】CustomPaintを用いてLoadingを描いてみる（応用編）

FlutterでLoadingを表すアニメーションWidgetが何個がありますが（例えば、`CircularProgressIndicator`とか）、自分なりのアニメーションのカスタマイズしたい場合、`CustomPaint`を用いて描くことも可能です。
<img src=https://storage.googleapis.com/zenn-user-upload/c302798dde3b-20230418.gif width=400>

# 回転円
まずはシンプルな円回転を描いてみましょう。
下記の例では、`AnimationController`の値に応じてアークを連続的に回転させるだけです。
<img src=https://storage.googleapis.com/zenn-user-upload/d8be4df7dd7b-20230418.gif width=400>

https://github.com/ling350181/flutter_ui/blob/main/lib/application/loading/simple_loading.dart

# 光輪（ハロー）
円の光輪（ハロー）は`Paint`のmaskFilterの属性で実現できます。
```dart
  /// See also:
  ///
  ///  * [Canvas.drawShadow], which is a more efficient way to draw shadows.
  const MaskFilter.blur(
    this._style,
    this._sigma,
  ) : assert(_style != null),
      assert(_sigma != null);
```
上記の属性でぼやけた影を生成できます。パラメーター`_sigma`がぼかしの程度を表します。
```dart
paint.maskFilter = MaskFilter.blur(BlurStyle.solid, 4);
```

その`_sigma`をアニメーションの値にすると、光輪の動画生成できます。
```dart
  final Animatable<double> breatheTween = TweenSequence<double>(
    <TweenSequenceItem<double>>[
      TweenSequenceItem<double>(
        tween: Tween<double>(begin: 0, end: 4),
        weight: 1,
      ),
      TweenSequenceItem<double>(
        tween: Tween<double>(begin: 4, end: 0),
        weight: 1,
      ),
    ],
  ).chain(CurveTween(curve: Curves.decelerate));
  paint.maskFilter = MaskFilter.blur(BlurStyle.solid, breatheTween.evaluate(animation));
```
<img src=https://storage.googleapis.com/zenn-user-upload/4978351a05ca-20230418.gif width=400>

# 着色

前の章でPathのpaint属性で[shader着色](https://zenn.dev/cxj/books/07def04506182d/viewer/466b9b#shader(%E3%82%B7%E3%82%A7%E3%83%BC%E3%83%80%E3%83%BC))を紹介したことあるので、今回をこちらの知識で光輪を着色しようと思います。

```dart
    // 光輪
    paint.maskFilter = MaskFilter.blur(BlurStyle.solid, breatheTween.evaluate(animation));
    List<Color> colors = [
      Colors.red,
      Colors.orange,
      Colors.yellow,
      Colors.green,
      Colors.cyan,
      Colors.blue,
      Colors.purple,
    ];

    colors.addAll(colors.toList());

    final List<double> pos = List.generate(colors.length, (index) => index / colors.length);
    // shader
    paint.shader = ui.Gradient.sweep(Offset.zero, colors, pos, TileMode.clamp, 0, 2 * pi);
```
<img src=https://storage.googleapis.com/zenn-user-upload/cf756db7e99d-20230418.gif width=400>

# 光輪を回転させる
最後は光輪と回転する円を結合すれば、ぐるぐる回るLoading円ができます。
```dart
    canvas.translate(size.width / 2, size.height / 2);
    final Paint paint = Paint()
      ..style = PaintingStyle.stroke
      ..color = Colors.purple
      ..strokeWidth = 1;
    // 光輪
    paint.maskFilter = MaskFilter.blur(BlurStyle.solid, breatheTween.evaluate(animation));
    List<Color> colors = [
      Colors.red,
      Colors.orange,
      Colors.yellow,
      Colors.green,
      Colors.cyan,
      Colors.blue,
      Colors.purple,
    ];

    colors.addAll(colors.toList());

    final List<double> pos = List.generate(colors.length, (index) => index / colors.length);
    // shader
    paint.shader = ui.Gradient.sweep(Offset.zero, colors, pos, TileMode.clamp, 0, 2 * pi);
    Path path = Path();
    path.addOval(Rect.fromCenter(center: Offset.zero, width: 200, height: 200));
    canvas.save();
    canvas.rotate(animation.value * 2 * pi);
    canvas.drawPath(path, paint);
    canvas.restore();
```
<img src=https://storage.googleapis.com/zenn-user-upload/c302798dde3b-20230418.gif width=400>