From 1d5419103178163ff1a0e13fefefd737441a11b1 Mon Sep 17 00:00:00 2001 From: matt Sullivan Date: Thu, 27 May 2021 17:37:54 -0700 Subject: [PATCH] Adds high level RiveAnimation widget --- example/lib/example_animation.dart | 1 - example/lib/main.dart | 17 ++- example/lib/simple_animation.dart | 22 +++ .../macos/Runner/DebugProfile.entitlements | 2 + example/pubspec.yaml | 2 +- lib/rive.dart | 1 + lib/src/rive.dart | 7 +- lib/src/widgets/rive_animation.dart | 133 ++++++++++++++++++ 8 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 example/lib/simple_animation.dart create mode 100644 lib/src/widgets/rive_animation.dart diff --git a/example/lib/example_animation.dart b/example/lib/example_animation.dart index 6d0ca48..38da10b 100644 --- a/example/lib/example_animation.dart +++ b/example/lib/example_animation.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; import 'package:rive/rive.dart'; class ExampleAnimation extends StatefulWidget { diff --git a/example/lib/main.dart b/example/lib/main.dart index 27fe750..945415a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -3,6 +3,7 @@ import 'package:rive_example/example_animation.dart'; import 'package:rive_example/example_state_machine.dart'; import 'package:rive_example/liquid_download.dart'; import 'package:rive_example/little_machine.dart'; +import 'package:rive_example/simple_animation.dart'; import 'package:rive_example/state_machine_skills.dart'; void main() => runApp(MaterialApp( @@ -22,7 +23,21 @@ class Home extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ ElevatedButton( - child: const Text('Animation'), + child: const Text('Simple Animation'), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const SimpleAnimation(), + ), + ); + }, + ), + const SizedBox( + height: 10, + ), + ElevatedButton( + child: const Text('Play/Pause Animation'), onPressed: () { Navigator.push( context, diff --git a/example/lib/simple_animation.dart b/example/lib/simple_animation.dart new file mode 100644 index 0000000..6b374a5 --- /dev/null +++ b/example/lib/simple_animation.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:rive/rive.dart'; + +class SimpleAnimation extends StatelessWidget { + const SimpleAnimation({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Simple Animation'), + ), + body: const Center( + // child: RiveAnimation.asset('assets/off_road_car.riv'), + child: RiveAnimation.network( + 'https://cdn.rive.app/animations/truck.riv', + fit: BoxFit.cover, + ), + ), + ); + } +} diff --git a/example/macos/Runner/DebugProfile.entitlements b/example/macos/Runner/DebugProfile.entitlements index dddb8a3..3ba6c12 100644 --- a/example/macos/Runner/DebugProfile.entitlements +++ b/example/macos/Runner/DebugProfile.entitlements @@ -6,6 +6,8 @@ com.apple.security.cs.allow-jit + com.apple.security.network.client + com.apple.security.network.server diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 3e4cdad..b95b980 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,5 +1,5 @@ name: rive_example -description: A new Flutter project. +description: A collection of Rive Flutter examples publish_to: "none" # Remove this line if you wish to publish to pub.dev diff --git a/lib/rive.dart b/lib/rive.dart index a9eff5b..b7920ce 100644 --- a/lib/rive.dart +++ b/lib/rive.dart @@ -21,3 +21,4 @@ export 'package:rive/src/rive_core/shapes/shape.dart'; export 'package:rive/src/rive_file.dart'; export 'package:rive/src/runtime_artboard.dart'; export 'package:rive/src/state_machine_controller.dart'; +export 'package:rive/src/widgets/rive_animation.dart'; diff --git a/lib/src/rive.dart b/lib/src/rive.dart index a2cc9da..f02d229 100644 --- a/lib/src/rive.dart +++ b/lib/src/rive.dart @@ -33,9 +33,10 @@ class Rive extends LeafRenderObjectWidget { const Rive({ required this.artboard, this.useArtboardSize = false, - this.fit = BoxFit.contain, - this.alignment = Alignment.center, - }); + BoxFit? fit, + Alignment? alignment, + }) : fit = fit ?? BoxFit.contain, + alignment = alignment ?? Alignment.center; @override RenderObject createRenderObject(BuildContext context) { diff --git a/lib/src/widgets/rive_animation.dart b/lib/src/widgets/rive_animation.dart new file mode 100644 index 0000000..0d9c1d3 --- /dev/null +++ b/lib/src/widgets/rive_animation.dart @@ -0,0 +1,133 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:rive/rive.dart'; +import 'package:rive/src/rive_core/artboard.dart'; + +enum _Source { + asset, + network, +} + +/// High level widget that plays an animation from a Rive file. If artboard or +/// animation are not specified, the default artboard and first animation fonund +/// within it are used. +class RiveAnimation extends StatefulWidget { + final String name; + final _Source src; + final String? artboard; + final String? animation; + final BoxFit? fit; + final Alignment? alignment; + + /// Creates a new RiveAnimation from an asset bundle + const RiveAnimation.asset( + this.name, { + this.artboard, + this.animation, + this.fit, + this.alignment, + }) : src = _Source.asset; + + const RiveAnimation.network( + this.name, { + this.artboard, + this.animation, + this.fit, + this.alignment, + }) : src = _Source.network; + + @override + _RiveAnimationState createState() => _RiveAnimationState(); +} + +class _RiveAnimationState extends State { + /// Rive controller + late RiveAnimationController _controller; + + /// Active artboard + Artboard? _artboard; + + @override + void initState() { + super.initState(); + // Load the Rive file from the asset bundle + switch (widget.src) { + case _Source.asset: + _loadAsset(); + break; + case _Source.network: + _loadNetwork(); + break; + } + } + + /// Loads a Rive file from an asset bundle and configures artboard, animation, + /// and controller. + void _loadAsset() { + rootBundle.load(widget.name).then( + (data) async { + _init(data); + }, + ); + } + + /// Loads a Rive file from an HTTP source and configures artboard, animation, + /// and controller. + void _loadNetwork() { + final client = HttpClient(); + final contents = []; + + client + .getUrl(Uri.parse(widget.name)) + .then( + (req) async => req.close(), + ) + .then( + (res) => res.listen( + contents.addAll, + onDone: () { + final data = ByteData.view(Uint8List.fromList(contents).buffer); + _init(data); + }, + ), + ); + } + + /// Initializes the artboard, animation, and controller + void _init(ByteData data) { + final file = RiveFile.import(data); + final artboard = widget.artboard != null + ? file.artboardByName(widget.artboard!) + : file.mainArtboard; + + if (artboard == null) { + throw const FormatException('Unable to load artboard'); + } + if (artboard.animations.isEmpty) { + throw FormatException('No animations in artboard ${artboard.name}'); + } + + final animationName = widget.animation ?? artboard.animations.first.name; + + artboard.addController(_controller = SimpleAnimation(animationName)); + setState(() => _artboard = artboard); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => _artboard != null + ? Rive( + artboard: _artboard!, + fit: widget.fit, + alignment: widget.alignment, + ) + : const SizedBox(); +}