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

Touch Hover Drag
🤖

State Machines

Complex animation logic built-in

Transitions Conditions Events
âš¡

Real-time

Rendered at runtime, not pre-rendered

Dynamic Responsive Adaptive
🎨

Rive Editor

Powerful design tool

Visual Timeline Bones

🔹 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

🧠 Test Your Knowledge

What makes Rive animations different from Lottie?