【Flutter】Rive→Flutter StateMachineを使ったアニメーション
今回やること
『Rive』アニメーションツールを使って状態を管理し、アニメーションさせる実装を Flutter にて行っていきます。
ボックスの選択、バウンドの回数は Rive 内で state を更新し、DarkMode と Move(始動)は Flutter から入力します。
Rive でアニメーションを作成する
数字の書かれたボックスを選択(タップ)し、その数字の数だけバウンドするアニメーションを作成します。
バウンドアニメーションは Constraints でボールを Path に追従するようにし、Distance を増加させ動かします。
スケールなども調整すると良いでしょう。
State Machine の設定
Rive では state を設定、保持してくれるので、その state を使いインタラクティブなアニメーションを作成することができます。
今回設定した state パラメータを紹介します。
Inputs
- isMove (bool) → true になると動き出す
- isDark (bool) → true でダークモードになる
- count (Number) → バウンドする(動く)回数
Listeners
- select0 → target(0 のボックス)をタップすると count の値を 0 へ変更
- select1 → target(1 のボックス)をタップすると count の値を 1 へ変更
- select2 , select3 同様
Animations
-
dark , light → ボックスの色のみを定義 動かなくても特定のパラメータを定義できる
-
move1~3 → move◯ の ◯ 回バウンドするアニメーション
-
select0~3 → ボールの初期状態と選択されたボックスに枠線を付ける
Rive 側での動き
下記リンクより実際の動きを確認できます。
全体の解説
Flutter 側で実装する
まずはパッケージをインストール
flutter pub add rive
まずは、作成した.riv ファイルを/asset などに入れます。画像などと同様です。
コードは下記のようになります。State Machine を使用する場合は onInit を定義する必要が
あります。今回は Inputs も使用するのでそれぞれ定義します。
import 'package:rive/rive.dart';
SMIInput<bool>? _isDarkModeInput;
SMIInput<bool>? _isMoveInput;
SMIInput<double>? _count; // InputsでNumberを選択した場合の型はdouble
/// RiveAnimationの初期化 Widget build時に呼ばれる
void _onRiveInit(Artboard artboard) {
final controller = StateMachineController.fromArtboard(
artboard,
'State Machine 1', // Animations欄の StateMachine名
onStateChange: _onStateChange, // callback関数
);
artboard.addController(controller!);
// それぞれInputを定義 初期値はnullなので注意が必要
// 'isDark'などのテキストはInputsの表示名と対応します
_isDarkModeInput = controller.findInput<bool>('isDark') as SMIBool;
_isMoveInput = controller.findInput<bool>('isMove') as SMIBool;
_count = controller.findInput<double>('count') as SMINumber;
}
/// Widget
RiveAnimation.asset(
'assets/rive/sample.riv',
onInit: _onRiveInit,
),
後は Inputs のそれぞれの値を入れてあげれば動作します。
isMove に true を入れると count 回バウンドします。
Inputs の現在の値を取得することもできます。init 時は SMIInput<bool>? isMoveInput は null で入ってくるので処理には注意が必要です。
Inputs の count は Rive 内でボックスをタップした際に数値が変更されます。
Move の切り替え
ElevatedButton(
onPressed: () {
_isMoveInput!.value = !_isMoveInput!.value;
},
child: Text(_isMoveInput != null
? _isMoveInput!.value
? "Reset"
: "Move"
: "Move"),
),
Dark Mode の切り替え
Switch(
onChanged: (value) => setState(() {
_isDarkModeInput!.value = !_isDarkModeInput!.value;
}),
value: _isDarkModeInput != null ? _isDarkModeInput!.value : false,
)
callback について
_onRiveInit()の onStateChange で設定した callback で現在実行している Animations の
Animation 名をテキストで取得できます。
String _currentAnimationState = '';
/// アニメーションの状態が変わった時の処理
void _onStateChange(
String stateMachineName,
String stateName,
) =>
setState(
() => _currentAnimationState = stateName,
);
下 の "animation state" が現在実行している Animation 名です。
その他の実装例
終わりに
Rive ではここで紹介した機能の他に、たくさんの機能を備えています。
公式の YouTube も頻繁に更新されていてチュートリアル動画もとても分かりやすので、
是非チェックして見てください。
コード全文
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class RiveSample extends StatefulWidget {
const RiveSample({super.key});
State<RiveSample> createState() => _RiveSampleState();
}
class _RiveSampleState extends State<RiveSample> {
SMIInput<bool>? _isDarkModeInput;
SMIInput<bool>? _isMoveInput;
SMIInput<double>? _count;
String _currentAnimationState = '';
/// アニメーションの状態が変わった時の処理
void _onStateChange(
String stateMachineName,
String stateName,
) =>
setState(
() => _currentAnimationState = stateName,
);
void _onRiveInit(Artboard artboard) {
final controller = StateMachineController.fromArtboard(
artboard,
'State Machine 1',
onStateChange: _onStateChange,
);
artboard.addController(controller!);
_isDarkModeInput = controller.findInput<bool>('isDark') as SMIBool;
_isMoveInput = controller.findInput<bool>('isMove') as SMIBool;
_count = controller.findInput<double>('count') as SMINumber;
/// 初期値の入力
_count?.value = 0;
_isDarkModeInput?.value = false;
_isMoveInput?.value = false;
}
Widget build(BuildContext context) {
return Scaffold(
/// RiveAnimationのisDarkを監視して、BGを変更
backgroundColor: _isDarkModeInput != null
? _isDarkModeInput!.value
? Colors.grey
: Colors.white
: Colors.white,
body: Center(
child: Column(
children: [
SizedBox(
width: double.infinity,
height: 300,
child: RiveAnimation.asset(
'assets/rive/sample.riv',
onInit: _onRiveInit,
fit: BoxFit.contain,
),
),
Text(
"count : ${_count != null ? _count!.value : ""}",
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
Text(
"animation state : $_currentAnimationState",
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("Dark Mode"),
Switch(
onChanged: (value) => setState(() {
_isDarkModeInput!.value = !_isDarkModeInput!.value;
}),
value: _isDarkModeInput != null ? _isDarkModeInput!.value : false,
),
],
),
ElevatedButton(
onPressed: () {
_isMoveInput!.value = !_isMoveInput!.value;
},
child: Text(_isMoveInput != null
? _isMoveInput!.value
? "Reset"
: "Move"
: "Move"),
),
],
),
),
);
}
}
Discussion