Skip to main content
Flutter Custom Rive RenderObject

Custom Rive RenderObject

Extend RiveRenderObject to perform more advanced operations.

It is possible to have finer control over your Rive animation at runtime by extending RiveRenderObject. This allows you to override low-level methods such as advance, beforeDraw, and draw to have more control and optionally perform additional operations. See below for example usage.

Note that this is a low-level API, and under most circumstances, it is preferable to make use of RiveAnimation or Rive widgets instead.

Example Code

The following is a basic example that demonstrates how to paint a Rive animation and advance a state machine using a custom RenderObject.

import 'package:flutter/material.dart';
import 'package:rive/math.dart';
import 'package:rive/rive.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyRiveWidget(),
    );
  }
}

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

  @override
  State<MyRiveWidget> createState() => _MyRiveWidgetState();
}

class _MyRiveWidgetState extends State<MyRiveWidget> {
  Artboard? _riveArtboard;

  Future<void> _load() async {
    // You need to manage adding the controller to the artboard yourself,
    // unlike with the RiveAnimation widget that can handle a lot of this logic
    // for you by simply providing the state machine (or animation) name.
    final file = await RiveFile.asset('assets/little_machine.riv');
    final artboard = file.mainArtboard;
    final controller = StateMachineController.fromArtboard(
      artboard,
      'State Machine 1',
    );
    artboard.addController(controller!);
    setState(() => _riveArtboard = artboard);
  }

  @override
  void initState() {
    super.initState();
    _load();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: _riveArtboard == null
            ? const SizedBox()
            : CustomRiveRenderObjectWidget(
                artboard: _riveArtboard!,
                fit: BoxFit.contain,
              ),
      ),
    );
  }
}

class CustomRiveRenderObjectWidget extends LeafRenderObjectWidget {
  final Artboard artboard;
  final BoxFit fit;
  final Alignment alignment;

  const CustomRiveRenderObjectWidget({
    super.key,
    required this.artboard,
    this.fit = BoxFit.contain,
    this.alignment = Alignment.center,
  });

  @override
  RenderObject createRenderObject(BuildContext context) {
    return CustomRiveRenderObject(artboard as RuntimeArtboard)
      ..artboard = artboard
      ..fit = fit
      ..alignment = alignment;
  }

  @override
  void updateRenderObject(
      BuildContext context, covariant CustomRiveRenderObject renderObject) {
    renderObject
      ..artboard = artboard
      ..fit = fit
      ..alignment = alignment;
  }

  @override
  void didUnmountRenderObject(covariant CustomRiveRenderObject renderObject) {
    renderObject.dispose();
  }
}

class CustomRiveRenderObject extends RiveRenderObject {
  CustomRiveRenderObject(super.artboard) {
    _artboardReference = artboard;
  }

  late final RuntimeArtboard _artboardReference;

  @override
  bool advance(double elapsedSeconds) {
    // The super method will update the animation and advance the artboard.
    // You can either advance the artboard yourself, or call the super method.
    return _artboardReference.advance(elapsedSeconds, nested: true);
		// Example showing how to advance the artboard at twice the speed.
    return super.advance(elapsedSeconds * 2);
  }

  @override
  void beforeDraw(Canvas canvas, Offset offset) {
    // Called before `draw`. Can be used to perform clipping, for example.
    super.beforeDraw(canvas, offset);
  }

  @override
  void draw(Canvas canvas, Mat2D viewTransform) {
    // Here you can tap into the draw method and perform custom operations.
    super.draw(canvas, viewTransform);
  }
}

The artboard can be controlled and configured as it normally would, through a StateMachineController (or any other animation controller).

Example Usage

  • Dynamically update component colors at runtime - Make use of a custom Rive render object to change a shape's fill color, accessing it by name. This is helpful for when a color's opacity is animating, but the color needs to be changed at runtime.

  • Track a Rive component in Flutter - Track a Rive component’s position at runtime and overlay a Flutter widget or perform additional painting operations.

Additional Documenation

For additional information on RenderObjects, see the official Flutter examples:

  • RenderObject documentation

  • How to build a RenderObject