mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-06-23 23:46:42 +08:00
Adding asset resolver for loading out of band assets
This commit is contained in:
@ -4,12 +4,32 @@ import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/rive_core/assets/file_asset.dart';
|
||||
import 'package:rive/src/rive_core/assets/file_asset_contents.dart';
|
||||
|
||||
/// A helper for resolving Rive file assets (like images) that are provided out
|
||||
/// of band with regards to the .riv file itself.
|
||||
// ignore: one_member_abstracts
|
||||
abstract class RiveAssetResolver {
|
||||
Future<Uint8List> loadContents(FileAsset asset);
|
||||
}
|
||||
|
||||
class FileAssetImporter extends ImportStackObject {
|
||||
final RiveAssetResolver? assetResolver;
|
||||
final FileAsset fileAsset;
|
||||
|
||||
FileAssetImporter(this.fileAsset);
|
||||
FileAssetImporter(this.fileAsset, this.assetResolver);
|
||||
|
||||
bool _loadedContents = false;
|
||||
|
||||
void loadContents(FileAssetContents contents) {
|
||||
_loadedContents = true;
|
||||
fileAsset.decode(contents.bytes as Uint8List);
|
||||
}
|
||||
|
||||
@override
|
||||
bool resolve() {
|
||||
if (!_loadedContents) {
|
||||
// try to get them out of band
|
||||
assetResolver?.loadContents(fileAsset).then(fileAsset.decode);
|
||||
}
|
||||
return super.resolve();
|
||||
}
|
||||
}
|
||||
|
@ -9,4 +9,33 @@ abstract class AssetBase<T extends CoreContext> extends Core<T> {
|
||||
int get coreType => AssetBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes => {AssetBase.typeKey};
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// Name field with key 203.
|
||||
static const String nameInitialValue = '';
|
||||
String _name = nameInitialValue;
|
||||
static const int namePropertyKey = 203;
|
||||
|
||||
/// Name of the asset
|
||||
String get name => _name;
|
||||
|
||||
/// Change the [_name] field value.
|
||||
/// [nameChanged] will be invoked only if the field's value has changed.
|
||||
set name(String value) {
|
||||
if (_name == value) {
|
||||
return;
|
||||
}
|
||||
String from = _name;
|
||||
_name = value;
|
||||
if (hasValidated) {
|
||||
nameChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void nameChanged(String from, String to);
|
||||
|
||||
@override
|
||||
void copy(covariant AssetBase source) {
|
||||
_name = source._name;
|
||||
}
|
||||
}
|
||||
|
@ -10,4 +10,34 @@ abstract class FileAssetBase extends Asset {
|
||||
int get coreType => FileAssetBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes => {FileAssetBase.typeKey, AssetBase.typeKey};
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// AssetId field with key 204.
|
||||
static const int assetIdInitialValue = 0;
|
||||
int _assetId = assetIdInitialValue;
|
||||
static const int assetIdPropertyKey = 204;
|
||||
|
||||
/// Id of the asset as stored on the backend
|
||||
int get assetId => _assetId;
|
||||
|
||||
/// Change the [_assetId] field value.
|
||||
/// [assetIdChanged] will be invoked only if the field's value has changed.
|
||||
set assetId(int value) {
|
||||
if (_assetId == value) {
|
||||
return;
|
||||
}
|
||||
int from = _assetId;
|
||||
_assetId = value;
|
||||
if (hasValidated) {
|
||||
assetIdChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void assetIdChanged(int from, int to);
|
||||
|
||||
@override
|
||||
void copy(covariant FileAssetBase source) {
|
||||
super.copy(source);
|
||||
_assetId = source._assetId;
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,9 @@ import 'package:rive/src/generated/animation/transition_number_condition_base.da
|
||||
import 'package:rive/src/generated/animation/transition_trigger_condition_base.dart';
|
||||
import 'package:rive/src/generated/animation/transition_value_condition_base.dart';
|
||||
import 'package:rive/src/generated/artboard_base.dart';
|
||||
import 'package:rive/src/generated/assets/asset_base.dart';
|
||||
import 'package:rive/src/generated/assets/drawable_asset_base.dart';
|
||||
import 'package:rive/src/generated/assets/file_asset_base.dart';
|
||||
import 'package:rive/src/generated/assets/file_asset_contents_base.dart';
|
||||
import 'package:rive/src/generated/assets/folder_base.dart';
|
||||
import 'package:rive/src/generated/assets/image_asset_base.dart';
|
||||
@ -1098,6 +1100,16 @@ class RiveCoreContext {
|
||||
object.ty = value;
|
||||
}
|
||||
break;
|
||||
case AssetBase.namePropertyKey:
|
||||
if (object is AssetBase && value is String) {
|
||||
object.name = value;
|
||||
}
|
||||
break;
|
||||
case FileAssetBase.assetIdPropertyKey:
|
||||
if (object is FileAssetBase && value is int) {
|
||||
object.assetId = value;
|
||||
}
|
||||
break;
|
||||
case DrawableAssetBase.heightPropertyKey:
|
||||
if (object is DrawableAssetBase && value is double) {
|
||||
object.height = value;
|
||||
@ -1127,6 +1139,7 @@ class RiveCoreContext {
|
||||
case ComponentBase.namePropertyKey:
|
||||
case AnimationBase.namePropertyKey:
|
||||
case StateMachineComponentBase.namePropertyKey:
|
||||
case AssetBase.namePropertyKey:
|
||||
return stringType;
|
||||
case ComponentBase.parentIdPropertyKey:
|
||||
case DrawTargetBase.drawableIdPropertyKey:
|
||||
@ -1180,6 +1193,7 @@ class RiveCoreContext {
|
||||
case ImageBase.assetIdPropertyKey:
|
||||
case DrawRulesBase.drawTargetIdPropertyKey:
|
||||
case TendonBase.boneIdPropertyKey:
|
||||
case FileAssetBase.assetIdPropertyKey:
|
||||
return uintType;
|
||||
case ConstraintBase.strengthPropertyKey:
|
||||
case DistanceConstraintBase.distancePropertyKey:
|
||||
@ -1301,6 +1315,8 @@ class RiveCoreContext {
|
||||
return (object as AnimationBase).name;
|
||||
case StateMachineComponentBase.namePropertyKey:
|
||||
return (object as StateMachineComponentBase).name;
|
||||
case AssetBase.namePropertyKey:
|
||||
return (object as AssetBase).name;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@ -1411,6 +1427,8 @@ class RiveCoreContext {
|
||||
return (object as DrawRulesBase).drawTargetId;
|
||||
case TendonBase.boneIdPropertyKey:
|
||||
return (object as TendonBase).boneId;
|
||||
case FileAssetBase.assetIdPropertyKey:
|
||||
return (object as FileAssetBase).assetId;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -1662,6 +1680,11 @@ class RiveCoreContext {
|
||||
object.name = value;
|
||||
}
|
||||
break;
|
||||
case AssetBase.namePropertyKey:
|
||||
if (object is AssetBase) {
|
||||
object.name = value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1927,6 +1950,11 @@ class RiveCoreContext {
|
||||
object.boneId = value;
|
||||
}
|
||||
break;
|
||||
case FileAssetBase.assetIdPropertyKey:
|
||||
if (object is FileAssetBase) {
|
||||
object.assetId = value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,14 +13,14 @@ class Asset extends AssetBase {
|
||||
_backboard = value;
|
||||
}
|
||||
|
||||
@override
|
||||
void nameChanged(String from, String to) {}
|
||||
|
||||
@override
|
||||
void onAdded() {}
|
||||
|
||||
@override
|
||||
void onAddedDirty() {}
|
||||
|
||||
@override
|
||||
void parentIdChanged(int from, int to) {}
|
||||
|
||||
bool get isUsable => false;
|
||||
}
|
||||
|
@ -10,4 +10,9 @@ abstract class DrawableAsset extends DrawableAssetBase {
|
||||
|
||||
@override
|
||||
void widthChanged(double from, double to) {}
|
||||
|
||||
@override
|
||||
void assetIdChanged(int from, int to) {
|
||||
width = height = 0;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,9 @@ import 'package:rive/src/rive_core/backboard.dart';
|
||||
export 'package:rive/src/generated/assets/file_asset_base.dart';
|
||||
|
||||
abstract class FileAsset extends FileAssetBase {
|
||||
@override
|
||||
void assetIdChanged(int from, int to) {}
|
||||
|
||||
Future<void> decode(Uint8List bytes);
|
||||
|
||||
@override
|
||||
@ -20,6 +23,20 @@ abstract class FileAsset extends FileAssetBase {
|
||||
|
||||
return super.import(stack);
|
||||
}
|
||||
|
||||
/// Returns the file extension, for example for an image it would be png.
|
||||
String get fileExtension;
|
||||
|
||||
/// Returns a unique filename, useful for resolving files out of band.
|
||||
String get uniqueFilename {
|
||||
// remove final extension
|
||||
var uniqueFilename = name;
|
||||
var index = uniqueFilename.lastIndexOf('.');
|
||||
if (index != -1) {
|
||||
uniqueFilename = uniqueFilename.substring(0, index);
|
||||
}
|
||||
return '$uniqueFilename-$assetId.$fileExtension';
|
||||
}
|
||||
}
|
||||
|
||||
/// A mixin for any class that references a generic FileAsset.
|
||||
|
@ -1,7 +1,10 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:rive/src/generated/assets/image_asset_base.dart';
|
||||
import 'package:rive/src/rive_core/shapes/image.dart';
|
||||
|
||||
export 'package:rive/src/generated/assets/image_asset_base.dart';
|
||||
|
||||
class ImageAsset extends ImageAssetBase {
|
||||
@ -17,4 +20,13 @@ class ImageAsset extends ImageAssetBase {
|
||||
});
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Image getDefaultObject() => Image()
|
||||
..asset = this
|
||||
..name = name;
|
||||
|
||||
/// The editor works with images as PNGs, even if their sources may have come
|
||||
/// from other formats.
|
||||
@override
|
||||
String get fileExtension => 'png';
|
||||
}
|
||||
|
@ -89,10 +89,12 @@ class RiveFile {
|
||||
|
||||
Backboard _backboard = Backboard.unknown;
|
||||
final _artboards = <Artboard>[];
|
||||
final RiveAssetResolver? _assetResolver;
|
||||
|
||||
RiveFile._(
|
||||
BinaryReader reader,
|
||||
this.header,
|
||||
this._assetResolver,
|
||||
) {
|
||||
/// Property fields table of contents
|
||||
final propertyToField = HashMap<int, CoreFieldType>();
|
||||
@ -182,7 +184,7 @@ class RiveFile {
|
||||
break;
|
||||
}
|
||||
case ImageAssetBase.typeKey:
|
||||
stackObject = FileAssetImporter(object as FileAsset);
|
||||
stackObject = FileAssetImporter(object as FileAsset, _assetResolver);
|
||||
stackType = FileAssetBase.typeKey;
|
||||
break;
|
||||
default:
|
||||
@ -243,22 +245,38 @@ class RiveFile {
|
||||
/// Imports a Rive file from an array of bytes. Will throw
|
||||
/// [RiveFormatErrorException] if data is malformed. Will throw
|
||||
/// [RiveUnsupportedVersionException] if the version is not supported.
|
||||
factory RiveFile.import(ByteData bytes) {
|
||||
factory RiveFile.import(
|
||||
ByteData bytes, {
|
||||
RiveAssetResolver? assetResolver,
|
||||
}) {
|
||||
var reader = BinaryReader(bytes);
|
||||
return RiveFile._(reader, RuntimeHeader.read(reader));
|
||||
return RiveFile._(reader, RuntimeHeader.read(reader), assetResolver);
|
||||
}
|
||||
|
||||
/// Imports a Rive file from an asset bundle
|
||||
static Future<RiveFile> asset(String bundleKey) async {
|
||||
/// Imports a Rive file from an asset bundle. Provide [basePath] if any nested
|
||||
/// Rive asset isn't in the same path as the [bundleKey].
|
||||
static Future<RiveFile> asset(String bundleKey, {String? basePath}) async {
|
||||
final bytes = await rootBundle.load(bundleKey);
|
||||
return RiveFile.import(bytes);
|
||||
if (basePath == null) {
|
||||
int index = bundleKey.lastIndexOf('/');
|
||||
if (index != -1) {
|
||||
basePath = bundleKey.substring(0, index + 1);
|
||||
} else {
|
||||
// ignore: parameter_assignments
|
||||
basePath = '';
|
||||
}
|
||||
}
|
||||
return RiveFile.import(bytes, assetResolver: _LocalAssetResolver(basePath));
|
||||
}
|
||||
|
||||
/// Imports a Rive file from a url over http
|
||||
static Future<RiveFile> network(String url) async {
|
||||
/// Imports a Rive file from a url over http. Provide an [assetResolver] if
|
||||
/// your file contains images that needed to be loaded with separate network
|
||||
/// requests.
|
||||
static Future<RiveFile> network(String url,
|
||||
{RiveAssetResolver? assetResolver}) async {
|
||||
final res = await http.get(Uri.parse(url));
|
||||
final bytes = ByteData.view(res.bodyBytes.buffer);
|
||||
return RiveFile.import(bytes);
|
||||
return RiveFile.import(bytes, assetResolver: assetResolver);
|
||||
}
|
||||
|
||||
/// Imports a Rive file from local folder
|
||||
@ -278,3 +296,25 @@ class RiveFile {
|
||||
Artboard? artboardByName(String name) =>
|
||||
_artboards.firstWhereOrNull((a) => a.name == name);
|
||||
}
|
||||
|
||||
/// Resolves a Rive asset from the network provided a [baseUrl].
|
||||
class NetworkAssetResolver extends RiveAssetResolver {
|
||||
final String baseUrl;
|
||||
NetworkAssetResolver(this.baseUrl);
|
||||
|
||||
@override
|
||||
Future<Uint8List> loadContents(FileAsset asset) async {
|
||||
final res = await http.get(Uri.parse(baseUrl + asset.uniqueFilename));
|
||||
return Uint8List.view(res.bodyBytes.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class _LocalAssetResolver extends RiveAssetResolver {
|
||||
String basePath;
|
||||
_LocalAssetResolver(this.basePath);
|
||||
@override
|
||||
Future<Uint8List> loadContents(FileAsset asset) async {
|
||||
final bytes = await rootBundle.load(basePath + asset.uniqueFilename);
|
||||
return Uint8List.view(bytes.buffer);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user