The Rive engineering team’s a fan of Flutter; we use it throughout Rive's editor. While most of our runtimes use a shared C++ core, the Flutter runtime does not; it uses the editor’s Dart rendering engine. It also has a different API style, more in keeping Flutter’s reactive pattern. The runtime’s available in pub and Github.

Let’s dive into the runtime, explore how to play animations, and how to control them in code.

Playing Rive animations

In this first example we’ll take a spin in our truck. The vehicles.riv file has two artboards: Truck and Jeep, each with their own animations and state machines. Truck has 3 animations: idle, curves, and bounce, which are designed to be mixed together to create excellent trucking action, but let’s start simple and animate our truck in its idle state.

We’ll add the rive package to pubspec.yaml:

dependencies:
  rive: ^0.7.18

flutter:
  assets:
    - assets/vehicles.riv
pubspec.yaml

We can add vehicles.riv as an asset if we want to ship it with our app. Rive also makes it easy to fetch it over http.

Let's use the RiveAnimation widget to play our file. This widget has two contructors to either load the file from the asset bundle, or over http:

  • RiveAnimation.asset('assets/vehicles.riv')
  • RiveAnimation.network('https://cdn.rive.app/animations/vehicles.riv')
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';

void main() => runApp(MaterialApp(
      home: MyRiveAnimation(),
    ));

class MyRiveAnimation extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: RiveAnimation.network(
          'https://cdn.rive.app/animations/vehicles.riv',
          fit: BoxFit.cover,
        ),
      ),
    );
  }
}
main.dart
This is supposed to show an animation! If you're viewing this in your email, please view this post in a browser.

That's all you need to get an animation playing. If you don't specify an artboard or an animation, it'll automatically select the default artboard and the first animation in the list, in this case Truck and idle. If you'd like to specify which ones to use, you can do that in the widget:

RiveAnimation.network(
  'https://cdn.rive.app/animations/vehicles.riv',
  artboard: 'Jeep',
  animations: ['idle'],
  fit: BoxFit.cover,
);

What about the fit parameter? This lets you specify how you'd like Rive to fit the animation into the available space: cover ensures that Rive fills all available space, whilst maintaining aspect ratio; fill does the same but doesn't respect aspect ratio; and contain shows the entire animation without cropping. If not provided, it defaults to contain.

You can provide a list of animations for playback and Rive will mix them together. If we want our truck to make some smooth curves, we can give it:

RiveAnimation.network(
  'https://cdn.rive.app/animations/vehicles.riv',
  artboard: 'Truck',
  animations: ['idle', 'curves'],
  fit: BoxFit.cover,
);

Both the idle and curves animations will be mixed together.

This is supposed to show an animation! If you're viewing this in your email, please view this post in a browser.

Controlling animations

What if you want to trigger an animation to play based on some event? Rive uses the concept of controllers to provide programmatic access to individual animations and state machines. Controllers can be created for each animation you want to control, passing them to the widget. SimpleController can be used to control looping animations, and OneShotController to make firing a one-shot animation a breeze.

Let's say we want our truck to hit a bump the road when we tap on it. We create a OneShotController for the animation we want to trigger, optionally setting it not to play when it's first shown. We then pass the controller to the widget, along with any other animations we want to play but don't care about controlling.

class BouncyTruckAnimation extends StatefulWidget {
  const BouncyTruckAnimation({Key? key}) : super(key: key);

  @override
  _BouncyTruckAnimationState createState() => _BouncyTruckAnimationState();
}

class _BouncyTruckAnimationState extends State<BouncyTruckAnimation> {
  /// Controller for playback
  late RiveAnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = OneShotAnimation('bounce', autoplay: false);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GestureDetector(
          onTap: () => _controller.isActive = true,
          child: RiveAnimation.network(
            'https://cdn.rive.app/animations/vehicles.riv',
            animations: const ['idle', 'curves'],
            controllers: [_controller],
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }
}

Our truck now happily curves along the road until we tap on it. The bounce animation is triggered and mixed in with the other two animations, and then it completes, ready to be fired again.

You can have similar control over looping animations using SimpleController. Let's take our jeep out for a spin, though the weather's not looking good. Jeep has a looping windshield_wipers animation, which animates the wipers. We create a SimpleAnimation control its playback with the isActive property.

class RainyJeepAnimation extends StatefulWidget {
  const RainyJeepAnimation({Key? key}) : super(key: key);

  @override
  _RainyJeepAnimationState createState() => _RainyJeepAnimationState();
}

class _RainyJeepAnimationState extends State<RainyJeepAnimation> {
  /// Controller for playback
  late RiveAnimationController _controller;

  /// Toggles between play and pause animation states
  void _togglePlay() => _controller.isActive = !_controller.isActive;

  @override
  void initState() {
    super.initState();
    _controller = SimpleAnimation('windshield_wipers');
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GestureDetector(
          onTap: _togglePlay,
          child: RiveAnimation.network(
            'https://cdn.rive.app/animations/vehicles.riv',
            artboard: 'Jeep',
            animations: ['idle'],
            controllers: [_controller],
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }
}

Tap on the jeep animation to start and stop the wipers.

This is supposed to show an animation! If you're viewing this in your email, please view this post in a browser.