Rive Animations
Interactive real-time animations in Flutter
🎮 What are Rive Animations?
Rive animations are interactive, real-time graphics created with the Rive editor. Unlike Lottie, Rive supports state machines, user input, and dynamic content changes, making them perfect for interactive UI elements, game characters, and responsive animations.
// Display Rive animation
RiveAnimation.asset('assets/animation.riv')
Rive Features
Interactive
Respond to user input in real-time
State Machines
Complex animation logic built-in
Real-time
Rendered at runtime, not pre-rendered
Rive Editor
Powerful design tool
🔹 Installation
Add Rive package to your project:
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
rive: ^0.12.0
# Add your Rive files
flutter:
assets:
- assets/rive/
Getting Started with Rive:
- Rive.app: Create animations in the browser
- Community: Free animations on Rive Community
- Tutorials: Learn at rive.app/learn
🔹 Basic Rive Animation
Display a simple Rive animation:
import 'package:rive/rive.dart';
class BasicRiveExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Rive Animation')),
body: Center(
child: RiveAnimation.asset(
'assets/rive/character.riv',
fit: BoxFit.contain,
),
),
);
}
}
// Load from network
class NetworkRive extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RiveAnimation.network(
'https://cdn.rive.app/animations/example.riv',
fit: BoxFit.cover,
);
}
}
🔹 Controlling Rive Animations
Control animation playback with controllers:
class ControlledRive extends StatefulWidget {
@override
_ControlledRiveState createState() => _ControlledRiveState();
}
class _ControlledRiveState extends State {
late RiveAnimationController _controller;
@override
void initState() {
super.initState();
_controller = SimpleAnimation('idle');
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 300,
height: 300,
child: RiveAnimation.asset(
'assets/rive/character.riv',
controllers: [_controller],
),
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
setState(() {
_controller = SimpleAnimation('idle');
});
},
child: Text('Idle'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
setState(() {
_controller = SimpleAnimation('walk');
});
},
child: Text('Walk'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
setState(() {
_controller = SimpleAnimation('jump');
});
},
child: Text('Jump'),
),
],
),
],
);
}
}
🔹 State Machine Example
Use Rive's powerful state machines:
class StateMachineExample extends StatefulWidget {
@override
_StateMachineExampleState createState() => _StateMachineExampleState();
}
class _StateMachineExampleState extends State {
SMITrigger? _trigger;
SMIBool? _isActive;
SMINumber? _level;
void _onRiveInit(Artboard artboard) {
final controller = StateMachineController.fromArtboard(
artboard,
'State Machine 1',
);
if (controller != null) {
artboard.addController(controller);
// Get inputs from state machine
_trigger = controller.findInput('Trigger') as SMITrigger;
_isActive = controller.findInput('isActive') as SMIBool;
_level = controller.findInput('level') as SMINumber;
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
width: 300,
height: 300,
child: RiveAnimation.asset(
'assets/rive/interactive.riv',
onInit: _onRiveInit,
),
),
ElevatedButton(
onPressed: () => _trigger?.fire(),
child: Text('Fire Trigger'),
),
SwitchListTile(
title: Text('Active'),
value: _isActive?.value ?? false,
onChanged: (value) {
_isActive?.value = value;
},
),
Slider(
value: _level?.value ?? 0,
min: 0,
max: 100,
onChanged: (value) {
_level?.value = value;
},
),
],
);
}
}
🔹 Interactive Button with Rive
Create an interactive animated button:
class RiveButton extends StatefulWidget {
@override
_RiveButtonState createState() => _RiveButtonState();
}
class _RiveButtonState extends State {
SMITrigger? _press;
SMIBool? _isHovered;
void _onRiveInit(Artboard artboard) {
final controller = StateMachineController.fromArtboard(
artboard,
'Button',
);
if (controller != null) {
artboard.addController(controller);
_press = controller.findInput('Press') as SMITrigger;
_isHovered = controller.findInput('Hover') as SMIBool;
}
}
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => _isHovered?.value = true,
onExit: (_) => _isHovered?.value = false,
child: GestureDetector(
onTap: () {
_press?.fire();
// Perform button action
print('Button pressed!');
},
child: SizedBox(
width: 200,
height: 80,
child: RiveAnimation.asset(
'assets/rive/button.riv',
onInit: _onRiveInit,
),
),
),
);
}
}
🔹 Character Animation with Input
Control character based on user input:
class CharacterController extends StatefulWidget {
@override
_CharacterControllerState createState() => _CharacterControllerState();
}
class _CharacterControllerState extends State {
SMINumber? _lookX;
SMINumber? _lookY;
void _onRiveInit(Artboard artboard) {
final controller = StateMachineController.fromArtboard(
artboard,
'State Machine',
);
if (controller != null) {
artboard.addController(controller);
_lookX = controller.findInput('lookX') as SMINumber;
_lookY = controller.findInput('lookY') as SMINumber;
}
}
void _onPointerMove(PointerEvent details) {
final box = context.findRenderObject() as RenderBox;
final localPosition = box.globalToLocal(details.position);
final size = box.size;
// Convert to -1 to 1 range
final x = (localPosition.dx / size.width) * 2 - 1;
final y = (localPosition.dy / size.height) * 2 - 1;
_lookX?.value = x * 100;
_lookY?.value = y * 100;
}
@override
Widget build(BuildContext context) {
return Listener(
onPointerMove: _onPointerMove,
child: Container(
width: 400,
height: 400,
color: Colors.grey[200],
child: RiveAnimation.asset(
'assets/rive/character_eyes.riv',
onInit: _onRiveInit,
),
),
);
}
}
🔹 Loading Animation with Rive
Create a custom loading indicator:
class RiveLoadingIndicator extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 150,
height: 150,
child: RiveAnimation.asset(
'assets/rive/loading_spinner.riv',
fit: BoxFit.contain,
),
),
SizedBox(height: 20),
Text(
'Loading your content...',
style: TextStyle(
fontSize: 16,
color: Colors.grey[700],
),
),
],
),
),
);
}
}
// Progress bar with Rive
class RiveProgressBar extends StatefulWidget {
final double progress;
RiveProgressBar({required this.progress});
@override
_RiveProgressBarState createState() => _RiveProgressBarState();
}
class _RiveProgressBarState extends State {
SMINumber? _progressInput;
void _onRiveInit(Artboard artboard) {
final controller = StateMachineController.fromArtboard(
artboard,
'ProgressBar',
);
if (controller != null) {
artboard.addController(controller);
_progressInput = controller.findInput('progress') as SMINumber;
}
}
@override
void didUpdateWidget(RiveProgressBar oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.progress != oldWidget.progress) {
_progressInput?.value = widget.progress;
}
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: 300,
height: 50,
child: RiveAnimation.asset(
'assets/rive/progress_bar.riv',
onInit: _onRiveInit,
),
);
}
}
🔹 Rive vs Lottie
When to use each animation format:
Use Rive When:
- Interactivity: Need user input and state machines
- Dynamic Content: Animation changes based on data
- Game-like: Character animations with multiple states
- Real-time: Need runtime control and modifications
Use Lottie When:
- Simple Playback: Just play/pause animations
- After Effects: Already have AE animations
- Large Library: Need access to LottieFiles
- Simpler: Don't need complex interactions
💡 Rive Best Practices:
- State Machines: Use for complex animation logic
- Inputs: Expose inputs for runtime control
- Optimization: Keep artboards simple for performance
- Testing: Test on target devices early
- Community: Explore Rive Community for examples
- Editor: Learn the Rive editor at rive.app