mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-07-07 17:04:54 +08:00

Bascially add tracking of fileAssetReferencers from file assets, so that when file assets update, they can tell things that reference them to update also. its worth looking at the rive-flutter & runtime implementations in cpp it looks like we can hook into the delete hooks nicely to clean up after ourselves (so that when artboards get collected we are not holding references to them from file assets) in dart this is more of a problem, however using weakReferences we do end up seeing artboards go out of scope but weakReferences requires us to bump to a min dart of 2.17 (we are 2.14 in flutter & 2.12 in our editor atm) the update here also uses the referencers to mark fonts dirty when fonts are set, which causes them to be updated on the next draw cycle (video showing font updates working properly now in dart) https://github.com/rive-app/rive/assets/1216025/d65ea2cf-95d6-4412-b8f5-368cda609d0b (video showing how referencers get collected and trashed in dart, it looks like we hold onto about 10 of them at a time.. but they do drop off over time. also we start with 2 references, the main artboard and the artboard instance) https://github.com/rive-app/rive/assets/1216025/b11b7b46-aa9d-4dcc-bc11-f745d8449575 Diffs= 7bc216b03 add ability to attach callbacks when resolving file asset listeners (#6068) Co-authored-by: Maxwell Talbot <talbot.maxwell@gmail.com>
285 lines
7.2 KiB
Dart
285 lines
7.2 KiB
Dart
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
|
import 'dart:math';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import 'package:rive/rive.dart';
|
|
|
|
/// An example showing how to load image or font assets dynamically.
|
|
///
|
|
/// In this example there is no delay in the assets loading, as they are
|
|
/// cached in memory.
|
|
///
|
|
/// The example also shows how to swap out the assets multiple times by
|
|
/// keeping a reference to the asset and swapping it out.
|
|
class CustomCachedAssetLoading extends StatefulWidget {
|
|
const CustomCachedAssetLoading({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
State<CustomCachedAssetLoading> createState() =>
|
|
_CustomCachedAssetLoadingState();
|
|
}
|
|
|
|
class _CustomCachedAssetLoadingState extends State<CustomCachedAssetLoading> {
|
|
var _index = 0;
|
|
var _ready = false;
|
|
final _imageCache = [];
|
|
final _fontCache = [];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_warmUpCache();
|
|
}
|
|
|
|
/// Create a cache of images and fonts to swap out instantly.
|
|
Future<void> _warmUpCache() async {
|
|
final futures = <Future>[];
|
|
loadImage() async {
|
|
final res = await http.get(Uri.parse('https://picsum.photos/1000/1000'));
|
|
final body = Uint8List.view(res.bodyBytes.buffer);
|
|
final image = await ImageAsset.parseBytes(body);
|
|
if (image != null) {
|
|
_imageCache.add(image);
|
|
}
|
|
}
|
|
|
|
loadFont(url) async {
|
|
final res = await http.get(Uri.parse(url));
|
|
final body = Uint8List.view(res.bodyBytes.buffer);
|
|
final font = await FontAsset.parseBytes(body);
|
|
|
|
if (font != null) {
|
|
_fontCache.add(font);
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i <= 10; i++) {
|
|
futures.add(loadImage());
|
|
}
|
|
|
|
for (var url in [
|
|
'https://cdn.rive.app/runtime/flutter/IndieFlower-Regular.ttf',
|
|
'https://cdn.rive.app/runtime/flutter/comic-neue.ttf',
|
|
'https://cdn.rive.app/runtime/flutter/inter.ttf',
|
|
'https://cdn.rive.app/runtime/flutter/inter-tight.ttf',
|
|
'https://cdn.rive.app/runtime/flutter/josefin-sans.ttf',
|
|
'https://cdn.rive.app/runtime/flutter/send-flowers.ttf',
|
|
]) {
|
|
futures.add(loadFont(url));
|
|
}
|
|
|
|
await Future.wait(futures);
|
|
|
|
setState(() => _ready = true);
|
|
}
|
|
|
|
void next() {
|
|
setState(() => _index += 1);
|
|
}
|
|
|
|
void previous() {
|
|
setState(() => _index -= 1);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (!_ready) {
|
|
return const Scaffold(
|
|
body: Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Padding(
|
|
padding: EdgeInsets.all(8.0),
|
|
child: Text('Warming up cache. Loading files from network...'),
|
|
),
|
|
CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Custom Cached Asset Loading'),
|
|
),
|
|
body: Center(
|
|
child: Row(
|
|
children: [
|
|
GestureDetector(
|
|
onTap: previous,
|
|
child: const Icon(Icons.arrow_back),
|
|
),
|
|
Expanded(
|
|
child: (_index % 2 == 0)
|
|
? _RiveRandomCachedImage(imageCache: _imageCache)
|
|
: _RiveRandomCachedFont(fontCache: _fontCache),
|
|
),
|
|
GestureDetector(
|
|
onTap: next,
|
|
child: const Icon(Icons.arrow_forward),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _RiveRandomCachedImage extends StatefulWidget {
|
|
const _RiveRandomCachedImage({
|
|
Key? key,
|
|
required this.imageCache,
|
|
}) : super(key: key);
|
|
|
|
final List imageCache;
|
|
|
|
@override
|
|
State<_RiveRandomCachedImage> createState() => __RiveRandomCachedImageState();
|
|
}
|
|
|
|
class __RiveRandomCachedImageState extends State<_RiveRandomCachedImage> {
|
|
List get _imageCache => widget.imageCache;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadRiveFile();
|
|
}
|
|
|
|
RiveFile? _riveImageSampleFile;
|
|
// A reference to the Rive image. Can be use to swap out the image at any
|
|
// point.
|
|
ImageAsset? _imageAsset;
|
|
|
|
Future<void> _loadRiveFile() async {
|
|
final imageFile = await RiveFile.asset(
|
|
'assets/asset.riv',
|
|
loadEmbeddedAssets: false,
|
|
assetLoader: CallbackAssetLoader(
|
|
(asset) async {
|
|
if (asset is ImageAsset) {
|
|
asset.image = _imageCache[Random().nextInt(_imageCache.length)];
|
|
// Maintain a reference to the image asset
|
|
// so we can swap it out later instantly.
|
|
_imageAsset = asset;
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
),
|
|
);
|
|
|
|
setState(() => _riveImageSampleFile = imageFile);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (_riveImageSampleFile == null) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
|
|
return Column(
|
|
children: [
|
|
Expanded(
|
|
child: RiveAnimation.direct(
|
|
_riveImageSampleFile!,
|
|
fit: BoxFit.cover,
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
_imageAsset?.image =
|
|
_imageCache[Random().nextInt(_imageCache.length)];
|
|
},
|
|
child: const Text('Random image asset'),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _RiveRandomCachedFont extends StatefulWidget {
|
|
const _RiveRandomCachedFont({
|
|
Key? key,
|
|
required this.fontCache,
|
|
}) : super(key: key);
|
|
|
|
final List fontCache;
|
|
|
|
@override
|
|
State<_RiveRandomCachedFont> createState() => __RiveRandomCachedFontState();
|
|
}
|
|
|
|
class __RiveRandomCachedFontState extends State<_RiveRandomCachedFont> {
|
|
List get _fontCache => widget.fontCache;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadRiveFile();
|
|
}
|
|
|
|
RiveFile? _riveFontSampleFile;
|
|
final List<FontAsset?> _fontAssets = [];
|
|
|
|
Future<void> _loadRiveFile() async {
|
|
final fontFile = await RiveFile.asset(
|
|
'assets/sampletext.riv',
|
|
loadEmbeddedAssets: false,
|
|
assetLoader: CallbackAssetLoader(
|
|
(asset) async {
|
|
if (asset is FontAsset) {
|
|
asset.font = _fontCache[Random().nextInt(_fontCache.length)];
|
|
_fontAssets.add(asset);
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
),
|
|
);
|
|
|
|
setState(() {
|
|
_riveFontSampleFile = fontFile;
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (_riveFontSampleFile == null) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
|
|
return Column(
|
|
children: [
|
|
Expanded(
|
|
child: RiveAnimation.direct(
|
|
_riveFontSampleFile!,
|
|
fit: BoxFit.cover,
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
for (var element in _fontAssets) {
|
|
element?.font = _fontCache[Random().nextInt(_fontCache.length)];
|
|
}
|
|
},
|
|
child: const Text('Random font asset'),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|