🫨

Riveとflutterでインタラクティブアニメーションをやってみた

に公開

Design Trends 2025系の動画を見ていたら「インタラクティブアニメーション」というワードがよく目に入リました。
なので、「インタラクティブアニメーションとはなんぞや」というところから、実際にアニメーションを作成してflutterアプリで表示するまでを実装してみました。

対象の読者

インタラクティブアニメーションに興味がある人
Riveを触ってみたい人/興味がある人
Riveで作ったアニメーションをflutterに取り込みたい人

デモ

今回のゴールです。(gifになっている+ファイルサイズ制限あるのでイメージ程度に、、、)

そもそもインタラクティブアニメーションとは?

「ユーザーの操作に反応して動くアニメーション」のことです!

操作 アニメーションの動き
ボタンをクリック ボタンがグッとへこんで跳ね返る
マウスをアイコンに乗せる アイコンがふわっと拡大する
スワイプする キャラクターが走り出す

海外のデザインとか見ていると結構使われているイメージが強いですね。

Rive編

ここからRiveについての解説です。Riveはもう知っててflutterのところが見たいよーって言う人は飛ばしてOKです。

Riveとは?

リアルタイムでアニメーションをデザイン・作成・実装できるツールおよびそのランタイムライブラリです。(生成AIより)

いまいちピンとこないので個人的な解釈も含めて言うと、「状態管理ができるアニメーション作成ツール」みたいな感じです。

AdobeのAffterEffectsが一番近いかなーと思いますが、加えて「状態管理」の概念があります。これは後ほど解説します。

公式:Rive

見てもらうのが一番早いと思うので、公式HPでどんなのが作れるのかチラ見すると良いと思います!

Riveのエディター

左からHierarchyエリア、ArtBoradエリア、Designタブエリアとなっています。これはFigma等のデザインツールとほぼ同様なので親しみやすいですね。

Hierarchyはレイヤー構造になっており、ここにオブジェクトを配置していってイラストを完成させます。figmaでいうフレーム=artBoardとなります。ただ、figmaで多用するフレームはRiveでは多用せず、オブジェクトをグループ化していくことを公式としても推奨しているようです。

ArtBoardエリアは、実際にオブジェクトの位置や色を変更したりしてイラストを作成していくエリアです。基本的なプロパティは他のデザインツールと同様で、デザインツールを触ったことのある人ならある程度直感で操作できると思います。

Designタブエリアは、プロパティの表示エリアです。RiveではAnimateモードがあります。キャプチャではDesignというタブが選択されていますが、Animateを選択するとアニメーションを作成する設定画面になります。figmaのプロトタイプモードみたいな感じです。

Animateモードになると、画面下部に新しいエリアが追加されます。TimelineとStateMachineというアニメーションがあり、これは後述しますが、Timelineが1つのアニメーションでStateMachineがアニメーションを組み合わせる場所、みたいに私はイメージしています。

Timelineの編集エリアは下記のような画面です。キーフレームアニメーションを行ったことがある人は馴染みがあると思います。

一方、StateMachineエリアは下記のような画面です。私はあまり馴染みがなかったのですが、他のツールでこういうUIはあるかもしれません。この作業エリア上に作成したアニメーション(Timeline)を配置し、特定のアクション等によってアニメーションを発火させることができます。

オブジェクトを配置する

今回はデモで表示しているモンスターで解説していきます。

ざっくりいうと

  • カーソル部分(マウスを目で追うためのもの)
  • 両目部分
  • 本体部分
  • 影の部分
  • 骨の部分(後述)

みたいな構造になっています。(構造はもう少し綺麗にしたほうがいいと思います。。。)

両目の部分、本体の部分、影の部分は比較的単純なので、いったん割愛します。個人的に詰まったカーソル部分と骨の部分をメインで解説します。

まず、カーソル部分ですが、これまでのキャプチャでは非表示にしていましたが、わかりやすいように赤色で表示してみました。

赤色のサークルが表示されています。アニメーション時には、このサークルをマウス追従させて、このサークルの位置に応じて両目の位置を制御することで、目がマウスを追ってくるようなアニメーションを再現しています。

設定は下記のような感じで、黒目部分のデザインのConstraintsにTransformを設定しています。このTransformのターゲットをカーソル部分に設定します。加えて、Strengthを10%程度で設定します。

この設定によって、赤いサークルの位置によって黒目部分が少し追従して動くようになります。Strengthはどれくらい赤いサークルに引っ張られるかの強度を示しています。100%だと赤いサークルに追従します。この設定を白目と黒目全てに設定しています。(Strengthの値は目の外側にいくほど弱くします)

これで目の追従アニメーションの準備は完了です。Animateモードに変更し、StateMachineを編集します。

必要なのがListenersのプロパティです。

Listenersの横のプラスボタンからListenerを追加します。一番右側(緑部分)は目のオブジェクトを選択し、赤色の部分はカーソルのオブジェクトを選択します。黄色の部分はPointerMoveを選択しましょう。

ここでポイントとなるのが、目のオブジェクトはartBorad全体を覆うような透明な四角いオブジェクトを配置しおいてください。一番右側(緑部分)で選択したオブジェクトの範囲をマウス追従するからです。

これで、Layerの横にある再生ボタンを押してアニメーションを確認すると目がマウスに追従するようになると思います。

次に骨の部分について解説します。これはYoutubeで公式が解説しているものがわかりやすいのでそれを見るのをおすすめします。ここでは簡単に説明をさせていただきます。

骨の部分はモンスターの可動範囲に制約を設けています。

体部分のPathの右側にBindBonesというプロパティがあると思いますが、ここと骨のオブジェクトを紐づけています。体部分の1つ1つのPathに骨のオブジェクトをどれくらい関連づけるかを設定することができ、例えば右上のPathには青色の骨を100%関連づけています。

このようにして、左上と右上は青色の骨、左下と右下は黄色の骨が関連づいています。

こうすることによって、青色の骨の角度が変わったときに体のオブジェクトが自然な形で変形します。それを利用してゆらゆら揺れるアニメーションを作成します。

再度Animateモードにして、次はAnimationsの中のTimelineを作成します。

Timelineで骨の角度にキーフレームを打って、それをゆらゆらさせてループしています。これだけで体がゆらゆらするアニメーションが完成します。必要に応じて目などのオブジェクトもアニメーションするようにキーフレームを打ちます。

作ったアニメーションをStateMachineで管理する

このモンスターは

  • ゆらゆらする
  • 飛び跳ねる
  • クリック時に目が怪訝になる/解除する

の3つのアニメーションがあります。

StateMachineでは、作ったTimelineを配置して、発火のトリガーを設定することができます。

Layer1ではゆらゆら→飛び跳ねるを定義しています。

yurayura Timelineをドラッグ&ドロップで配置して、idle(飛び跳ねるアニメーション)も同様に配置します。ゆらゆらは4秒で終わるアニメーションなので、これが完了したら1回飛び跳ねます。その設定方法は下記になります。

ポイントは「Exit Time」を100%とすることです。こうすることによって、yurayuraが完了後にidleのアニメーションが発火します。飛び跳ねるアニメーションからyurayuraに戻るときも同様の設定とします。

Layer2はクリックしたときに怪訝な目になるようなアニメーションです。全体像は下記の通りです。

クリック時のイベント制御のために、InputsからTriggerを作成します(キャプチャで言うとtapとなっているやつ)。次に、Listenersを追加して、Pathにtapを設定します。一番右側のターゲットは画面全体を覆っていれば何のオブジェクトでもいいはずです。私は目のオブジェクトを選択しました。

上記の設定をすると、目のオブジェクトをクリックしたときにtapが発火します。

次にデフォルト時と、タップ時のアニメーションをそれぞれ配置して繋げます。デフォルト時とタップ時のアニメーションは下記のような形で、キーフレームを1箇所だけ打ってある目の形が違うアニメーションです。

上記の状態をtapをトリガーにアニメーションする設定は下記のような形で設定をします。Durationも適当に設定したほうが滑らかに動きます。

exportしてrivファイルを生成する

ExportボタンからExport for Runtimeを押すだけでrivファイルがダウンロードされます。

以上でRive側でやることは完了です。(長かった、、、)

flutter編

ここからRiveで作ったファイルをflutterに読み込んでいい感じに動かしていきます。

環境

  • Flutter 3.24.3
  • rive: ^0.13.20

riveパッケージのインストール

flutter pub add rive

全ソース

import 'package:flutter/material.dart';
import 'package:flutter_multi_app/custom_app_bar.dart';
import 'package:flutter_multi_app/utility/app_logger.dart';
import 'package:rive/rive.dart';

class FlutterRiveView extends StatefulWidget {
  const FlutterRiveView({super.key, required this.title});
  final String title;

  
  State<FlutterRiveView> createState() => _FlutterRiveViewState();
}

class _FlutterRiveViewState extends State<FlutterRiveView> {
  SMITrigger? _bump;

  void _onRiveInit(Artboard artboard) {
    final controller = StateMachineController.fromArtboard(artboard, 'StateMachine1');
    if (controller == null) {
      logger.w('State Machine Controller is null');
      return;
    }
    artboard.addController(controller);
    _bump = controller.getTriggerInput('tap');
  }

  void _hitBump() => _bump?.fire();

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: CustomAppBar(title: widget.title),
      body: SafeArea(
        child: Column(
          children: [
            ElevatedButton(onPressed: _hitBump, child: const Text('Trigger Bump')),
            Expanded(
              child: SizedBox(
                height: double.infinity,
                width: double.infinity,
                child: RiveAnimation.asset(
                  'assets/myfirstrive.riv',
                  fit: BoxFit.cover,
                  onInit: _onRiveInit,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

解説

まず、rivファイルをassets/配下に入れておきます。忘れずにyamlも編集しましょう。assets/にさえ入れてしまえば、rivファイルを読み込んで画面の描画するのは比較的簡単です。

                child: RiveAnimation.asset(
                  'assets/myfirstrive.riv',
                  fit: BoxFit.cover,
                  onInit: _onRiveInit,
                ),

ウィジェット部分はここだけです。気になるプロパティとしてonInitがあります。これはアニメーションをさせる際に使用する部分です。試しにコメントアウトしてみると、ファイル自体は正常に読み込まれ、アニメーションがしていない、と言う状態になります。

onInitではrivファイルを読み込んだ際に実行されるコールバック関数を用意します。実際に使用されているのが下記です。

  void _onRiveInit(Artboard artboard) {
    final controller = StateMachineController.fromArtboard(artboard, 'StateMachine1');
    if (controller == null) {
      logger.w('State Machine Controller is null');
      return;
    }
    artboard.addController(controller);
    _bump = controller.getTriggerInput('tap');
  }

ここではartboardから'StateMachine1'というStateMachineを探して、コントローラーを作成します。その後、artboard.addController(controller);で、artboardにコントローラーを追加し、アニメーションを制御できるように設定をしています。

_bump = controller.getTriggerInput('tap');はRiveでInputsに追加した'tap'というトリガーを_bumpに格納しており、`_bump?.fire()'とすることでトリガーを発火させることが可能になります。

Riveをflutterで扱ってみての感想

いい感じのアニメーションを比較的簡単にflutterに取り込めるので便利かなーと思いました。アニメーションを再生するだけならLottieとかGifとかでいいじゃんっていうのもあると思うんですけど、Riveのいいところって、設定したInputsをflutter側のウィジェットやイベントをトリガーに発火させることができるのがかなり魅力的だと思いました。

今回のデモではボタンにRive側のトリガーを発火できるような実装をしていますが、これは色々やれることがあると思います。

あとはシンプルにヌルヌル動くアニメーションっていいね!!

今回はRiveを使ってflutterに取り込むところまで一通りやってみましたが、オブジェクトの名称等適当な部分が多くて申し訳ないです。また、誤り等ありましたらコメントいただけると幸いです。

Discussion