【Flutter/Rive】インタラクティブなアニメーションを作ってアプリで動かす
はじめに
まだまだ勉強中なので、間違っている箇所があればコメントで教えてください🙏🏻
今回はDashをインタラクティブなアニメーションにしてアプリで動かしてみました🐥
Riveでインタラクティブなアニメーションをつくる
動かしたいオブジェクトをつくる
SVG, JSON, PNG, PSD, JPGがインポート可能なので、慣れているツールがあればそちらで作成しても良さそうです。
Figmaから直接SVGをコピーして貼り付けたり、Lottieアニメーションのインポートもできるみたいです。
今回はRiveだけでオブジェクトを作ります。楕円・長方形を組み合わせたり変形させたりしてこんな感じにしてみました。
(今回作ったアニメーションでは足の動きをつけなかったので足部分のボーンは不要でした)
アニメーションをつける
アイドル状態・手を振る・笑顔になる の3パターンのアニメーションを作ります。
アイドル状態 | 手を振る | 笑顔になる |
---|---|---|
各パターン用のTimelineを作成し、Timeline上で動きをつけていきます。
idle, wavingHand, smilingという名前のTimelineを作成
動かし方の詳細は公式ガイドやYoutubeに詳しく書かれているので省きますが、今回取り入れたアニメーションの一例としては以下のようなものがあります。
- 羽やトサカのボーンを左右に回転させることでふわふわした動きを作る
- 目の光を拡大縮小することでキラキラしているようにみせる
State Machineでインタラクティブなアニメーションにする
今回のテーマであるインタラクティブなアニメーションを作るためには、Timeline間の遷移を管理することができるState Machineが必要です。
State Machineで idle, wavingHand, smiling間の遷移を管理
今回は3つのTimelineをIDで管理し、ユーザーがアクションを起こした時にIDを渡して再生するアニメーションを変えてみようと思うので、どのIDが入力されたかによって遷移先を変えるようにState Machineを作りました。
※ IDは入力タイプ:Numberです。今回は使用しませんが、入力タイプをBoolen, Triggerとすることも可能です。
Any StateとそれぞれのTimelineを繋ぎ、間の矢印を選択すると各Timelineに遷移するための条件を指定できます。
入力されたIDによって遷移先を変えたいので id==2 の時に smiling(笑顔)に遷移するように指定
それぞれのTimelineに遷移する条件を指定できたらOKです!
rivファイルをエクスポートします。
Flutterで動かす
パッケージの導入
パッケージの追加とインポートを行います。
flutter pub add rive
dependencies:
rive: ^0.13.2
import 'package:rive/rive.dart';
rivファイルをアプリ上に表示する
rivファイルをFlutterアプリ上に表示するためには、RiveAnimation.network()
を使用してURLを指定して表示する方法と、RiveAnimation.asset()
を使用してAssetを指定する方法があります。
インタラクティブではないアニメーションを表示したいだけであればどちらかのコンストラクタにパスを渡してインスタンス生成すればOKです🙆🏻♀️
入力によってアニメーションが変わるようにする
Timelineのidをどこかで管理しておきます。
今回はEnumにしましたが何でもいいと思います。
TextButtonに表示させるためのlabelも作成しておきました。
enum Timeline {
idle(id: 0, label: '🐥'),
wavingHand(id: 1, label: '👋'),
smiling(id: 2, label: '😊'),
;
const Timeline({required this.id, required this.label});
final int id;
final String label;
}
絵文字をラベルにしたTextButtonを押下した時に、idが一致したTimelineに遷移するコードは以下のようになります。
class _MyHomePage extends StatefulWidget {
const _MyHomePage();
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<_MyHomePage> {
SMIInput<double>? _inputId;
void _onInit(Artboard artboard) {
final controller =
StateMachineController.fromArtboard(artboard, 'StateMachine');
if (controller == null) {
return;
}
artboard.addController(controller);
_inputId = controller.getNumberInput('id');
}
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
height: 300,
child: RiveAnimation.asset(
Assets.dash,
onInit: _onInit,
),
),
const Gap(40),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: Timeline.values
.map(
(timeline) => TextButton(
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 30),
),
onPressed: () {
_inputId?.value = timeline.id.toDouble();
},
child: Text(
timeline.label,
style: const TextStyle(fontSize: 30),
),
),
)
.toList(),
),
],
),
),
);
}
}
先ほどと違うのは、onInit()でStateMachineControllerインスタンスを生成している点ですね。
getNumberInput()で入力値を取得したり、TextButtonを押下した時に入力値をセットすることで遷移するTimelineを決めることができます。
さいごに
今回は各アニメーションを再生するためのボタンを用意する簡単な実装ですが、もっと複雑な遷移を考えてみたり可能性が無限大なのでワクワクしますね〜!
まだRiveの操作も分かってない部分が多いので楽しみながら勉強していきたいと思います🙏🏻
Discussion