mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +08:00
[flutter] Add SpineWidgetController.pause/resume/isPlaying. Can be used to stop frame updates entirely.
This commit is contained in:
parent
147439fe95
commit
455189c000
@ -10,7 +10,6 @@ class PlayPauseAnimation extends StatefulWidget {
|
|||||||
|
|
||||||
class PlayPauseAnimationState extends State<PlayPauseAnimation> {
|
class PlayPauseAnimationState extends State<PlayPauseAnimation> {
|
||||||
late SpineWidgetController controller;
|
late SpineWidgetController controller;
|
||||||
late bool isPlaying;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -18,12 +17,14 @@ class PlayPauseAnimationState extends State<PlayPauseAnimation> {
|
|||||||
controller = SpineWidgetController(onInitialized: (controller) {
|
controller = SpineWidgetController(onInitialized: (controller) {
|
||||||
controller.animationState.setAnimationByName(0, "flying", true);
|
controller.animationState.setAnimationByName(0, "flying", true);
|
||||||
});
|
});
|
||||||
isPlaying = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _togglePlay() {
|
void _togglePlay() {
|
||||||
isPlaying = !isPlaying;
|
if (controller.isPlaying) {
|
||||||
controller.animationState.setTimeScale(isPlaying ? 1 : 0);
|
controller.pause();
|
||||||
|
} else {
|
||||||
|
controller.resume();
|
||||||
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +42,7 @@ class PlayPauseAnimationState extends State<PlayPauseAnimation> {
|
|||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: _togglePlay,
|
onPressed: _togglePlay,
|
||||||
child: Icon(isPlaying ? Icons.pause : Icons.play_arrow),
|
child: Icon(controller.isPlaying ? Icons.pause : Icons.play_arrow),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,9 +14,7 @@ class SimpleAnimation extends StatelessWidget {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('Simple Animation')),
|
appBar: AppBar(title: const Text('Simple Animation')),
|
||||||
body: SpineWidget.fromAsset("assets/spineboy.atlas", "assets/spineboy-pro.skel", controller),
|
body: SpineWidget.fromAsset("assets/spineboy.atlas", "assets/spineboy-pro.skel", controller)
|
||||||
// body: SpineWidget.file( "/Users/badlogic/workspaces/spine-runtimes/examples/spineboy/export/spineboy.atlas", "/Users/badlogic/workspaces/spine-runtimes/examples/spineboy/export/spineboy-pro.skel", controller),
|
|
||||||
// body: const SpineWidget.http("https://marioslab.io/dump/spineboy/spineboy.atlas", "https://marioslab.io/dump/spineboy/spineboy-pro.json"),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,9 +28,15 @@ import 'spine_flutter.dart';
|
|||||||
/// The underlying [Atlas], [SkeletonData], [Skeleton], [AnimationStateData], [AnimationState], and [SkeletonDrawable]
|
/// The underlying [Atlas], [SkeletonData], [Skeleton], [AnimationStateData], [AnimationState], and [SkeletonDrawable]
|
||||||
/// can be accessed through their respective getters to inspect and/or modify the skeleton and its associated data. Accessing
|
/// can be accessed through their respective getters to inspect and/or modify the skeleton and its associated data. Accessing
|
||||||
/// this data is only allowed if the [SpineWidget] and its data have been initialized and have not been disposed yet.
|
/// this data is only allowed if the [SpineWidget] and its data have been initialized and have not been disposed yet.
|
||||||
|
///
|
||||||
|
/// By default, the widget updates and renders the skeleton every frame. The [pause] method can be used to pause updating
|
||||||
|
/// and rendering the skeleton. The [resume] method resumes updating and rendering the skeleton. The [isPlaying] getter
|
||||||
|
/// reports the current state.
|
||||||
class SpineWidgetController {
|
class SpineWidgetController {
|
||||||
SkeletonDrawable? _drawable;
|
SkeletonDrawable? _drawable;
|
||||||
double _offsetX = 0, _offsetY = 0, _scaleX = 1, _scaleY = 1;
|
double _offsetX = 0, _offsetY = 0, _scaleX = 1, _scaleY = 1;
|
||||||
|
bool _isPlaying = true;
|
||||||
|
_SpineRenderObject? _renderObject = null;
|
||||||
final void Function(SpineWidgetController controller)? onInitialized;
|
final void Function(SpineWidgetController controller)? onInitialized;
|
||||||
final void Function(SpineWidgetController controller)? onBeforeUpdateWorldTransforms;
|
final void Function(SpineWidgetController controller)? onBeforeUpdateWorldTransforms;
|
||||||
final void Function(SpineWidgetController controller)? onAfterUpdateWorldTransforms;
|
final void Function(SpineWidgetController controller)? onAfterUpdateWorldTransforms;
|
||||||
@ -92,6 +98,10 @@ class SpineWidgetController {
|
|||||||
_scaleY = scaleY;
|
_scaleY = scaleY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _setRenderObject(_SpineRenderObject? renderObject) {
|
||||||
|
_renderObject = renderObject;
|
||||||
|
}
|
||||||
|
|
||||||
/// Transforms the coordinates given in the [SpineWidget] coordinate system in [position] to
|
/// Transforms the coordinates given in the [SpineWidget] coordinate system in [position] to
|
||||||
/// the skeleton coordinate system. See the `ik_following.dart` example how to use this
|
/// the skeleton coordinate system. See the `ik_following.dart` example how to use this
|
||||||
/// to move a bone based on user touch input.
|
/// to move a bone based on user touch input.
|
||||||
@ -100,6 +110,23 @@ class SpineWidgetController {
|
|||||||
var y = position.dy;
|
var y = position.dy;
|
||||||
return Offset(x / _scaleX - _offsetX, y / _scaleY - _offsetY);
|
return Offset(x / _scaleX - _offsetX, y / _scaleY - _offsetY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pauses updating and rendering the skeleton.
|
||||||
|
void pause() {
|
||||||
|
_isPlaying = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resumes updating and rendering the skeleton.
|
||||||
|
void resume() {
|
||||||
|
_isPlaying = true;
|
||||||
|
_renderObject?._stopwatch.reset();
|
||||||
|
_renderObject?._stopwatch.start();
|
||||||
|
_renderObject?._scheduleFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isPlaying {
|
||||||
|
return _isPlaying;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum _AssetType { asset, file, http, drawable }
|
enum _AssetType { asset, file, http, drawable }
|
||||||
@ -391,6 +418,7 @@ class _SpineRenderObject extends RenderBox {
|
|||||||
Alignment _alignment;
|
Alignment _alignment;
|
||||||
Bounds _bounds;
|
Bounds _bounds;
|
||||||
bool _sizedByBounds;
|
bool _sizedByBounds;
|
||||||
|
bool _disposed = false;
|
||||||
|
|
||||||
_SpineRenderObject(this._skeletonDrawable, this._controller, this._fit, this._alignment, this._bounds, this._sizedByBounds);
|
_SpineRenderObject(this._skeletonDrawable, this._controller, this._fit, this._alignment, this._bounds, this._sizedByBounds);
|
||||||
|
|
||||||
@ -493,22 +521,39 @@ class _SpineRenderObject extends RenderBox {
|
|||||||
void attach(rendering.PipelineOwner owner) {
|
void attach(rendering.PipelineOwner owner) {
|
||||||
super.attach(owner);
|
super.attach(owner);
|
||||||
_stopwatch.start();
|
_stopwatch.start();
|
||||||
|
SchedulerBinding.instance.scheduleFrameCallback(_beginFrame);
|
||||||
|
_controller._setRenderObject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void detach() {
|
void detach() {
|
||||||
_stopwatch.stop();
|
_stopwatch.stop();
|
||||||
super.detach();
|
super.detach();
|
||||||
|
_controller._setRenderObject(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _scheduleFrame() {
|
||||||
|
SchedulerBinding.instance.scheduleFrameCallback(_beginFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _beginFrame(Duration duration) {
|
void _beginFrame(Duration duration) {
|
||||||
|
if (_disposed) return;
|
||||||
_deltaTime = _stopwatch.elapsedTicks / _stopwatch.frequency;
|
_deltaTime = _stopwatch.elapsedTicks / _stopwatch.frequency;
|
||||||
_stopwatch.reset();
|
_stopwatch.reset();
|
||||||
_stopwatch.start();
|
_stopwatch.start();
|
||||||
_controller.onBeforeUpdateWorldTransforms?.call(_controller);
|
if (_controller.isPlaying) {
|
||||||
_skeletonDrawable.update(_deltaTime);
|
_controller.onBeforeUpdateWorldTransforms?.call(_controller);
|
||||||
_controller.onAfterUpdateWorldTransforms?.call(_controller);
|
_skeletonDrawable.update(_deltaTime);
|
||||||
markNeedsPaint();
|
_controller.onAfterUpdateWorldTransforms?.call(_controller);
|
||||||
|
markNeedsPaint();
|
||||||
|
_scheduleFrame();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setCanvasTransform(Canvas canvas, Offset offset) {
|
void _setCanvasTransform(Canvas canvas, Offset offset) {
|
||||||
@ -568,6 +613,5 @@ class _SpineRenderObject extends RenderBox {
|
|||||||
_controller.onAfterPaint?.call(_controller, canvas, commands);
|
_controller.onAfterPaint?.call(_controller, canvas, commands);
|
||||||
|
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
SchedulerBinding.instance.scheduleFrameCallback(_beginFrame);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user