WIP on simplifying controller use

This commit is contained in:
matt Sullivan
2021-06-11 18:45:57 -07:00
parent c751bd7d41
commit 7d82d06c79
8 changed files with 190 additions and 102 deletions

View File

@ -8,7 +8,7 @@ Runtime docs are available in [Rive's help center](https://help.rive.app/runtime
```yaml ```yaml
dependencies: dependencies:
rive: ^0.7.16 rive: ^0.7.17
``` ```
## Quick Start ## Quick Start

View File

@ -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<ExampleAnimation> {
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,
),
),
);
}
}

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; 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/example_state_machine.dart';
import 'package:rive_example/liquid_download.dart'; import 'package:rive_example/liquid_download.dart';
import 'package:rive_example/little_machine.dart'; import 'package:rive_example/little_machine.dart';
@ -42,7 +42,7 @@ class Home extends StatelessWidget {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute<void>( MaterialPageRoute<void>(
builder: (context) => const ExampleAnimation(), builder: (context) => const PlayPauseAnimation(),
), ),
); );
}, },

View File

@ -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<PlayPauseAnimation> {
// 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,
),
),
);
}
}

View File

@ -22,3 +22,4 @@ export 'package:rive/src/rive_file.dart';
export 'package:rive/src/runtime_artboard.dart'; export 'package:rive/src/runtime_artboard.dart';
export 'package:rive/src/state_machine_controller.dart'; export 'package:rive/src/state_machine_controller.dart';
export 'package:rive/src/widgets/rive_animation.dart'; export 'package:rive/src/widgets/rive_animation.dart';
export 'package:rive/src/widgets/rive_controller_animation.dart';

View File

@ -2,6 +2,8 @@ import 'dart:collection';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:collection/collection.dart'; 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/core.dart';
import 'package:rive/src/core/field_types/core_field_type.dart'; import 'package:rive/src/core/field_types/core_field_type.dart';
import 'package:rive/src/generated/animation/animation_state_base.dart'; import 'package:rive/src/generated/animation/animation_state_base.dart';
@ -219,6 +221,19 @@ class RiveFile {
return RiveFile._(reader, RuntimeHeader.read(reader)); return RiveFile._(reader, RuntimeHeader.read(reader));
} }
/// Imports a Rive file from an asset bundle
static Future<RiveFile> 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<RiveFile> 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 /// Returns all artboards in the file
List<Artboard> get artboards => _artboards; List<Artboard> get artboards => _artboards;

View File

@ -1,10 +1,6 @@
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:rive/rive.dart'; import 'package:rive/rive.dart';
import 'package:rive/src/rive_core/artboard.dart'; import 'package:rive/src/rive_core/artboard.dart';
import 'package:http/http.dart' as http;
enum _Source { enum _Source {
asset, asset,
@ -79,38 +75,16 @@ class _RiveAnimationState extends State<RiveAnimation> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// Load the Rive file from the asset bundle
switch (widget.src) { if (widget.src == _Source.asset) {
case _Source.asset: RiveFile.asset(widget.name).then(_init);
_loadAsset(); } else if (widget.src == _Source.network) {
break; RiveFile.network(widget.name).then(_init);
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.
Future<void> _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 /// Initializes the artboard, animation, and controller
void _init(ByteData data) { void _init(RiveFile file) {
final file = RiveFile.import(data);
final artboard = widget.artboard != null final artboard = widget.artboard != null
? file.artboardByName(widget.artboard!) ? file.artboardByName(widget.artboard!)
: file.mainArtboard; : file.mainArtboard;

View File

@ -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<RiveAnimationController> 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<RiveControllerAnimation> {
/// Rive controller
final _controllers = <RiveAnimationController>[];
/// 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();
}