From 7d82d06c791335d6ce627d4175ae5b15c0fe77f7 Mon Sep 17 00:00:00 2001 From: matt Sullivan Date: Fri, 11 Jun 2021 18:45:57 -0700 Subject: [PATCH] WIP on simplifying controller use --- README.md | 2 +- example/lib/example_animation.dart | 67 ---------- example/lib/main.dart | 4 +- example/lib/play_pause_animation.dart | 50 ++++++++ lib/rive.dart | 1 + lib/src/rive_file.dart | 15 +++ lib/src/widgets/rive_animation.dart | 38 +----- .../widgets/rive_controller_animation.dart | 115 ++++++++++++++++++ 8 files changed, 190 insertions(+), 102 deletions(-) delete mode 100644 example/lib/example_animation.dart create mode 100644 example/lib/play_pause_animation.dart create mode 100644 lib/src/widgets/rive_controller_animation.dart diff --git a/README.md b/README.md index e87c522..3970b20 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Runtime docs are available in [Rive's help center](https://help.rive.app/runtime ```yaml dependencies: - rive: ^0.7.16 + rive: ^0.7.17 ``` ## Quick Start diff --git a/example/lib/example_animation.dart b/example/lib/example_animation.dart deleted file mode 100644 index cd2eb06..0000000 --- a/example/lib/example_animation.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:rive/rive.dart'; - -class ExampleAnimation extends StatefulWidget { - const ExampleAnimation({Key? key}) : super(key: key); - - @override - _ExampleAnimationState createState() => _ExampleAnimationState(); -} - -class _ExampleAnimationState extends State { - void _togglePlay() { - if (_controller == null) { - return; - } - setState(() => _controller!.isActive = !_controller!.isActive); - } - - /// Tracks if the animation is playing by whether controller is running. - bool get isPlaying => _controller?.isActive ?? false; - - Artboard? _riveArtboard; - RiveAnimationController? _controller; - @override - void initState() { - super.initState(); - - // Load the animation file from the bundle, note that you could also - // download this. The RiveFile just expects a list of bytes. - rootBundle.load('assets/off_road_car.riv').then( - (data) async { - // Load the RiveFile from the binary data. - final file = RiveFile.import(data); - - // The artboard is the root of the animation and gets drawn in the - // Rive widget. - final artboard = file.mainArtboard.instance(); - // Add a controller to play back a known animation on the main/default - // artboard. We store a reference to it so we can toggle playback. - artboard.addController(_controller = SimpleAnimation('idle')); - setState(() => _riveArtboard = artboard); - }, - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Animation Example'), - ), - body: Center( - child: _riveArtboard == null - ? const SizedBox() - : Rive(artboard: _riveArtboard!), - ), - floatingActionButton: FloatingActionButton( - onPressed: _togglePlay, - tooltip: isPlaying ? 'Pause' : 'Play', - child: Icon( - isPlaying ? Icons.pause : Icons.play_arrow, - ), - ), - ); - } -} diff --git a/example/lib/main.dart b/example/lib/main.dart index 945415a..32ee5fe 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:rive_example/example_animation.dart'; +import 'package:rive_example/play_pause_animation.dart'; import 'package:rive_example/example_state_machine.dart'; import 'package:rive_example/liquid_download.dart'; import 'package:rive_example/little_machine.dart'; @@ -42,7 +42,7 @@ class Home extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => const ExampleAnimation(), + builder: (context) => const PlayPauseAnimation(), ), ); }, diff --git a/example/lib/play_pause_animation.dart b/example/lib/play_pause_animation.dart new file mode 100644 index 0000000..64ff1b8 --- /dev/null +++ b/example/lib/play_pause_animation.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:rive/rive.dart'; + +class PlayPauseAnimation extends StatefulWidget { + const PlayPauseAnimation({Key? key}) : super(key: key); + + @override + _PlayPauseAnimationState createState() => _PlayPauseAnimationState(); +} + +class _PlayPauseAnimationState extends State { + // Controller for playback + late RiveAnimationController _controller; + + // This will toggle between play and pause states for the animation + void _togglePlay() { + setState(() => _controller.isActive = !_controller.isActive); + } + + /// Tracks if the animation is playing by whether controller is running. + bool get isPlaying => _controller.isActive; + + @override + void initState() { + super.initState(); + _controller = SimpleAnimation('idle'); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Animation Example'), + ), + body: Center( + child: RiveControllerAnimation.network( + 'https://cdn.rive.app/animations/vehicles.riv', + controllers: [_controller], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: _togglePlay, + tooltip: isPlaying ? 'Pause' : 'Play', + child: Icon( + isPlaying ? Icons.pause : Icons.play_arrow, + ), + ), + ); + } +} diff --git a/lib/rive.dart b/lib/rive.dart index b7920ce..2c5ee87 100644 --- a/lib/rive.dart +++ b/lib/rive.dart @@ -22,3 +22,4 @@ 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'; +export 'package:rive/src/widgets/rive_controller_animation.dart'; diff --git a/lib/src/rive_file.dart b/lib/src/rive_file.dart index 3dde563..c8e17a9 100644 --- a/lib/src/rive_file.dart +++ b/lib/src/rive_file.dart @@ -2,6 +2,8 @@ import 'dart:collection'; import 'dart:typed_data'; import 'package:collection/collection.dart'; +import 'package:flutter/services.dart'; +import 'package:http/http.dart' as http; import 'package:rive/src/core/core.dart'; import 'package:rive/src/core/field_types/core_field_type.dart'; import 'package:rive/src/generated/animation/animation_state_base.dart'; @@ -219,6 +221,19 @@ class RiveFile { return RiveFile._(reader, RuntimeHeader.read(reader)); } + /// Imports a Rive file from an asset bundle + static Future asset(String bundleKey) async { + final bytes = await rootBundle.load(bundleKey); + return RiveFile.import(bytes); + } + + /// Imports a Rive file from a url over http + static Future network(String url) async { + final res = await http.get(Uri.parse(url)); + final bytes = ByteData.view(res.bodyBytes.buffer); + return RiveFile.import(bytes); + } + /// Returns all artboards in the file List get artboards => _artboards; diff --git a/lib/src/widgets/rive_animation.dart b/lib/src/widgets/rive_animation.dart index 6857c48..8dc557a 100644 --- a/lib/src/widgets/rive_animation.dart +++ b/lib/src/widgets/rive_animation.dart @@ -1,10 +1,6 @@ -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'; -import 'package:http/http.dart' as http; enum _Source { asset, @@ -79,38 +75,16 @@ class _RiveAnimationState extends State { @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; + + if (widget.src == _Source.asset) { + RiveFile.asset(widget.name).then(_init); + } else if (widget.src == _Source.network) { + RiveFile.network(widget.name).then(_init); } } - /// 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. - Future _loadNetwork() async { - final res = await http.get(Uri.parse(widget.name)); - final data = ByteData.view(res.bodyBytes.buffer); - _init(data); - } - /// Initializes the artboard, animation, and controller - void _init(ByteData data) { - final file = RiveFile.import(data); + void _init(RiveFile file) { final artboard = widget.artboard != null ? file.artboardByName(widget.artboard!) : file.mainArtboard; diff --git a/lib/src/widgets/rive_controller_animation.dart b/lib/src/widgets/rive_controller_animation.dart new file mode 100644 index 0000000..0066a25 --- /dev/null +++ b/lib/src/widgets/rive_controller_animation.dart @@ -0,0 +1,115 @@ +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 RiveControllerAnimation extends StatefulWidget { + /// The asset name or url + final String name; + + /// The type of source used to retrieve the asset + final _Source src; + + /// The name of the artboard to use; default artboard if not specified + final String? artboard; + + /// List of Rive controllers to attach + final List controllers; + + /// Fit for the animation in the widget + final BoxFit? fit; + + /// Alignment for the animation in the widget + final Alignment? alignment; + + /// Enable/disable antialiasing when rendering + final bool antialiasing; + + /// Widget displayed while the rive is loading + final Widget? placeHolder; + + /// Creates a new RiveControllerAnimation from an asset bundle + const RiveControllerAnimation.asset( + this.name, { + this.artboard, + this.controllers = const [], + this.fit, + this.alignment, + this.placeHolder, + this.antialiasing = true, + }) : src = _Source.asset; + + const RiveControllerAnimation.network( + this.name, { + this.artboard, + this.controllers = const [], + this.fit, + this.alignment, + this.placeHolder, + this.antialiasing = true, + }) : src = _Source.network; + + @override + _RiveControllerAnimationState createState() => + _RiveControllerAnimationState(); +} + +class _RiveControllerAnimationState extends State { + /// Rive controller + final _controllers = []; + + /// Active artboard + Artboard? _artboard; + + @override + void initState() { + super.initState(); + + if (widget.src == _Source.asset) { + RiveFile.asset(widget.name).then(_init); + } else if (widget.src == _Source.network) { + RiveFile.network(widget.name).then(_init); + } + } + + /// Initializes the artboard, animation, and controller + void _init(RiveFile file) { + 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}'); + } + + // Attach each controller to the artboard + widget.controllers.forEach(artboard.addController); + setState(() => _artboard = artboard); + } + + @override + void dispose() { + _controllers.forEach((c) => c.dispose()); + super.dispose(); + } + + @override + Widget build(BuildContext context) => _artboard != null + ? Rive( + artboard: _artboard!, + fit: widget.fit, + alignment: widget.alignment, + antialiasing: widget.antialiasing, + ) + : widget.placeHolder ?? const SizedBox(); +}