import 'dart:math';
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(
debugShowCheckedModeBanner: false,
home: MyRiveWidget(),
);
}
}
class MyRiveWidget extends StatefulWidget {
const MyRiveWidget({Key? key}) : super(key: key);
@override
State<MyRiveWidget> createState() => _MyRiveWidgetState();
}
class _MyRiveWidgetState extends State<MyRiveWidget>
with SingleTickerProviderStateMixin {
late final AnimationController _animationController =
AnimationController(vsync: this, duration: const Duration(seconds: 10));
RiveArtboardRenderer? _artboardRenderer;
Future<void> _load() async {
final file = await RiveFile.asset('assets/little_machine.riv');
final artboard = file.mainArtboard.instance();
final controller = StateMachineController.fromArtboard(
artboard,
'State Machine 1',
);
artboard.addController(controller!);
setState(
() => _artboardRenderer = RiveArtboardRenderer(
antialiasing: true,
fit: BoxFit.cover,
alignment: Alignment.center,
artboard: artboard,
),
);
}
@override
void initState() {
super.initState();
_animationController.repeat();
_load();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: _artboardRenderer == null
? const SizedBox()
: CustomPaint(
painter: RiveCustomPainter(
_artboardRenderer!,
repaint: _animationController,
),
child: const SizedBox.expand(),
),
),
);
}
}
class RiveCustomPainter extends CustomPainter {
final RiveArtboardRenderer artboardRenderer;
RiveCustomPainter(this.artboardRenderer, {super.repaint}) {
_lastTickTime = DateTime.now();
_elapsedTime = Duration.zero;
}
late DateTime _lastTickTime;
late Duration _elapsedTime;
void _calculateElapsedTime() {
final currentTime = DateTime.now();
_elapsedTime = currentTime.difference(_lastTickTime);
_lastTickTime = currentTime;
}
@override
void paint(Canvas canvas, Size size) {
_calculateElapsedTime();
artboardRenderer.advance(_elapsedTime.inMicroseconds / 1000000);
final width = size.width / 3;
final height = size.height / 2;
final artboardSize = Size(width, height);
canvas.save();
artboardRenderer.render(canvas, artboardSize);
canvas.restore();
canvas.save();
canvas.translate(width, 0);
artboardRenderer.render(canvas, artboardSize);
canvas.restore();
canvas.save();
canvas.translate(width * 2, 0);
artboardRenderer.render(canvas, artboardSize);
canvas.restore();
canvas.save();
canvas.translate(0, height);
artboardRenderer.render(canvas, artboardSize);
canvas.restore();
canvas.save();
canvas.translate(width, height);
artboardRenderer.render(canvas, artboardSize);
canvas.restore();
canvas.save();
canvas.translate(width * 2, height);
artboardRenderer.render(canvas, artboardSize);
canvas.restore();
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
class RiveArtboardRenderer {
final Artboard artboard;
final bool antialiasing;
final BoxFit fit;
final Alignment alignment;
RiveArtboardRenderer({
required this.antialiasing,
required this.fit,
required this.alignment,
required this.artboard,
}) {
artboard.antialiasing = antialiasing;
}
void advance(double dt) {
artboard.advance(dt, nested: true);
}
late final aabb = AABB.fromValues(0, 0, artboard.width, artboard.height);
void render(Canvas canvas, Size size) {
_paint(canvas, aabb, size);
}
final _transform = Mat2D();
final _center = Mat2D();
void _paint(Canvas canvas, AABB bounds, Size size) {
const position = Offset.zero;
final contentWidth = bounds[2] - bounds[0];
final contentHeight = bounds[3] - bounds[1];
if (contentWidth == 0 || contentHeight == 0) {
return;
}
final x = -1 * bounds[0] -
contentWidth / 2.0 -
(alignment.x * contentWidth / 2.0);
final y = -1 * bounds[1] -
contentHeight / 2.0 -
(alignment.y * contentHeight / 2.0);
var scaleX = 1.0;
var scaleY = 1.0;
canvas.save();
canvas.clipRect(position & size);
switch (fit) {
case BoxFit.fill:
scaleX = size.width / contentWidth;
scaleY = size.height / contentHeight;
break;
case BoxFit.contain:
final minScale =
min(size.width / contentWidth, size.height / contentHeight);
scaleX = scaleY = minScale;
break;
case BoxFit.cover:
final maxScale =
max(size.width / contentWidth, size.height / contentHeight);
scaleX = scaleY = maxScale;
break;
case BoxFit.fitHeight:
final minScale = size.height / contentHeight;
scaleX = scaleY = minScale;
break;
case BoxFit.fitWidth:
final minScale = size.width / contentWidth;
scaleX = scaleY = minScale;
break;
case BoxFit.none:
scaleX = scaleY = 1.0;
break;
case BoxFit.scaleDown:
final minScale =
min(size.width / contentWidth, size.height / contentHeight);
scaleX = scaleY = minScale < 1.0 ? minScale : 1.0;
break;
}
Mat2D.setIdentity(_transform);
_transform[4] = size.width / 2.0 + (alignment.x * size.width / 2.0);
_transform[5] = size.height / 2.0 + (alignment.y * size.height / 2.0);
Mat2D.scale(_transform, _transform, Vec2D.fromValues(scaleX, scaleY));
Mat2D.setIdentity(_center);
_center[4] = x;
_center[5] = y;
Mat2D.multiply(_transform, _transform, _center);
canvas.translate(
size.width / 2.0 + (alignment.x * size.width / 2.0),
size.height / 2.0 + (alignment.y * size.height / 2.0),
);
canvas.scale(scaleX, scaleY);
canvas.translate(x, y);
artboard.draw(canvas);
canvas.restore();
}
}