[Flutter] beta版のRive2でアニメーション付きダイアログを実装してみた

本記事は[Flutter #2 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/flutter-2)のの15日目の記事です:snowman:
# アニメーションの使い所
アプリ開発においてアニメーションは必須ではありませんが、適切なタイミングで使うことでユーザー体験が大きく向上します。

ここでいう「適切なタイミング」とはアプリのメイン機能を使った時などが挙げられます。特に初めてユーザーがメイン機能を使った（投稿・注文などを行った）タイミングでは、**今後も継続して利用してもらうためにユーザーの心をしっかり掴みたい**ですよね。初デートが重要なのと同じ話です。失敗したら2回目はありません。

そういった時にアニメーションが強い味方となるのではないかと考えています。（もちろん、後押しをするという意味です。良い機能が実装できている前提で。）

例えばですが、UberEatsの注文後に出てくる料理のアニメーションって「ご飯が来るぞ！」というワクワク感を与えてくれますよね。

Flutterで簡単なアニメーションを実装する場合は[Animated系Widget](https://flutter.dev/docs/development/ui/widgets/animation)をうまく利用することで簡単に実装できるのですが、複雑になればなるほど、職人芸のような実装が必要になってしまいます。

今回は[Rive2](https://beta.rive.app/)というサービスを利用して、アニメーションを作成しFlutterに追加する流れをご紹介したいと思います。

# Riveとは
![スクリーンショット 2020-12-15 14.35.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405550/6d53ebd1-1c6b-ee86-f8f8-9e9a83bfcc2a.png)
ざっくりRiveついて説明をすると、SchechやFigmaといったデザインツールのアニメーション特化版といった感じです。非デザイナーでも[公式チュートリアル動画](https://www.youtube.com/channel/UCPal2R1FxwRTPylhP_7ofEg)をみれば、操作方法は理解できると思います。

元々はFlareというサービスだったものがRive1となり、現在のRive2(beta)になっています。

どうやら[flare_flutter](https://pub.dev/packages/flare_flutter)パッケージで`.flr`ファイルを使うRive1の新規登録は終了しており、新しい人は全員[rive](https://pub.dev/packages/rive)パッケージで`.riv`ファイルを使うRive2の登録になるようです。（betaなのに？）![スクリーンショット 2020-12-14 23.09.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405550/95bc42e9-f206-5d0e-8a96-d4ad3a325a94.png)

# Rive2の新機能
### 同時編集ができる(要課金)
Figmaなどにもありますが、便利ですよね。アニメーションの実装が頻繁でないチームにはそこまでメリットはないかもしれません...

### Webやデスクトップアプリにも対応した
RiveはFlare時代からFlutterとの関わりが強く、最近のFlutterのマルチプラットフォーム化に合わせてRiveも様々なプラットフォームで使えるようになってきているみたいです。
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Thank you to our friends at <a href="https://twitter.com/hashtag/flutterdev?src=hash&amp;ref_src=twsrc%5Etfw">#flutterdev</a>. Their tech has made our Rive 2 native web and desktop apps a reality! <a href="https://twitter.com/timsneath?ref_src=twsrc%5Etfw">@timsneath</a> <a href="https://twitter.com/marihasnany?ref_src=twsrc%5Etfw">@marihasnany</a> <a href="https://twitter.com/csells?ref_src=twsrc%5Etfw">@csells</a> <a href="https://twitter.com/FlutterDev?ref_src=twsrc%5Etfw">@FlutterDev</a> <a href="https://twitter.com/_eseidel?ref_src=twsrc%5Etfw">@_eseidel</a> <a href="https://t.co/7cpEa7zIJn">https://t.co/7cpEa7zIJn</a></p>&mdash; Rive (@rive_app) <a href="https://twitter.com/rive_app/status/1288497831234269185?ref_src=twsrc%5Etfw">July 29, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


その他色々な部分でUXの改善が行われたようです。

# モチベーション
現在開発しているサービスのメイン機能が、「地図上に地点をシェアする」というもので、シェアが完了した時にこのような自動で閉じるダイアログを表示させています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405550/60684954-8e12-63dd-6e76-484dc9469ade.jpeg" width="500">
悪くはないのですが、少し味気ないですよね。これのおかげで「シェアをした」体験がユーザーに強く印象に残るということはなさそうです。せっかくシェアしてもらったのだから、このタイミングでしっかり盛り上げるべきです。
**「良い気持ちでシェアを完了してもらい、2回目以降のシェアにつなげる」**という目的を達成すべく、Riveで作ったアニメーションをここに追加します。

# Riveファイルの作成
今回アニメーションしてもらうのはこちらの雪だるまです。開発中のサービスのロゴなのですが、サービスについては今後別の記事にできればと思います。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405550/b76ed90d-d2c4-5e59-f560-72a60a66317a.png" width="200">
パーツごとの制御をしたいので、SVGファイルをドラッグ＆ドロップします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405550/0871f7ce-2825-3e8e-f532-fc71719e06b3.png" width="800">
右上のトグルボタンで`Design`から`Animate`に変更します。`Design`モードで新しい図形を追加したり形を変えたりして、`Animate`モードでそれぞれの図形に動きをつけます。
![スクリーンショット 2020-12-15 4.25.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405550/55ca37c6-9db7-5820-f08b-9d9de25c31e6.png)
ここで重要なのは、`Animate`モードで初期位置や初期サイズの調整を行わないことです。`Animate`**モードにした瞬間からパーツの移動やリサイズは全てアニメーションの一部として見なされ、下のマーカー（アニメーションのスケジュールのようなもの）に記録される**からです。
![スクリーンショット 2020-12-15 4.35.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405550/e124f0d4-7efe-0aa5-cd51-1d950fca3a81.png)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405550/7561cdf4-aa3e-b369-ce4a-41aeaafd550f.png" width="400">
アニメーションに名前をつけます。**Flutter側ではこの名前でアニメーションを呼び出します。**
また、複数のアニメーション（ジャンプ、ダッシュなど）を作っても一つのRiveファイルに保存することができ、**呼び出し側で名前を切り替えるだけで自由にアニメーションを切り替えることができます。**

あまり凝りすぎると沼にハマってしまうので、今回はジャンプして喜ぶ（？）ようなシンプルなアニメーションにしようと思います。
![スクリーンショット 2020-12-15 5.38.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405550/e46c9e12-e1c0-da2c-3f06-3978abdc5e84.png)
こんな感じでマーカーをセットします。全体で2秒のタイムラインで、マーカーはその行のパーツがどの時間に、どの座標にいるかの情報を持っています。ここら辺の詳しい使い方は[公式チュートリアル動画](https://www.youtube.com/channel/UCPal2R1FxwRTPylhP_7ofEg)を参考にしてください。

ダイアログの表示中はずっと動いていて欲しいので、アニメーションの種類は`Loop`を選択します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405550/883f7a7b-ed62-56ad-c3e0-010336b5f82e.png" width="300">

ダウンロードすると`.riv`の拡張子のファイルが入手できます。たったの6KB。軽いですね。
![スクリーンショット 2020-12-15 5.27.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405550/ed64c3d5-04be-a8e2-2af4-1c30c4cac9d1.png)

# Flutterに追加

ライブラリを追加して、

```yaml:pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  rive: ^0.6.4 # 追加する
```
プロジェクトに`.riv`ファイルを追加し、`initState()`などの初期化のタイミングでこのようにファイルを読み込みます。


```dart
import 'package:flutter/services.dart';
import 'package:rive/rive.dart';
...

Artboard _riveArtboard;
RiveAnimationController _controller;

rootBundle.load('assets/share_animation.riv').then(
  (data) async {
    final file = RiveFile();
    if (file.import(data)) {
      final artboard = file.mainArtboard;
      artboard.addController(_controller = SimpleAnimation('icon_animation')); // 先ほど作成したアニメーションの名前を入れる
      setState(() => _riveArtboard = artboard);
    }
  },
);
```
今回はダイアログ上でループ再生させるだけなので、`RiveAnimationController`を使った再生・停止の処理は割愛します。

ダイアログの部分はこのような感じで実装します。

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

showDialog(
  context: context,
  builder: (context) {
    Future.delayed(
      const Duration(milliseconds: 3000), // 一定時間後に自動で閉じてもらう
      () => Navigator.of(context).pop(),
    );
    return SimpleDialog(
      title: Text(
        'シェアが完了しました！',
        style: TextStyle(fontWeight: FontWeight.bold),
      ),
      children: <Widget>[
        SizedBox(
          height: 100,                           // サイズが大きすぎたので調整する
          child: Rive(artboard: _riveArtboard),  // Riveアニメーションの部分！
        ),
        Center(
          child: Text(
            'ご協力ありがとうございます！'),
            style: TextStyle(fontSize: 17),
        ),
      ],
    );
  }
);
```

完成したダイアログがこちら。
gifなので無限ループしていますが、二回ジャンプしてダイアログが閉じます。
![ezgif-6-1f69f49db839.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405550/033b48b6-3dcb-fe8a-dbd0-a0a861184448.gif)
...なんか喜んでジャンプしているというより、準備体操みたいになってしまいましたね笑

シュールなアニメーションですが、これでユーザーにほんの少しでも楽しい（嬉しい）気持ちになってもらうことができれば目標達成です。最初の無機質なダイアログよりはマシになった気がします。
やったね。2回目のデートの約束も取り付けることができました。

# まとめ
少しRiveの編集画面（WEBブラウザ）が重い感じはありましたが、アニメーションを扱うので仕方がないのかもしれません。デスクトップアプリ版が出てくることを期待しています。

Flutterはアプリサイズが大きくなりがちなので、こういうリッチなUIを組む際に容量を食わないのは嬉しいですね。

それではみなさん、良いお年を。