【Flutter×Rive】FlutterでFlareアニメーションを連続再生する方法

# 追記 2021.3.6

[Rive2](https://rive.app/) が公開されたのに伴い、[旧Rive](https://flare.rive.app/) のURLが変わりました。
本記事は、[旧Rive](https://flare.rive.app/) について記述してものになりますので、ご了承ください。

# 概要

Riveでは複数のアニメーションをもつFlareアニメーションを作ることができます。これを使えば、ルーレット的なものも簡単に実装できるのではと考え、実際にやってみました。

# ざっくりいうと・・・

* Flutterアプリのスプラッシュアニメーションにルーレット機能を組み込みます。
* Flutterアプリのスプラッシュアニメーションを組み込む手順は、記事「[【はじめてのRive】Flutterに2Dアニメーションスプラッシュスクリーンを導入する手順](https://qiita.com/kami_teru/items/740c348e8c198856f66c)」をお読みください。
* アニメーションを制御するため、`FlareControls`クラスを継承したコントローラークラスを用意します。

# この記事の検証に使ったリソース

Riveの作品。Forkして改変することも含め、自由に使っていただくことができますので、必要に合わせてご利用ください。

* [app-splash-roulette - atsuteru](https://flare.rive.app/a/atsuteru/files/flare/app-splash-roulette/preview)

Flutterのソースコード。こちらも改変することも含め、自由に使っていただくことができますので、必要に合わせてご利用ください。

* [flutter_examples/examples_splash_flare_flutter [tag:Qiita_examples_splash_flare_flutter_roulette_1.0] - atsuteru](https://github.com/atsuteru/flutter_examples/tree/Qiita_examples_splash_flare_flutter_roulette_1.0/examples_splash_flare_flutter)

## 環境

### バージョン

* Flutter 1.20.1

### プロジェクト設定

* この記事では、Flutterプロジェクトの`Use androidx.* artifacts`は`ON`に設定して検証しました。

# 本編

## 1) Riveで複数のアニメーションが定義された2Dアニメーションを作成する

私は `shuffle`という最初のアニメーションと、
`result_1`～`result_6`という後続のアニメーションを作成しました。

|![コメント 2020-08-13 132552.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109362/6a670464-116f-6c71-7d77-e599fd2662a0.gif)|![コメント 2020-08-13 132553.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109362/a8a00b52-b180-cd60-c427-b4575705938f.gif)|
|:-:|:-:|
|shuffle|result_6|

作り方の詳細は省略しますが、ざっくりいうと・・・

* まずは「[【はじめてのRive】Flutterに2Dアニメーションスプラッシュスクリーンを導入する手順](https://qiita.com/kami_teru/items/740c348e8c198856f66c)」の手順を参考にしてください。
* Riveの`ANIMATE`ページの画面左下、`ANIMATION`にある`+`ボタンを押すと、アニメーションを追加できます。
* 本記事で作成した[app-splash-roulette - atsuteru](https://flare.rive.app/a/atsuteru/files/flare/app-splash-roulette/preview)も参考にしてください。

いくつか、TIPS。

* 個々のアニメーションは独立しています。それぞれで`00:00:00`からのアニメーションを作成します。
* といいつつも・・・、<font color="Red">Flutterでアニメーションを連続再生すると、パーツのプロパティ値は引き継がれます。</font>
* つまり、アニメーション2で表示するパーツを、先行のアニメーションでOpacity=0にしていたら、連続再生時にはそのパーツはアニメーション2の上でもOpacity=0となります。なのでそのような場合は、アニメーション2の`00:00:00`でOpacity=1をセットしましょう。
* アニメーションは右クリックメニューから`Duplicate`を選択するとコピーできます。
* 回転は、0° -> 15° -> 350° -> 15° -> 350° という形で変化をつけました。

あと、複数のアニメーションの話ではないですが・・・`Drop Shaddow`や`Blur`などのエフェクトを多用すると、Android上でのアニメーションの再生がカクカクしがちです。(iPhoneではそんなことはないのに)。なのでエフェクトは必要最小限にとどめるようにしたほうがよさそうです。（あたりまえか・・・）。

## 2) 初期画面でスプラッシュアニメーションを連続再生できるように改修する

改修のベースは、「[【はじめてのRive】Flutterに2Dアニメーションスプラッシュスクリーンを導入する手順](https://qiita.com/kami_teru/items/740c348e8c198856f66c)」で作成されたソースとします。

### 2-a) アニメーションを制御するコントローラークラスを作成する

`FlareControls`を継承するコントローラークラス`_AppSplashController`を作成しましょう。

ここでは、スプラッシュアニメーションに対するコントローラークラスなので、このアプリのほかの個所で再利用することもないでしょうから、同じソースファイル内にプライベートクラスとして作ることにします。

`app_splash_page.dart`の下部に、以下のコードを追加しましょう。

```dart:app_splash_page.dart
class _AppSplashController extends FlareControls {

  final int number;
  final FlareCompletedCallback callback;
  _AppSplashController({
    @required this.number,
    this.callback
  });

  @override
  void initialize(FlutterActorArtboard artboard) {
    super.initialize(artboard);
    play('shuffle');
  }

  @override
  void onCompleted(String name) {
    if (name == 'shuffle') {
      play('result_$number');
      return;
    }
    callback?.call(name);
  }
}
```

* initializeメソッドで、最初に再生するアニメーション名をコード: `play('shuffle')` で指定しています。
* onCompletedメソッドは各アニメーションの再生が終わるごとに呼び出されるので、`shuffle`アニメーションが終わったら`result_`アニメーションを、コンストラクタで与えられた数値に基づいて再生するようにしています。
* `callback`は、すべてのアニメーションが完了したタイミングで処理を実行させるために必要です。

### 2-b) アニメーションを制御するコントローラークラスを利用する

では、作成したコントローラークラス`_AppSplashController`を組み込みましょう。
組み込むためには、FlareActorのcontrollerプロパティに`_AppSplashController`を指定します。

```dart:app_splash_page.dart
class _AppSplashPageState extends State<AppSplashPage>{

  @override
  Widget build(BuildContext context) {
    var resultNumber = Random().nextInt(6) + 1;
    ...(中略)...
        child: FlareActor(
          'assets/app_splash_roulette.flr',
          alignment: Alignment.center,
          fit: BoxFit.contain,
          controller: _AppSplashController(
            number: resultNumber,
            callback: (name) {
              Future.delayed(Duration(milliseconds: 500)).then((value) {
                Navigator.of(context).pushReplacementNamed("/home");
              });
            },
          ),
        )
    ...(中略)...
  }
}

```

* controllerプロパティに `_AppSplashController`を指定。
* `_AppSplashController`にランダムに求めた数値を渡すことで公正(※1)なルーレットを実現。
* `_AppSplashController`にアニメーション完了後のコールバックで、ホーム画面への遷移処理を実装。ルーレットの結果を味わえる(※2)500msの遅延を定義。

※1 厳密さに対するツッコミは無しで。。。
※2 個人の感想です。

## 動作確認

では早速、デバイスで動作させてみましょう。
以下、リリースビルドして動作させた結果です。

```powershell
flutter run --release
```

![コメント 2020-08-13 132551.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109362/62e3a731-3ace-79dc-b065-0d461a53d967.gif)

6が出ました。今日は良いことありそうです。

じゃなくて・・・。

アニメーションの連続再生がスムーズにできましたね！


# 最後に

コントローラークラスを使えば他にも、時間経過に応じて処理を組み込んだり(`advance`メソッド)、`Node`を使ってパーツを動かしたりなど、いろんなことが出来るようです。そのあたりはRiveの公式のドキュメント、チュートリアルや公開作品から学習することができます。

私ももっと学習を進めて、面白いことができるようになったら、また記事で公開したいなーと思うところ。。。

この記事が1人でも多くの方の助けになれば幸いです。