[flutter] Bounds type and controllable parent sizing.

This commit is contained in:
Mario Zechner 2022-11-15 09:19:24 +01:00
parent c8e2032b1c
commit 86f68ecf04
2 changed files with 105 additions and 34 deletions

View File

@ -50,13 +50,13 @@ class SimpleAnimation extends StatelessWidget {
reportLeaks(); reportLeaks();
final controller = SpineWidgetController((controller) { final controller = SpineWidgetController((controller) {
// Set the walk animation on track 0, let it loop // Set the walk animation on track 0, let it loop
// controller.animationState?.setAnimationByName(0, "walk", true); controller.animationState?.setAnimationByName(0, "animation", true);
}); });
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('Spineboy')), appBar: AppBar(title: const Text('Spineboy')),
// body: SpineWidget.asset("assets/skeleton.json", "assets/skeleton.atlas", controller, alignment: Alignment.center, fit: BoxFit.none), body: SpineWidget.asset("assets/skeleton.json", "assets/skeleton.atlas", controller, alignment: Alignment.centerLeft, fit: BoxFit.none),
body: SpineWidget.asset("assets/spineboy-pro.skel", "assets/spineboy.atlas", controller) // body: SpineWidget.asset("assets/spineboy-pro.skel", "assets/spineboy.atlas", controller, alignment: Alignment.bottomLeft)
// 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: 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: const SpineWidget.http("https://marioslab.io/dump/spineboy/spineboy-pro.json", "https://marioslab.io/dump/spineboy/spineboy.atlas"),
); );

View File

@ -42,35 +42,83 @@ class SpineWidgetController {
enum AssetType { Asset, File, Http, Raw } enum AssetType { Asset, File, Http, Raw }
abstract class BoundsProvider {
const BoundsProvider();
Bounds computeBounds(SkeletonDrawable drawable);
}
class SetupPoseBounds extends BoundsProvider {
const SetupPoseBounds();
Bounds computeBounds(SkeletonDrawable drawable) {
return drawable.skeleton.getBounds();
}
}
class RawBounds extends BoundsProvider {
final double x, y, width, height;
RawBounds(this.x, this.y, this.width, this.height);
Bounds computeBounds(SkeletonDrawable drawable) {
return Bounds(x, y, width, height);
}
}
class ComputedBounds extends BoundsProvider {
Bounds computeBounds(SkeletonDrawable drawable) {
return Bounds(0, 0, 0, 0);
}
}
class SpineWidget extends StatefulWidget { class SpineWidget extends StatefulWidget {
final AssetType _assetType;
final String? skeletonFile; final String? skeletonFile;
final String? atlasFile; final String? atlasFile;
final SkeletonData? skeletonData; final SkeletonData? skeletonData;
final Atlas? atlas; final Atlas? atlas;
final SpineWidgetController controller; final SpineWidgetController controller;
final BoxFit? fit; final BoxFit fit;
final Alignment? alignment; final Alignment alignment;
final AssetType _assetType; final BoundsProvider boundsProvider;
final bool sizedByBounds;
const SpineWidget.asset(this.skeletonFile, this.atlasFile, this.controller, {this.fit, this.alignment, super.key}) const SpineWidget.asset(this.skeletonFile, this.atlasFile, this.controller, {BoxFit? fit, Alignment? alignment, BoundsProvider? boundsProvider, bool? sizedByBounds, super.key})
: _assetType = AssetType.Asset, : _assetType = AssetType.Asset,
atlas = null, fit = fit ?? BoxFit.contain,
skeletonData = null; alignment = alignment ?? Alignment.center,
boundsProvider = boundsProvider ?? const SetupPoseBounds(),
sizedByBounds = sizedByBounds ?? false,
skeletonData = null,
atlas = null;
const SpineWidget.file(this.skeletonFile, this.atlasFile, this.controller, {this.fit, this.alignment, super.key}) const SpineWidget.file(this.skeletonFile, this.atlasFile, this.controller, {BoxFit? fit, Alignment? alignment, BoundsProvider? boundsProvider, bool? sizedByBounds, super.key})
: _assetType = AssetType.File, : _assetType = AssetType.File,
atlas = null, fit = fit ?? BoxFit.contain,
skeletonData = null; alignment = alignment ?? Alignment.center,
boundsProvider = boundsProvider ?? const SetupPoseBounds(),
sizedByBounds = sizedByBounds ?? false,
skeletonData = null,
atlas = null;
const SpineWidget.http(this.skeletonFile, this.atlasFile, this.controller, {this.fit, this.alignment, super.key}) const SpineWidget.http(this.skeletonFile, this.atlasFile, this.controller, {BoxFit? fit, Alignment? alignment, BoundsProvider? boundsProvider, bool? sizedByBounds, super.key})
: _assetType = AssetType.Http, : _assetType = AssetType.Http,
atlas = null, fit = fit ?? BoxFit.contain,
skeletonData = null; alignment = alignment ?? Alignment.center,
boundsProvider = boundsProvider ?? const SetupPoseBounds(),
sizedByBounds = sizedByBounds ?? false,
skeletonData = null,
atlas = null;
const SpineWidget.raw(this.skeletonData, this.atlas, this.controller, {this.fit, this.alignment, super.key}) const SpineWidget.raw(this.skeletonData, this.atlas, this.controller, {BoxFit? fit, Alignment? alignment, BoundsProvider? boundsProvider, bool? sizedByBounds, super.key})
: _assetType = AssetType.Raw, : _assetType = AssetType.Raw,
atlasFile = null, fit = fit ?? BoxFit.contain,
skeletonFile = null; alignment = alignment ?? Alignment.center,
boundsProvider = boundsProvider ?? const SetupPoseBounds(),
sizedByBounds = sizedByBounds ?? false,
skeletonFile = null,
atlasFile = null;
@override @override
State<SpineWidget> createState() => _SpineWidgetState(); State<SpineWidget> createState() => _SpineWidgetState();
@ -131,7 +179,7 @@ class _SpineWidgetState extends State<SpineWidget> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (skeletonDrawable != null) { if (skeletonDrawable != null) {
print("Skeleton loaded, rebuilding painter"); print("Skeleton loaded, rebuilding painter");
return _SpineRenderObjectWidget(skeletonDrawable!, widget.controller, widget.fit, widget.alignment); return _SpineRenderObjectWidget(skeletonDrawable!, widget.fit, widget.alignment, widget.boundsProvider, widget.sizedByBounds);
} else { } else {
print("Skeleton not loaded yet"); print("Skeleton not loaded yet");
return const SizedBox(); return const SizedBox();
@ -147,38 +195,46 @@ class _SpineWidgetState extends State<SpineWidget> {
class _SpineRenderObjectWidget extends LeafRenderObjectWidget { class _SpineRenderObjectWidget extends LeafRenderObjectWidget {
final SkeletonDrawable _skeletonDrawable; final SkeletonDrawable _skeletonDrawable;
final SpineWidgetController _controller;
final BoxFit _fit; final BoxFit _fit;
final Alignment _alignment; final Alignment _alignment;
final BoundsProvider _boundsProvider;
final bool _sizedByBounds;
_SpineRenderObjectWidget(this._skeletonDrawable, this._controller, BoxFit? fit, Alignment? alignment) : _SpineRenderObjectWidget(this._skeletonDrawable, this._fit, this._alignment, this._boundsProvider, this._sizedByBounds);
_fit = fit ?? BoxFit.contain,
_alignment = alignment ?? Alignment.center;
@override @override
RenderObject createRenderObject(BuildContext context) { RenderObject createRenderObject(BuildContext context) {
return _SpineRenderObject(_skeletonDrawable, _controller, _fit, _alignment); return _SpineRenderObject(_skeletonDrawable, _fit, _alignment, _boundsProvider, _sizedByBounds);
} }
@override @override
void updateRenderObject(BuildContext context, covariant _SpineRenderObject renderObject) { void updateRenderObject(BuildContext context, covariant _SpineRenderObject renderObject) {
renderObject.skeletonDrawable = _skeletonDrawable; renderObject.skeletonDrawable = _skeletonDrawable;
renderObject.fit = _fit;
renderObject.alignment = _alignment;
renderObject.boundsProvider = _boundsProvider;
renderObject.sizedByBounds = _sizedByBounds;
} }
} }
class _SpineRenderObject extends RenderBox { class _SpineRenderObject extends RenderBox {
SkeletonDrawable _skeletonDrawable; SkeletonDrawable _skeletonDrawable;
SpineWidgetController _controller;
double _deltaTime = 0; double _deltaTime = 0;
final Stopwatch _stopwatch = Stopwatch(); final Stopwatch _stopwatch = Stopwatch();
BoxFit _fit; BoxFit _fit;
Alignment _alignment; Alignment _alignment;
BoundsProvider _boundsProvider;
bool _sizedByBounds;
Bounds _bounds; Bounds _bounds;
_SpineRenderObject(this._skeletonDrawable, this._fit, this._alignment, this._boundsProvider, this._sizedByBounds): _bounds = _boundsProvider.computeBounds(_skeletonDrawable);
_SpineRenderObject(this._skeletonDrawable, this._controller, this._fit, this._alignment): _bounds = _computeBounds(_skeletonDrawable); set skeletonDrawable(SkeletonDrawable skeletonDrawable) {
if (_skeletonDrawable == skeletonDrawable) return;
static Bounds _computeBounds(SkeletonDrawable drawable) { _skeletonDrawable = skeletonDrawable;
return drawable.skeleton.getBounds(); _bounds = _boundsProvider.computeBounds(_skeletonDrawable);
markNeedsLayout();
markNeedsPaint();
} }
BoxFit get fit => _fit; BoxFit get fit => _fit;
@ -186,6 +242,7 @@ class _SpineRenderObject extends RenderBox {
set fit(BoxFit fit) { set fit(BoxFit fit) {
if (fit != _fit) { if (fit != _fit) {
_fit = fit; _fit = fit;
markNeedsLayout();
markNeedsPaint(); markNeedsPaint();
} }
} }
@ -195,20 +252,34 @@ class _SpineRenderObject extends RenderBox {
set alignment(Alignment alignment) { set alignment(Alignment alignment) {
if (alignment != _alignment) { if (alignment != _alignment) {
_alignment = alignment; _alignment = alignment;
markNeedsLayout();
markNeedsPaint(); markNeedsPaint();
} }
} }
set skeletonDrawable(SkeletonDrawable skeletonDrawable) { BoundsProvider get boundsProvider => _boundsProvider;
if (_skeletonDrawable == skeletonDrawable) return;
_skeletonDrawable = skeletonDrawable; set boundsProvider(BoundsProvider boundsProvider) {
_bounds = _computeBounds(_skeletonDrawable); if (boundsProvider != _boundsProvider) {
markNeedsPaint(); _boundsProvider = boundsProvider;
_bounds = boundsProvider.computeBounds(_skeletonDrawable);
markNeedsLayout();
markNeedsPaint();
}
}
bool get sizedByBounds => _sizedByBounds;
set sizedByBounds(bool sizedByBounds) {
if (sizedByBounds != _sizedByBounds) {
_sizedByBounds = _sizedByBounds;
markNeedsLayout();
markNeedsPaint();
}
} }
@override @override
bool get sizedByParent => true; bool get sizedByParent => !_sizedByBounds;
@override @override
bool get isRepaintBoundary => true; bool get isRepaintBoundary => true;