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


本記事はFlutter #2 Advent Calendar 2020のの15日目の記事です

アニメーションの使い所

アプリ開発においてアニメーションは必須ではありませんが、適切なタイミングで使うことでユーザー体験が大きく向上します。

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

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

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

Flutterで簡単なアニメーションを実装する場合はAnimated系Widgetをうまく利用することで簡単に実装できるのですが、複雑になればなるほど、職人芸のような実装が必要になってしまいます。

今回はRive2というサービスを利用して、アニメーションを作成しFlutterに追加する流れをご紹介したいと思います。

Riveとは


ざっくりRiveついて説明をすると、SchechやFigmaといったデザインツールのアニメーション特化版といった感じです。非デザイナーでも公式チュートリアル動画をみれば、操作方法は理解できると思います。

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

どうやらflare_flutterパッケージで.flrファイルを使うRive1の新規登録は終了しており、新しい人は全員riveパッケージで.rivファイルを使うRive2の登録になるようです。(betaなのに?)

Rive2の新機能

同時編集ができる(要課金)

Figmaなどにもありますが、便利ですよね。アニメーションの実装が頻繁でないチームにはそこまでメリットはないかもしれません...

Webやデスクトップアプリにも対応した

RiveはFlare時代からFlutterとの関わりが強く、最近のFlutterのマルチプラットフォーム化に合わせてRiveも様々なプラットフォームで使えるようになってきているみたいです。

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

モチベーション

現在開発しているサービスのメイン機能が、「地図上に地点をシェアする」というもので、シェアが完了した時にこのような自動で閉じるダイアログを表示させています。

悪くはないのですが、少し味気ないですよね。これのおかげで「シェアをした」体験がユーザーに強く印象に残るということはなさそうです。せっかくシェアしてもらったのだから、このタイミングでしっかり盛り上げるべきです。
「良い気持ちでシェアを完了してもらい、2回目以降のシェアにつなげる」という目的を達成すべく、Riveで作ったアニメーションをここに追加します。

Riveファイルの作成

今回アニメーションしてもらうのはこちらの雪だるまです。開発中のサービスのロゴなのですが、サービスについては今後別の記事にできればと思います。

パーツごとの制御をしたいので、SVGファイルをドラッグ&ドロップします。

右上のトグルボタンでDesignからAnimateに変更します。Designモードで新しい図形を追加したり形を変えたりして、Animateモードでそれぞれの図形に動きをつけます。

ここで重要なのは、Animateモードで初期位置や初期サイズの調整を行わないことです。Animateモードにした瞬間からパーツの移動やリサイズは全てアニメーションの一部として見なされ、下のマーカー(アニメーションのスケジュールのようなもの)に記録されるからです。


アニメーションに名前をつけます。Flutter側ではこの名前でアニメーションを呼び出します。
また、複数のアニメーション(ジャンプ、ダッシュなど)を作っても一つのRiveファイルに保存することができ、呼び出し側で名前を切り替えるだけで自由にアニメーションを切り替えることができます。

あまり凝りすぎると沼にハマってしまうので、今回はジャンプして喜ぶ(?)ようなシンプルなアニメーションにしようと思います。

こんな感じでマーカーをセットします。全体で2秒のタイムラインで、マーカーはその行のパーツがどの時間に、どの座標にいるかの情報を持っています。ここら辺の詳しい使い方は公式チュートリアル動画を参考にしてください。

ダイアログの表示中はずっと動いていて欲しいので、アニメーションの種類はLoopを選択します。

ダウンロードすると.rivの拡張子のファイルが入手できます。たったの6KB。軽いですね。

Flutterに追加

ライブラリを追加して、

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  rive: ^0.6.4 # 追加する

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

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を使った再生・停止の処理は割愛します。

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

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なので無限ループしていますが、二回ジャンプしてダイアログが閉じます。

...なんか喜んでジャンプしているというより、準備体操みたいになってしまいましたね笑

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

まとめ

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

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

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