From 7bb1d65bfe147ceeb84fd246d2938e6388ebac58 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 17 Nov 2022 11:35:36 +0100 Subject: [PATCH] [flutter] Fix dependency cycle with widget->drawable, refactor examples. --- .../example/lib/animation_state_events.dart | 45 ++++ spine-flutter/example/lib/dress_up.dart | 51 +++++ spine-flutter/example/lib/main.dart | 203 ++---------------- .../example/lib/pause_play_animation.dart | 43 ++++ .../example/lib/simple_animation.dart | 23 ++ spine-flutter/example/lib/skins.dart | 79 +++++++ spine-flutter/lib/spine_flutter.dart | 43 +++- spine-flutter/lib/spine_widget.dart | 160 +++++++------- 8 files changed, 374 insertions(+), 273 deletions(-) create mode 100644 spine-flutter/example/lib/animation_state_events.dart create mode 100644 spine-flutter/example/lib/dress_up.dart create mode 100644 spine-flutter/example/lib/pause_play_animation.dart create mode 100644 spine-flutter/example/lib/simple_animation.dart create mode 100644 spine-flutter/example/lib/skins.dart diff --git a/spine-flutter/example/lib/animation_state_events.dart b/spine-flutter/example/lib/animation_state_events.dart new file mode 100644 index 000000000..770cdf077 --- /dev/null +++ b/spine-flutter/example/lib/animation_state_events.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:spine_flutter/spine_flutter.dart'; + +class AnimationStateEvents extends StatelessWidget { + const AnimationStateEvents({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + reportLeaks(); + final controller = SpineWidgetController((controller) { + for (final bone in controller.skeleton.getBones()) { + print(bone); + } + controller.skeleton.setScaleX(0.5); + controller.skeleton.setScaleY(0.5); + controller.skeleton.findSlot("gun")?.setColor(Color(1, 0, 0, 1)); + controller.animationStateData.setDefaultMix(0.2); + controller.animationState.setAnimationByName(0, "walk", true).setListener((type, trackEntry, event) { + print("Walk animation event $type"); + }); + controller.animationState.addAnimationByName(0, "jump", false, 2); + controller.animationState.addAnimationByName(0, "run", true, 0).setListener((type, trackEntry, event) { + print("Run animation event $type"); + }); + controller.animationState.setListener((type, trackEntry, event) { + if (type == EventType.Event) { + print("User event: { name: ${event?.getData().getName()}, intValue: ${event?.getIntValue()}, floatValue: intValue: ${event?.getFloatValue()}, stringValue: ${event?.getStringValue()} }"); + } + }); + print("Current: ${controller.animationState.getCurrent(0)?.getAnimation().getName()}"); + }); + + return Scaffold( + appBar: AppBar(title: const Text('Spineboy')), + body: Column( + children: [ + const Text("See output in console!"), + Expanded( + child: SpineWidget.asset("assets/spineboy-pro.skel", "assets/spineboy.atlas", controller) + ) + ] + ) + ); + } +} \ No newline at end of file diff --git a/spine-flutter/example/lib/dress_up.dart b/spine-flutter/example/lib/dress_up.dart new file mode 100644 index 000000000..6ba544ff0 --- /dev/null +++ b/spine-flutter/example/lib/dress_up.dart @@ -0,0 +1,51 @@ +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:spine_flutter/spine_flutter.dart'; + +class DressUp extends StatefulWidget { + const DressUp({Key? key}) : super(key: key); + + @override + DressUpState createState() => DressUpState(); +} + +class DressUpState extends State { + static const double thumbnailSize = 200; + SkeletonDrawable? _drawable; + final List _skinImages = []; + + @override + void initState() { + super.initState(); + SkeletonDrawable.fromAsset("assets/mix-and-match-pro.skel", "assets/mix-and-match.atlas").then((drawable) async { + for (var skin in drawable.skeletonData.getSkins()) { + var recorder = ui.PictureRecorder(); + var canvas = Canvas(recorder, const Rect.fromLTWH(0, 0, thumbnailSize, thumbnailSize)); + canvas.drawRect(Rect(0, 0, 200, 200), ColorP) + var imageData = await (await recorder.endRecording().toImage(thumbnailSize.toInt(), thumbnailSize.toInt())).toByteData(format: ui.ImageByteFormat.png); + _skinImages.add(Image.memory(imageData!.buffer.asUint8List())); + } + _drawable = drawable; + setState(() {}); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Skins')), + body: _skinImages.isEmpty + ? const SizedBox() + : Row( + children: [ + Expanded( + child:ListView( + children: _skinImages + ) + ), + ] + ) + ); + } +} \ No newline at end of file diff --git a/spine-flutter/example/lib/main.dart b/spine-flutter/example/lib/main.dart index 42c50a764..c6bf69a65 100644 --- a/spine-flutter/example/lib/main.dart +++ b/spine-flutter/example/lib/main.dart @@ -1,8 +1,14 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:spine_flutter/spine_flutter.dart'; + +import 'simple_animation.dart'; +import 'animation_state_events.dart'; +import 'pause_play_animation.dart'; +import 'skins.dart'; +import 'dress_up.dart'; class ExampleSelector extends StatelessWidget { + const ExampleSelector({super.key}); + @override Widget build(BuildContext context) { const spacer = SizedBox(height: 10); @@ -59,6 +65,18 @@ class ExampleSelector extends StatelessWidget { ); }, ), + spacer, + ElevatedButton( + child: const Text('Skins'), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const DressUp(), + ), + ); + }, + ), spacer ] ) @@ -67,187 +85,8 @@ class ExampleSelector extends StatelessWidget { } } -class SimpleAnimation extends StatelessWidget { - const SimpleAnimation({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - reportLeaks(); - final controller = SpineWidgetController((controller) { - // Set the walk animation on track 0, let it loop - controller.animationState?.setAnimationByName(0, "walk", true); - }); - - return Scaffold( - appBar: AppBar(title: const Text('Simple Animation')), - body: SpineWidget.asset("assets/spineboy-pro.skel", "assets/spineboy.atlas", controller), - // body: SpineWidget.file("/Users/badlogic/workspaces/spine-runtimes/examples/spineboy/export/spineboy-pro.skel", "/Users/badlogic/workspaces/spine-runtimes/examples/spineboy/export/spineboy.atlas", controller), - // body: const SpineWidget.http("https://marioslab.io/dump/spineboy/spineboy-pro.json", "https://marioslab.io/dump/spineboy/spineboy.atlas"), - // body: SpineWidget.asset("assets/skeleton.json", "assets/skeleton.atlas", controller, alignment: Alignment.topLeft, fit: BoxFit.cover), - ); - } -} - -class PlayPauseAnimation extends StatefulWidget { - const PlayPauseAnimation({Key? key}) : super(key: key); - - @override - PlayPauseAnimationState createState() => PlayPauseAnimationState(); -} - -class PlayPauseAnimationState extends State { - late SpineWidgetController _controller; - - @override - void initState() { - super.initState(); - _controller = SpineWidgetController((controller) { - controller.animationState?.setAnimationByName(0, "walk", true); - }); - } - - void _togglePlaystate() { - _controller.togglePlay(); - setState(() {}); - } - - @override - Widget build(BuildContext context) { - reportLeaks(); - - return Scaffold( - appBar: AppBar(title: const Text('Play/Pause')), - body: SpineWidget.asset("assets/spineboy-pro.skel", "assets/spineboy.atlas", _controller), - floatingActionButton: FloatingActionButton( - onPressed: _togglePlaystate, - child: Icon(_controller.isPlaying ? Icons.pause : Icons.play_arrow), - ), - ); - } -} - -class Skins extends StatefulWidget { - const Skins({Key? key}) : super(key: key); - - @override - SkinsState createState() => SkinsState(); -} - -class SkinsState extends State { - Atlas? _atlas; - SkeletonData? _skeletonData; - final Map _availableSkins = {}; - Skin? _customSkin; - late SpineWidgetController _controller; - - @override - void initState() { - super.initState(); - - Atlas.fromAsset(rootBundle, "assets/mix-and-match.atlas").then((atlas) async { - _skeletonData = await SkeletonData.fromAsset(atlas, rootBundle, "assets/mix-and-match-pro.skel"); - _atlas = atlas; - for (var skin in _skeletonData?.getSkins() ?? []) { - _availableSkins[skin.getName()] = false; - } - - _controller = SpineWidgetController((controller) { - controller.animationState?.setAnimationByName(0, "walk", true); - }); - - setState(() => _toggleSkin("full-skins/girl")); - }); - } - - void _toggleSkin(String skinName) { - _availableSkins[skinName] = !_availableSkins[skinName]!; - - if (_customSkin != null) { - _customSkin?.dispose(); - _customSkin = null; - } - - _customSkin = Skin("custom-skin"); - for (var skinName in _availableSkins.keys) { - if (_availableSkins[skinName] == true) { - var skin = _controller.skeletonData?.findSkin(skinName); - if (skin != null) - _customSkin?.addSkin(skin); - } - } - _controller.skeleton?.setSkin(_customSkin!); - _controller.skeleton?.setToSetupPose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Skins')), - body: _skeletonData == null - ? SizedBox() - : Row( - children: [ - Expanded( - child:ListView( - children: _availableSkins.keys.map((skinName) { - return CheckboxListTile( - title: Text(skinName), - value: _availableSkins[skinName], - onChanged: (bool? value) { - _toggleSkin(skinName); - setState(() => {}); - }, - ); - }).toList() - ) - ), - Expanded( - child: SpineWidget.raw(_skeletonData, _atlas, _controller, boundsProvider: SkinAndAnimationBounds(["full-skins/girl"])) - ) - ] - ) - ); - } -} - -class AnimationStateEvents extends StatelessWidget { - const AnimationStateEvents({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - reportLeaks(); - final controller = SpineWidgetController((controller) { - for (final bone in controller.skeleton!.getBones()) { - print(bone); - } - controller.skeleton?.setScaleX(0.5); - controller.skeleton?.setScaleY(0.5); - controller.skeleton?.findSlot("gun")?.setColor(Color(1, 0, 0, 1)); - controller.animationStateData?.setDefaultMix(0.2); - controller.animationState?.setAnimationByName(0, "walk", true)?.setListener((type, trackEntry, event) { - print("Walk animation event ${type}"); - }); - controller.animationState?.addAnimationByName(0, "jump", false, 2); - controller.animationState?.addAnimationByName(0, "run", true, 0)?.setListener((type, trackEntry, event) { - print("Run animation event ${type}"); - }); - controller.animationState?.setListener((type, trackEntry, event) { - if (type == EventType.Event) { - print("User event: { name: ${event?.getData().getName()}, intValue: ${event?.getIntValue()}, floatValue: intValue: ${event?.getFloatValue()}, stringValue: ${event?.getStringValue()} }"); - } - }); - print("Current: ${controller.animationState?.getCurrent(0)?.getAnimation().getName()}"); - }); - - return Scaffold( - appBar: AppBar(title: const Text('Spineboy')), - body: SpineWidget.asset("assets/spineboy-pro.skel", "assets/spineboy.atlas", controller), - ); - } -} - void main() { - runApp(MaterialApp( + runApp(const MaterialApp( title: "Spine Examples", home: ExampleSelector() )); diff --git a/spine-flutter/example/lib/pause_play_animation.dart b/spine-flutter/example/lib/pause_play_animation.dart new file mode 100644 index 000000000..651e948b2 --- /dev/null +++ b/spine-flutter/example/lib/pause_play_animation.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:spine_flutter/spine_flutter.dart'; + +class PlayPauseAnimation extends StatefulWidget { + const PlayPauseAnimation({Key? key}) : super(key: key); + + @override + PlayPauseAnimationState createState() => PlayPauseAnimationState(); +} + +class PlayPauseAnimationState extends State { + late SpineWidgetController controller; + late bool isPlaying; + + @override + void initState() { + super.initState(); + controller = SpineWidgetController((controller) { + controller.animationState.setAnimationByName(0, "walk", true); + }); + isPlaying = true; + } + + void _togglePlay() { + isPlaying = !isPlaying; + controller.animationState.setTimeScale(isPlaying ? 1 : 0); + setState(() {}); + } + + @override + Widget build(BuildContext context) { + reportLeaks(); + + return Scaffold( + appBar: AppBar(title: const Text('Play/Pause')), + body: SpineWidget.asset("assets/spineboy-pro.skel", "assets/spineboy.atlas", controller), + floatingActionButton: FloatingActionButton( + onPressed: _togglePlay, + child: Icon(isPlaying ? Icons.pause : Icons.play_arrow), + ), + ); + } +} \ No newline at end of file diff --git a/spine-flutter/example/lib/simple_animation.dart b/spine-flutter/example/lib/simple_animation.dart new file mode 100644 index 000000000..a3c00af88 --- /dev/null +++ b/spine-flutter/example/lib/simple_animation.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:spine_flutter/spine_flutter.dart'; + +class SimpleAnimation extends StatelessWidget { + const SimpleAnimation({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + reportLeaks(); + final controller = SpineWidgetController((controller) { + // Set the walk animation on track 0, let it loop + controller.animationState.setAnimationByName(0, "walk", true); + }); + + return Scaffold( + appBar: AppBar(title: const Text('Simple Animation')), + body: SpineWidget.asset("assets/spineboy-pro.skel", "assets/spineboy.atlas", controller), + // body: SpineWidget.file("/Users/badlogic/workspaces/spine-runtimes/examples/spineboy/export/spineboy-pro.skel", "/Users/badlogic/workspaces/spine-runtimes/examples/spineboy/export/spineboy.atlas", controller), + // body: const SpineWidget.http("https://marioslab.io/dump/spineboy/spineboy-pro.json", "https://marioslab.io/dump/spineboy/spineboy.atlas"), + // body: SpineWidget.asset("assets/skeleton.json", "assets/skeleton.atlas", controller, alignment: Alignment.topLeft, fit: BoxFit.cover), + ); + } +} \ No newline at end of file diff --git a/spine-flutter/example/lib/skins.dart b/spine-flutter/example/lib/skins.dart new file mode 100644 index 000000000..f9d2142f9 --- /dev/null +++ b/spine-flutter/example/lib/skins.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:spine_flutter/spine_flutter.dart'; + +class Skins extends StatefulWidget { + const Skins({Key? key}) : super(key: key); + + @override + SkinsState createState() => SkinsState(); +} + +class SkinsState extends State { + SkeletonDrawable? _drawable; + late SpineWidgetController _controller; + final Map _selectedSkins = {}; + Skin? _customSkin; + + @override + void initState() { + super.initState(); + SkeletonDrawable.fromAsset("assets/mix-and-match-pro.skel", "assets/mix-and-match.atlas").then((drawable) { + for (var skin in drawable.skeletonData.getSkins()) { + _selectedSkins[skin.getName()] = false; + } + _controller = SpineWidgetController((controller) { + controller.animationState.setAnimationByName(0, "walk", true); + }); + drawable.skeleton.setSkinByName("full-skins/girl"); + _selectedSkins["full-skins/girl"] = true; + _drawable = drawable; + setState(() {}); + }); + } + + void _toggleSkin(String skinName) { + _selectedSkins[skinName] = !_selectedSkins[skinName]!; + + if (_customSkin != null) _customSkin?.dispose(); + + _customSkin = Skin("custom-skin"); + for (var skinName in _selectedSkins.keys) { + if (_selectedSkins[skinName] == true) { + var skin = _controller.skeletonData.findSkin(skinName); + if (skin != null) _customSkin?.addSkin(skin); + } + } + _controller.skeleton.setSkin(_customSkin!); + _controller.skeleton.setToSetupPose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Skins')), + body: _drawable == null + ? const SizedBox() + : Row( + children: [ + Expanded( + child:ListView( + children: _selectedSkins.keys.map((skinName) { + return CheckboxListTile( + title: Text(skinName), + value: _selectedSkins[skinName], + onChanged: (bool? value) { + _toggleSkin(skinName); + setState(() => {}); + }, + ); + }).toList() + ) + ), + Expanded( + child: SpineWidget.drawable(_drawable, _controller, boundsProvider: SkinAndAnimationBounds(["full-skins/girl"])) + ) + ] + ) + ); + } +} \ No newline at end of file diff --git a/spine-flutter/lib/spine_flutter.dart b/spine-flutter/lib/spine_flutter.dart index 852928f3d..f5617c6a7 100644 --- a/spine-flutter/lib/spine_flutter.dart +++ b/spine-flutter/lib/spine_flutter.dart @@ -143,11 +143,27 @@ class SkeletonData { return SkeletonData._(result.skeletonData); } - static Future fromAsset(Atlas atlas, AssetBundle assetBundle, String skeletonFileName) async { - if (skeletonFileName.endsWith(".json")) { - return fromJson(atlas, await assetBundle.loadString(skeletonFileName)); + static Future fromAsset(AssetBundle assetBundle, Atlas atlas, String skeletonFile) async { + if (skeletonFile.endsWith(".json")) { + return fromJson(atlas, await assetBundle.loadString(skeletonFile)); } else { - return fromBinary(atlas, (await assetBundle.load(skeletonFileName)).buffer.asUint8List()); + return fromBinary(atlas, (await assetBundle.load(skeletonFile)).buffer.asUint8List()); + } + } + + static Future fromFile(Atlas atlas, String skeletonFile) async { + if (skeletonFile.endsWith(".json")) { + return fromJson(atlas, convert.utf8.decode(await File(skeletonFile).readAsBytes())); + } else { + return fromBinary(atlas, await File(skeletonFile).readAsBytes()); + } + } + + static Future fromHttp(Atlas atlas, String skeletonFile) async { + if (skeletonFile.endsWith(".json")) { + return fromJson(atlas, convert.utf8.decode((await http.get(Uri.parse(skeletonFile))).bodyBytes)); + } else { + return fromBinary(atlas, (await http.get(Uri.parse(skeletonFile))).bodyBytes); } } @@ -3165,6 +3181,25 @@ class SkeletonDrawable { animationState = AnimationState._(_drawable.ref.animationState, _drawable.ref.animationStateEvents); } + + static Future fromAsset(String skeletonFile, String atlasFile) async { + var atlas = await Atlas.fromAsset(rootBundle, atlasFile); + var skeletonData = await SkeletonData.fromAsset(rootBundle, atlas, skeletonFile); + return SkeletonDrawable(atlas, skeletonData, true); + } + + static Future fromFile(String skeletonFile, String atlasFile) async { + var atlas = await Atlas.fromFile(atlasFile); + var skeletonData = await SkeletonData.fromFile(atlas, skeletonFile); + return SkeletonDrawable(atlas, skeletonData, true); + } + + static Future fromHttp(String skeletonFile, String atlasFile) async { + var atlas = await Atlas.fromUrl(atlasFile); + var skeletonData = await SkeletonData.fromHttp(atlas, skeletonFile); + return SkeletonDrawable(atlas, skeletonData, true); + } + void update(double delta) { if (_disposed) return; animationState.update(delta); diff --git a/spine-flutter/lib/spine_widget.dart b/spine-flutter/lib/spine_widget.dart index 0975ce374..68692392e 100644 --- a/spine-flutter/lib/spine_widget.dart +++ b/spine-flutter/lib/spine_widget.dart @@ -11,50 +11,51 @@ import 'package:http/http.dart' as http; import 'spine_flutter.dart'; class SpineWidgetController { - Atlas? _atlas; - SkeletonData? _data; SkeletonDrawable? _drawable; final void Function(SpineWidgetController controller)? onInitialized; bool initialized = false; SpineWidgetController([this.onInitialized]); - void _initialize(Atlas atlas, SkeletonData data, SkeletonDrawable drawable) { - if (initialized) - throw Exception("SpineWidgetController already initialized. A controller can only be used with one widget."); - _atlas = atlas; - _data = data; + void _initialize(SkeletonDrawable drawable) { + if (_drawable != null) throw Exception("SpineWidgetController already initialized. A controller can only be used with one widget."); _drawable = drawable; - onInitialized?.call(this); initialized = true; + onInitialized?.call(this); } - Atlas? get atlas => _atlas; - - SkeletonData? get skeletonData => _data; - - AnimationStateData? get animationStateData => _drawable?.animationStateData; - - AnimationState? get animationState => _drawable?.animationState; - - Skeleton? get skeleton => _drawable?.skeleton; - - void pause() { - _drawable?.animationState.setTimeScale(0); + Atlas get atlas { + if (_drawable == null) throw Exception("Controller is not initialized yet."); + return _drawable!.atlas; } - void play() { - _drawable?.animationState.setTimeScale(1); + SkeletonData get skeletonData { + if (_drawable == null) throw Exception("Controller is not initialized yet."); + return _drawable!.skeletonData; } - void togglePlay() { - _drawable?.animationState.setTimeScale(isPlaying ? 0 : 1); + AnimationStateData get animationStateData { + if (_drawable == null) throw Exception("Controller is not initialized yet."); + return _drawable!.animationStateData; } - bool get isPlaying => _drawable?.animationState.getTimeScale() != 0; + AnimationState get animationState { + if (_drawable == null) throw Exception("Controller is not initialized yet."); + return _drawable!.animationState; + } + + Skeleton get skeleton { + if (_drawable == null) throw Exception("Controller is not initialized yet."); + return _drawable!.skeleton; + } + + SkeletonDrawable get drawable { + if (_drawable == null) throw Exception("Controller is not initialized yet."); + return _drawable!; + } } -enum AssetType { Asset, File, Http, Raw } +enum AssetType { Asset, File, Http, Drawable } abstract class BoundsProvider { const BoundsProvider(); @@ -122,51 +123,47 @@ class ComputedBounds extends BoundsProvider { class SpineWidget extends StatefulWidget { final AssetType _assetType; - final String? skeletonFile; - final String? atlasFile; - final SkeletonData? skeletonData; - final Atlas? atlas; - final SpineWidgetController controller; - final BoxFit fit; - final Alignment alignment; - final BoundsProvider boundsProvider; - final bool sizedByBounds; + final String? _skeletonFile; + final String? _atlasFile; + final SkeletonDrawable? _drawable; + final SpineWidgetController _controller; + final BoxFit _fit; + final Alignment _alignment; + final BoundsProvider _boundsProvider; + final bool _sizedByBounds; - const SpineWidget.asset(this.skeletonFile, this.atlasFile, this.controller, {BoxFit? fit, Alignment? alignment, BoundsProvider? boundsProvider, bool? sizedByBounds, super.key}) + const SpineWidget.asset(this._skeletonFile, this._atlasFile, this._controller, {BoxFit? fit, Alignment? alignment, BoundsProvider? boundsProvider, bool? sizedByBounds, super.key}) : _assetType = AssetType.Asset, - fit = fit ?? BoxFit.contain, - alignment = alignment ?? Alignment.center, - boundsProvider = boundsProvider ?? const SetupPoseBounds(), - sizedByBounds = sizedByBounds ?? false, - skeletonData = null, - atlas = null; + _fit = fit ?? BoxFit.contain, + _alignment = alignment ?? Alignment.center, + _boundsProvider = boundsProvider ?? const SetupPoseBounds(), + _sizedByBounds = sizedByBounds ?? false, + _drawable = null; - const SpineWidget.file(this.skeletonFile, this.atlasFile, this.controller, {BoxFit? fit, Alignment? alignment, BoundsProvider? boundsProvider, bool? sizedByBounds, super.key}) + const SpineWidget.file(this._skeletonFile, this._atlasFile, this._controller, {BoxFit? fit, Alignment? alignment, BoundsProvider? boundsProvider, bool? sizedByBounds, super.key}) : _assetType = AssetType.File, - fit = fit ?? BoxFit.contain, - alignment = alignment ?? Alignment.center, - boundsProvider = boundsProvider ?? const SetupPoseBounds(), - sizedByBounds = sizedByBounds ?? false, - skeletonData = null, - atlas = null; + _fit = fit ?? BoxFit.contain, + _alignment = alignment ?? Alignment.center, + _boundsProvider = boundsProvider ?? const SetupPoseBounds(), + _sizedByBounds = sizedByBounds ?? false, + _drawable = null; - const SpineWidget.http(this.skeletonFile, this.atlasFile, this.controller, {BoxFit? fit, Alignment? alignment, BoundsProvider? boundsProvider, bool? sizedByBounds, super.key}) + const SpineWidget.http(this._skeletonFile, this._atlasFile, this._controller, {BoxFit? fit, Alignment? alignment, BoundsProvider? boundsProvider, bool? sizedByBounds, super.key}) : _assetType = AssetType.Http, - fit = fit ?? BoxFit.contain, - alignment = alignment ?? Alignment.center, - boundsProvider = boundsProvider ?? const SetupPoseBounds(), - sizedByBounds = sizedByBounds ?? false, - skeletonData = null, - atlas = null; + _fit = fit ?? BoxFit.contain, + _alignment = alignment ?? Alignment.center, + _boundsProvider = boundsProvider ?? const SetupPoseBounds(), + _sizedByBounds = sizedByBounds ?? false, + _drawable = null; - const SpineWidget.raw(this.skeletonData, this.atlas, this.controller, {BoxFit? fit, Alignment? alignment, BoundsProvider? boundsProvider, bool? sizedByBounds, super.key}) - : _assetType = AssetType.Raw, - fit = fit ?? BoxFit.contain, - alignment = alignment ?? Alignment.center, - boundsProvider = boundsProvider ?? const SetupPoseBounds(), - sizedByBounds = sizedByBounds ?? false, - skeletonFile = null, - atlasFile = null; + const SpineWidget.drawable(this._drawable, this._controller, {BoxFit? fit, Alignment? alignment, BoundsProvider? boundsProvider, bool? sizedByBounds, super.key}) + : _assetType = AssetType.Drawable, + _fit = fit ?? BoxFit.contain, + _alignment = alignment ?? Alignment.center, + _boundsProvider = boundsProvider ?? const SetupPoseBounds(), + _sizedByBounds = sizedByBounds ?? false, + _skeletonFile = null, + _atlasFile = null; @override State createState() => _SpineWidgetState(); @@ -178,16 +175,16 @@ class _SpineWidgetState extends State { @override void initState() { super.initState(); - if (widget._assetType == AssetType.Raw) { - loadRaw(widget.skeletonData!, widget.atlas!); + if (widget._assetType == AssetType.Drawable) { + loadDrawable(widget._drawable!); } else { - loadFromAsset(widget.skeletonFile!, widget.atlasFile!, widget._assetType); + loadFromAsset(widget._skeletonFile!, widget._atlasFile!, widget._assetType); } } - void loadRaw(SkeletonData skeletonData, Atlas atlas) { - skeletonDrawable = SkeletonDrawable(atlas, skeletonData, false); - widget.controller._initialize(atlas, skeletonData, skeletonDrawable!); + void loadDrawable(SkeletonDrawable drawable) { + skeletonDrawable = drawable; + widget._controller._initialize(skeletonDrawable!); skeletonDrawable?.update(0); setState(() {}); } @@ -198,35 +195,24 @@ class _SpineWidgetState extends State { switch (assetType) { case AssetType.Asset: - atlas = await Atlas.fromAsset(rootBundle, atlasFile); - skeletonData = skeletonFile.endsWith(".json") - ? SkeletonData.fromJson(atlas, await rootBundle.loadString(skeletonFile)) - : SkeletonData.fromBinary(atlas, (await rootBundle.load(skeletonFile)).buffer.asUint8List()); + loadDrawable(await SkeletonDrawable.fromAsset(skeletonFile, atlasFile)); break; case AssetType.File: - atlas = await Atlas.fromFile(atlasFile); - skeletonData = skeletonFile.endsWith(".json") - ? SkeletonData.fromJson(atlas, utf8.decode(await File(skeletonFile).readAsBytes())) - : SkeletonData.fromBinary(atlas, await File(skeletonFile).readAsBytes()); + loadDrawable(await SkeletonDrawable.fromFile(skeletonFile, atlasFile)); break; case AssetType.Http: - atlas = await Atlas.fromUrl(atlasFile); - skeletonData = skeletonFile.endsWith(".json") - ? SkeletonData.fromJson(atlas, utf8.decode((await http.get(Uri.parse(skeletonFile))).bodyBytes)) - : SkeletonData.fromBinary(atlas, (await http.get(Uri.parse(skeletonFile))).bodyBytes); + loadDrawable(await SkeletonDrawable.fromHttp(skeletonFile, atlasFile)); break; - case AssetType.Raw: - throw Exception("Raw assets can not be loaded via loadFromAsset()."); + case AssetType.Drawable: + throw Exception("Drawable can not be loaded via loadFromAsset()."); } - - loadRaw(skeletonData, atlas); } @override Widget build(BuildContext context) { if (skeletonDrawable != null) { print("Skeleton loaded, rebuilding painter"); - return _SpineRenderObjectWidget(skeletonDrawable!, widget.fit, widget.alignment, widget.boundsProvider, widget.sizedByBounds); + return _SpineRenderObjectWidget(skeletonDrawable!, widget._fit, widget._alignment, widget._boundsProvider, widget._sizedByBounds); } else { print("Skeleton not loaded yet"); return const SizedBox();