mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-20 09:16:01 +08:00
[flutter] Added AtlasFlutter.fromMemroy, SkeletonDataFlutter.fromMemory, SkeletonDrawableFlutter.fromMemory, and SpineWidget.fromMemory. See CHANGELOG.md for details and #2939
This commit is contained in:
parent
332a001192
commit
5658eec015
@ -399,6 +399,10 @@
|
||||
|
||||
### Flutter
|
||||
|
||||
- **Additions**
|
||||
- Added `fromMemory` methods to `AtlasFlutter`, `SkeletonDataFlutter`, `SkeletonDrawableFlutter`, and `SpineWidget` for loading Spine data from custom sources (memory, encrypted storage, databases, custom caching, etc.)
|
||||
- Added example `load_from_memory.dart` demonstrating how to load all assets into memory and use the `fromMemory` API
|
||||
|
||||
- **Breaking changes**
|
||||
- Updated to use the new auto-generated Dart runtime with all the Dart API changes above
|
||||
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
# 4.3.1
|
||||
|
||||
## Flutter
|
||||
|
||||
- **Additions**
|
||||
- Added `fromMemory` methods to `AtlasFlutter`, `SkeletonDataFlutter`, `SkeletonDrawableFlutter`, and `SpineWidget` for loading Spine data from custom sources (memory, encrypted storage, databases, custom caching, etc.)
|
||||
- Added example `load_from_memory.dart` demonstrating how to load all assets into memory and use the `fromMemory` API
|
||||
|
||||
# 4.3.0
|
||||
|
||||
## Dart
|
||||
|
||||
162
spine-flutter/example/lib/load_from_memory.dart
Normal file
162
spine-flutter/example/lib/load_from_memory.dart
Normal file
@ -0,0 +1,162 @@
|
||||
//
|
||||
// Spine Runtimes License Agreement
|
||||
// Last updated April 5, 2025. Replaces all prior versions.
|
||||
//
|
||||
// Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
//
|
||||
// Integration of the Spine Runtimes into software or otherwise creating
|
||||
// derivative works of the Spine Runtimes is permitted under the terms and
|
||||
// conditions of Section 2 of the Spine Editor License Agreement:
|
||||
// http://esotericsoftware.com/spine-editor-license
|
||||
//
|
||||
// Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
// or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
// "Products"), provided that each user of the Products must obtain their own
|
||||
// Spine Editor license and redistribution of the Products in any form must
|
||||
// include this license and copyright notice.
|
||||
//
|
||||
// THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
// BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
|
||||
import 'dart:typed_data';
|
||||
import 'package:spine_flutter/spine_flutter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// This example demonstrates loading Spine skeleton data from memory using the
|
||||
/// fromMemory methods. This is useful when you want to:
|
||||
/// - Load data from custom storage (e.g., encrypted assets, databases)
|
||||
/// - Implement custom caching strategies
|
||||
/// - Download and cache assets at runtime
|
||||
/// - Pre-process assets before loading
|
||||
///
|
||||
/// The example loads all files (atlas, skeleton, images) into memory first,
|
||||
/// then uses the fromMemory API to create a SpineWidget.
|
||||
class LoadFromMemory extends StatefulWidget {
|
||||
const LoadFromMemory({super.key});
|
||||
|
||||
@override
|
||||
State<LoadFromMemory> createState() => _LoadFromMemoryState();
|
||||
}
|
||||
|
||||
class _LoadFromMemoryState extends State<LoadFromMemory> {
|
||||
// In-memory cache of all loaded files
|
||||
final Map<String, Uint8List> _fileCache = {};
|
||||
bool _isLoading = true;
|
||||
String _loadingStatus = 'Loading assets into memory...';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadAssetsIntoMemory();
|
||||
}
|
||||
|
||||
Future<void> _loadAssetsIntoMemory() async {
|
||||
try {
|
||||
// Step 1: Load atlas file into memory
|
||||
setState(() => _loadingStatus = 'Loading atlas file...');
|
||||
final atlasBytes = await rootBundle.load('assets/spineboy.atlas');
|
||||
_fileCache['assets/spineboy.atlas'] = atlasBytes.buffer.asUint8List();
|
||||
|
||||
// Step 2: Load skeleton file into memory
|
||||
setState(() => _loadingStatus = 'Loading skeleton file...');
|
||||
final skelBytes = await rootBundle.load('assets/spineboy-pro.skel');
|
||||
_fileCache['assets/spineboy-pro.skel'] = skelBytes.buffer.asUint8List();
|
||||
|
||||
// Step 3: Load image file(s) into memory
|
||||
setState(() => _loadingStatus = 'Loading image files...');
|
||||
final imageBytes = await rootBundle.load('assets/spineboy.png');
|
||||
_fileCache['assets/spineboy.png'] = imageBytes.buffer.asUint8List();
|
||||
|
||||
// All files loaded into memory!
|
||||
setState(() {
|
||||
_loadingStatus = 'All assets loaded into memory (${_fileCache.length} files, ${_getTotalSize()} bytes)';
|
||||
_isLoading = false;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_loadingStatus = 'Error loading assets: $e';
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
int _getTotalSize() {
|
||||
return _fileCache.values.fold(0, (sum, bytes) => sum + bytes.length);
|
||||
}
|
||||
|
||||
// Custom file loader that returns data from our in-memory cache
|
||||
Future<Uint8List> _loadFromCache(String filename) async {
|
||||
final data = _fileCache[filename];
|
||||
if (data == null) {
|
||||
throw Exception('File not found in cache: $filename');
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Load From Memory')),
|
||||
body: _isLoading
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 16),
|
||||
Text(_loadingStatus),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
color: Colors.blue.shade50,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Files in Memory Cache:',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
..._fileCache.entries.map((entry) => Text(
|
||||
' ${entry.key}: ${entry.value.length} bytes',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
)),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Total: ${_getTotalSize()} bytes',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: SpineWidget.fromMemory(
|
||||
'assets/spineboy.atlas',
|
||||
'assets/spineboy-pro.skel',
|
||||
_loadFromCache,
|
||||
SpineWidgetController(
|
||||
onInitialized: (controller) {
|
||||
controller.animationState.setAnimation(0, 'walk', true);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -35,6 +35,7 @@ import 'animation_state_events.dart';
|
||||
import 'dress_up.dart';
|
||||
import 'flame_example.dart';
|
||||
import 'ik_following.dart';
|
||||
import 'load_from_memory.dart';
|
||||
import 'pause_play_animation.dart';
|
||||
import 'physics.dart';
|
||||
import 'simple_animation.dart';
|
||||
@ -100,6 +101,13 @@ class ExampleSelector extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
spacer,
|
||||
ElevatedButton(
|
||||
child: const Text('Load From Memory'),
|
||||
onPressed: () {
|
||||
Navigator.push(context, MaterialPageRoute<void>(builder: (context) => const LoadFromMemory()));
|
||||
},
|
||||
),
|
||||
spacer,
|
||||
ElevatedButton(
|
||||
child: const Text('Flame: Simple Example'),
|
||||
onPressed: () {
|
||||
|
||||
@ -5,5 +5,6 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
}
|
||||
|
||||
@ -33,8 +33,37 @@ class AtlasFlutter extends Atlas {
|
||||
|
||||
AtlasFlutter._(super.ptr, this.atlasPages, this.atlasPagePaints) : super.fromPointer();
|
||||
|
||||
/// Internal method to load atlas and images
|
||||
static Future<AtlasFlutter> _load(String atlasFileName, Future<Uint8List> Function(String name) loadFile) async {
|
||||
/// Loads an [AtlasFlutter] using a custom file loading function.
|
||||
///
|
||||
/// This is the most flexible loading method that allows loading atlas data and images from any source
|
||||
/// (memory, custom storage, network with caching, etc.).
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [atlasFileName]: The path/name of the atlas file. This is passed to [loadFile] to load the atlas data.
|
||||
/// - [loadFile]: A function that takes a filename and returns the file data as [Uint8List].
|
||||
/// This function will be called once for the atlas file, and once for each atlas page image.
|
||||
/// The image paths are relative to the atlas file's directory (as specified in the atlas file).
|
||||
///
|
||||
/// Example - Loading from memory:
|
||||
/// ```dart
|
||||
/// final atlasData = Uint8List.fromList([...]); // Your atlas file data
|
||||
/// final page1Data = Uint8List.fromList([...]); // Your first page image data
|
||||
/// final page2Data = Uint8List.fromList([...]); // Your second page image data
|
||||
///
|
||||
/// final atlas = await AtlasFlutter.fromMemory(
|
||||
/// 'character.atlas',
|
||||
/// (filename) async {
|
||||
/// if (filename == 'character.atlas') return atlasData;
|
||||
/// if (filename == 'character.png') return page1Data;
|
||||
/// if (filename == 'character2.png') return page2Data;
|
||||
/// throw Exception('Unknown file: $filename');
|
||||
/// },
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// Note: The [loadFile] function receives the full relative path for images
|
||||
/// (e.g., "directory/page.png" if the atlas file specifies that path).
|
||||
static Future<AtlasFlutter> fromMemory(String atlasFileName, Future<Uint8List> Function(String name) loadFile) async {
|
||||
// Load atlas data
|
||||
final atlasBytes = await loadFile(atlasFileName);
|
||||
final atlasData = convert.utf8.decode(atlasBytes);
|
||||
@ -83,7 +112,7 @@ class AtlasFlutter extends Atlas {
|
||||
/// Loads an [AtlasFlutter] from the file [atlasFileName] in the root bundle or the optionally provided [bundle].
|
||||
static Future<AtlasFlutter> fromAsset(String atlasFileName, {AssetBundle? bundle}) async {
|
||||
bundle ??= rootBundle;
|
||||
return _load(atlasFileName, (file) async => (await bundle!.load(file)).buffer.asUint8List());
|
||||
return fromMemory(atlasFileName, (file) async => (await bundle!.load(file)).buffer.asUint8List());
|
||||
}
|
||||
|
||||
/// Loads an [AtlasFlutter] from the file [atlasFileName].
|
||||
@ -91,12 +120,12 @@ class AtlasFlutter extends Atlas {
|
||||
if (kIsWeb) {
|
||||
throw UnsupportedError('File operations are not supported on web. Use fromAsset or fromHttp instead.');
|
||||
}
|
||||
return _load(atlasFileName, (file) => File(file).readAsBytes());
|
||||
return fromMemory(atlasFileName, (file) => File(file).readAsBytes());
|
||||
}
|
||||
|
||||
/// Loads an [AtlasFlutter] from the URL [atlasURL].
|
||||
static Future<AtlasFlutter> fromHttp(String atlasURL) async {
|
||||
return _load(atlasURL, (file) async {
|
||||
return fromMemory(atlasURL, (file) async {
|
||||
final response = await http.get(Uri.parse(file));
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Failed to load $file: ${response.statusCode}');
|
||||
@ -122,21 +151,54 @@ class AtlasFlutter extends Atlas {
|
||||
class SkeletonDataFlutter extends SkeletonData {
|
||||
SkeletonDataFlutter._(super.ptr) : super.fromPointer();
|
||||
|
||||
/// Loads a [SkeletonDataFlutter] using a custom file loading function.
|
||||
///
|
||||
/// This is the most flexible loading method that allows loading skeleton data from any source
|
||||
/// (memory, custom storage, network with caching, etc.).
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [atlas]: The [AtlasFlutter] to use for resolving attachment images.
|
||||
/// - [skeletonFile]: The path/name of the skeleton file. This is passed to [loadFile] to load the skeleton data.
|
||||
/// - [loadFile]: A function that takes a filename and returns the file data as [Uint8List].
|
||||
///
|
||||
/// Example - Loading from memory:
|
||||
/// ```dart
|
||||
/// final skeletonData = Uint8List.fromList([...]); // Your skeleton file data
|
||||
///
|
||||
/// final skeleton = await SkeletonDataFlutter.fromMemory(
|
||||
/// atlas,
|
||||
/// 'character.json',
|
||||
/// (filename) async {
|
||||
/// if (filename == 'character.json') return skeletonData;
|
||||
/// throw Exception('Unknown file: $filename');
|
||||
/// },
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// Throws an [Exception] in case the skeleton data could not be loaded.
|
||||
static Future<SkeletonDataFlutter> fromMemory(
|
||||
AtlasFlutter atlas,
|
||||
String skeletonFile,
|
||||
Future<Uint8List> Function(String name) loadFile,
|
||||
) async {
|
||||
final fileData = await loadFile(skeletonFile);
|
||||
if (skeletonFile.endsWith(".json")) {
|
||||
final jsonData = convert.utf8.decode(fileData);
|
||||
final skeletonData = loadSkeletonDataJson(atlas, jsonData, path: skeletonFile);
|
||||
return SkeletonDataFlutter._(skeletonData.nativePtr.cast());
|
||||
} else {
|
||||
final skeletonData = loadSkeletonDataBinary(atlas, fileData, path: skeletonFile);
|
||||
return SkeletonDataFlutter._(skeletonData.nativePtr.cast());
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads a [SkeletonDataFlutter] from the file [skeletonFile] in the root bundle or the optionally provided [bundle].
|
||||
/// Uses the provided [atlasFlutter] to resolve attachment images.
|
||||
///
|
||||
/// Throws an [Exception] in case the skeleton data could not be loaded.
|
||||
static Future<SkeletonDataFlutter> fromAsset(AtlasFlutter atlas, String skeletonFile, {AssetBundle? bundle}) async {
|
||||
bundle ??= rootBundle;
|
||||
if (skeletonFile.endsWith(".json")) {
|
||||
final jsonData = await bundle.loadString(skeletonFile);
|
||||
final skeletonData = loadSkeletonDataJson(atlas, jsonData, path: skeletonFile);
|
||||
return SkeletonDataFlutter._(skeletonData.nativePtr.cast());
|
||||
} else {
|
||||
final binaryData = (await bundle.load(skeletonFile)).buffer.asUint8List();
|
||||
final skeletonData = loadSkeletonDataBinary(atlas, binaryData, path: skeletonFile);
|
||||
return SkeletonDataFlutter._(skeletonData.nativePtr.cast());
|
||||
}
|
||||
return fromMemory(atlas, skeletonFile, (file) async => (await bundle!.load(file)).buffer.asUint8List());
|
||||
}
|
||||
|
||||
/// Loads a [SkeletonDataFlutter] from the file [skeletonFile]. Uses the provided [atlasFlutter] to resolve attachment images.
|
||||
@ -146,36 +208,20 @@ class SkeletonDataFlutter extends SkeletonData {
|
||||
if (kIsWeb) {
|
||||
throw UnsupportedError('File operations are not supported on web. Use fromAsset or fromHttp instead.');
|
||||
}
|
||||
if (skeletonFile.endsWith(".json")) {
|
||||
final jsonData = await File(skeletonFile).readAsString();
|
||||
final skeletonData = loadSkeletonDataJson(atlasFlutter, jsonData, path: skeletonFile);
|
||||
return SkeletonDataFlutter._(skeletonData.nativePtr.cast());
|
||||
} else {
|
||||
final binaryData = await File(skeletonFile).readAsBytes();
|
||||
final skeletonData = loadSkeletonDataBinary(atlasFlutter, binaryData, path: skeletonFile);
|
||||
return SkeletonDataFlutter._(skeletonData.nativePtr.cast());
|
||||
}
|
||||
return fromMemory(atlasFlutter, skeletonFile, (file) => File(file).readAsBytes());
|
||||
}
|
||||
|
||||
/// Loads a [SkeletonDataFlutter] from the URL [skeletonURL]. Uses the provided [atlasFlutter] to resolve attachment images.
|
||||
///
|
||||
/// Throws an [Exception] in case the skeleton data could not be loaded.
|
||||
static Future<SkeletonDataFlutter> fromHttp(AtlasFlutter atlasFlutter, String skeletonURL) async {
|
||||
if (skeletonURL.endsWith(".json")) {
|
||||
final response = await http.get(Uri.parse(skeletonURL));
|
||||
return fromMemory(atlasFlutter, skeletonURL, (file) async {
|
||||
final response = await http.get(Uri.parse(file));
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Failed to load skeleton from $skeletonURL: ${response.statusCode}');
|
||||
}
|
||||
final skeletonData = loadSkeletonDataJson(atlasFlutter, response.body, path: skeletonURL);
|
||||
return SkeletonDataFlutter._(skeletonData.nativePtr.cast());
|
||||
} else {
|
||||
final response = await http.get(Uri.parse(skeletonURL));
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Failed to load skeleton from $skeletonURL: ${response.statusCode}');
|
||||
}
|
||||
final skeletonData = loadSkeletonDataBinary(atlasFlutter, response.bodyBytes, path: skeletonURL);
|
||||
return SkeletonDataFlutter._(skeletonData.nativePtr.cast());
|
||||
throw Exception('Failed to load $file: ${response.statusCode}');
|
||||
}
|
||||
return response.bodyBytes;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,6 +358,46 @@ class SkeletonDrawableFlutter extends SkeletonDrawable {
|
||||
/// In that case a call to [dispose] will also dispose the atlas and skeleton data.
|
||||
SkeletonDrawableFlutter(this.atlasFlutter, this.skeletonData, this._ownsAtlasAndSkeletonData) : super(skeletonData);
|
||||
|
||||
/// Constructs a new skeleton drawable using a custom file loading function.
|
||||
///
|
||||
/// This is the most flexible loading method that allows loading atlas and skeleton data from any source
|
||||
/// (memory, custom storage, network with caching, etc.).
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [atlasFile]: The path/name of the atlas file. This is passed to [loadFile] to load the atlas data.
|
||||
/// - [skeletonFile]: The path/name of the skeleton file. This is passed to [loadFile] to load the skeleton data.
|
||||
/// - [loadFile]: A function that takes a filename and returns the file data as [Uint8List].
|
||||
/// This function will be called for the atlas file, skeleton file, and each atlas page image.
|
||||
///
|
||||
/// Example - Loading from memory:
|
||||
/// ```dart
|
||||
/// final atlasData = Uint8List.fromList([...]); // Your atlas file data
|
||||
/// final skeletonData = Uint8List.fromList([...]); // Your skeleton file data
|
||||
/// final imageData = Uint8List.fromList([...]); // Your image file data
|
||||
///
|
||||
/// final drawable = await SkeletonDrawableFlutter.fromMemory(
|
||||
/// 'character.atlas',
|
||||
/// 'character.json',
|
||||
/// (filename) async {
|
||||
/// if (filename == 'character.atlas') return atlasData;
|
||||
/// if (filename == 'character.json') return skeletonData;
|
||||
/// if (filename == 'character.png') return imageData;
|
||||
/// throw Exception('Unknown file: $filename');
|
||||
/// },
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// Throws an exception in case the data could not be loaded.
|
||||
static Future<SkeletonDrawableFlutter> fromMemory(
|
||||
String atlasFile,
|
||||
String skeletonFile,
|
||||
Future<Uint8List> Function(String name) loadFile,
|
||||
) async {
|
||||
final atlasFlutter = await AtlasFlutter.fromMemory(atlasFile, loadFile);
|
||||
final skeletonDataFlutter = await SkeletonDataFlutter.fromMemory(atlasFlutter, skeletonFile, loadFile);
|
||||
return SkeletonDrawableFlutter(atlasFlutter, skeletonDataFlutter, true);
|
||||
}
|
||||
|
||||
/// Constructs a new skeleton drawable from the [atlasFile] and [skeletonFile] from the root asset bundle
|
||||
/// or the optionally provided [bundle].
|
||||
///
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
//
|
||||
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/rendering.dart' as rendering;
|
||||
import 'package:flutter/scheduler.dart';
|
||||
@ -164,7 +165,7 @@ class SpineWidgetController {
|
||||
}
|
||||
}
|
||||
|
||||
enum _AssetType { asset, file, http, drawable }
|
||||
enum _AssetType { asset, file, http, memory, drawable }
|
||||
|
||||
/// Base class for bounds providers. A bounds provider calculates the axis aligned bounding box
|
||||
/// used to scale and fit a skeleton inside the bounds of a [SpineWidget].
|
||||
@ -262,7 +263,8 @@ class SkinAndAnimationBounds extends BoundsProvider {
|
||||
}
|
||||
|
||||
/// A [StatefulWidget] to display a Spine skeleton. The skeleton can be loaded from an asset bundle ([SpineWidget.fromAsset],
|
||||
/// local files [SpineWidget.fromFile], URLs [SpineWidget.fromHttp], or a pre-loaded [SkeletonDrawableFlutter] ([SpineWidget.fromDrawable]).
|
||||
/// local files [SpineWidget.fromFile], URLs [SpineWidget.fromHttp], memory ([SpineWidget.fromMemory]), or a pre-loaded
|
||||
/// [SkeletonDrawableFlutter] ([SpineWidget.fromDrawable]).
|
||||
///
|
||||
/// The skeleton displayed by a `SpineWidget` can be controlled via a [SpineWidgetController].
|
||||
///
|
||||
@ -274,6 +276,7 @@ class SpineWidget extends StatefulWidget {
|
||||
final String? _skeletonFile;
|
||||
final String? _atlasFile;
|
||||
final SkeletonDrawableFlutter? _drawable;
|
||||
final Future<Uint8List> Function(String)? _loadFile;
|
||||
final SpineWidgetController _controller;
|
||||
final BoxFit _fit;
|
||||
final Alignment _alignment;
|
||||
@ -308,6 +311,7 @@ class SpineWidget extends StatefulWidget {
|
||||
_boundsProvider = boundsProvider ?? const SetupPoseBounds(),
|
||||
_sizedByBounds = sizedByBounds ?? false,
|
||||
_drawable = null,
|
||||
_loadFile = null,
|
||||
_bundle = bundle ?? rootBundle;
|
||||
|
||||
/// Constructs a new [SpineWidget] from files. The [_atlasFile] specifies the `.atlas` file to be loaded for the images used to render
|
||||
@ -336,7 +340,8 @@ class SpineWidget extends StatefulWidget {
|
||||
_alignment = alignment ?? Alignment.center,
|
||||
_boundsProvider = boundsProvider ?? const SetupPoseBounds(),
|
||||
_sizedByBounds = sizedByBounds ?? false,
|
||||
_drawable = null;
|
||||
_drawable = null,
|
||||
_loadFile = null;
|
||||
|
||||
/// Constructs a new [SpineWidget] from HTTP URLs. The [_atlasFile] specifies the `.atlas` file to be loaded for the images used to render
|
||||
/// the skeleton. The [_skeletonFile] specifies either a Skeleton `.json` or `.skel` file containing the skeleton data.
|
||||
@ -359,6 +364,38 @@ class SpineWidget extends StatefulWidget {
|
||||
bool? sizedByBounds,
|
||||
super.key,
|
||||
}) : _assetType = _AssetType.http,
|
||||
_bundle = null,
|
||||
_fit = fit ?? BoxFit.contain,
|
||||
_alignment = alignment ?? Alignment.center,
|
||||
_boundsProvider = boundsProvider ?? const SetupPoseBounds(),
|
||||
_sizedByBounds = sizedByBounds ?? false,
|
||||
_drawable = null,
|
||||
_loadFile = null;
|
||||
|
||||
/// Constructs a new [SpineWidget] using a custom file loading function.
|
||||
///
|
||||
/// This is the most flexible loading method that allows loading skeleton data from any source
|
||||
/// (memory, custom storage, network with caching, etc.).
|
||||
///
|
||||
/// After initialization is complete, the provided [_controller] is invoked as per the [SpineWidgetController] semantics, to allow
|
||||
/// modifying how the skeleton inside the widget is animated and rendered.
|
||||
///
|
||||
/// The skeleton is fitted and aligned inside the widget as per the [fit] and [alignment] arguments. For this purpose, the skeleton
|
||||
/// bounds must be computed via a [BoundsProvider]. By default, [BoxFit.contain], [Alignment.center], and a [SetupPoseBounds] provider
|
||||
/// are used.
|
||||
///
|
||||
/// The widget can optionally by sized by the bounds provided by the [BoundsProvider] by passing `true` for [sizedByBounds].
|
||||
const SpineWidget.fromMemory(
|
||||
this._atlasFile,
|
||||
this._skeletonFile,
|
||||
this._loadFile,
|
||||
this._controller, {
|
||||
BoxFit? fit,
|
||||
Alignment? alignment,
|
||||
BoundsProvider? boundsProvider,
|
||||
bool? sizedByBounds,
|
||||
super.key,
|
||||
}) : _assetType = _AssetType.memory,
|
||||
_bundle = null,
|
||||
_fit = fit ?? BoxFit.contain,
|
||||
_alignment = alignment ?? Alignment.center,
|
||||
@ -391,7 +428,8 @@ class SpineWidget extends StatefulWidget {
|
||||
_boundsProvider = boundsProvider ?? const SetupPoseBounds(),
|
||||
_sizedByBounds = sizedByBounds ?? false,
|
||||
_skeletonFile = null,
|
||||
_atlasFile = null;
|
||||
_atlasFile = null,
|
||||
_loadFile = null;
|
||||
|
||||
@override
|
||||
State<SpineWidget> createState() => _SpineWidgetState();
|
||||
@ -459,6 +497,9 @@ class _SpineWidgetState extends State<SpineWidget> {
|
||||
case _AssetType.http:
|
||||
loadDrawable(await SkeletonDrawableFlutter.fromHttp(atlasFile, skeletonFile));
|
||||
break;
|
||||
case _AssetType.memory:
|
||||
loadDrawable(await SkeletonDrawableFlutter.fromMemory(atlasFile, skeletonFile, widget._loadFile!));
|
||||
break;
|
||||
case _AssetType.drawable:
|
||||
throw Exception("Drawable can not be loaded via loadFromAsset().");
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
name: spine_flutter
|
||||
description: The official Spine Flutter Runtime to load, display and interact with Spine animations.
|
||||
version: 4.3.0
|
||||
version: 4.3.1
|
||||
homepage: https://esotericsoftware.com
|
||||
repository: https://github.com/esotericsoftware/spine-runtimes
|
||||
issue_tracker: https://github.com/esotericsoftware/spine-runtimes/issues
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user