|
|
@@ -37,16 +37,20 @@ class FrameAnimationView extends StatefulWidget {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
- int _currentFrame = -1;
|
|
|
+class FrameAnimationViewState extends State<FrameAnimationView>
|
|
|
+ with SingleTickerProviderStateMixin {
|
|
|
+ late final AnimationController _frameAnimationController;
|
|
|
List<File> imageFiles = [];
|
|
|
List<ui.Image> images = [];
|
|
|
- Timer? _timer;
|
|
|
StreamSubscription? _precacheStreamSubscription;
|
|
|
|
|
|
@override
|
|
|
void initState() {
|
|
|
super.initState();
|
|
|
+ _frameAnimationController = AnimationController(
|
|
|
+ vsync: this,
|
|
|
+ );
|
|
|
+ _frameAnimationController.addListener(() => setState(() {}));
|
|
|
widget.controller?.bindState(this);
|
|
|
loadFrameFromAssets()
|
|
|
.then((_) => initializeImageFiles())
|
|
|
@@ -59,8 +63,8 @@ class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
|
|
|
@override
|
|
|
void dispose() {
|
|
|
+ _frameAnimationController.dispose();
|
|
|
super.dispose();
|
|
|
- _timer?.cancel();
|
|
|
_precacheStreamSubscription?.cancel();
|
|
|
imageFiles.clear();
|
|
|
for (var image in images) {
|
|
|
@@ -71,14 +75,15 @@ class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
|
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
|
- if (_timer == null ||
|
|
|
- _currentFrame < 0 ||
|
|
|
- images.isEmpty ||
|
|
|
- _currentFrame >= images.length) {
|
|
|
+ if (imageFiles.isEmpty || !_frameAnimationController.isAnimating) {
|
|
|
return SizedBox(width: widget.width, height: widget.height);
|
|
|
}
|
|
|
+ final currentFrame = (_frameAnimationController.value * imageFiles.length)
|
|
|
+ .floor()
|
|
|
+ .clamp(0, images.length - 1);
|
|
|
+ debugPrint('currentFrame: $currentFrame');
|
|
|
return RawImage(
|
|
|
- image: images[_currentFrame],
|
|
|
+ image: images[currentFrame],
|
|
|
width: widget.width,
|
|
|
height: widget.height,
|
|
|
);
|
|
|
@@ -192,24 +197,19 @@ class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
if (imageFiles.isEmpty) {
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- if (_timer != null && _timer!.isActive) {
|
|
|
+ if (_frameAnimationController.isAnimating) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
double speed = widget.speed ?? 1.0;
|
|
|
|
|
|
- _timer = Timer.periodic(
|
|
|
- Duration(milliseconds: 1000 ~/ (widget.frameRate * speed)), (_) {
|
|
|
- if (images.isEmpty) {
|
|
|
- return;
|
|
|
- }
|
|
|
- setState(() {
|
|
|
- int targetFrame = (_currentFrame + 1) % imageFiles.length;
|
|
|
- _currentFrame =
|
|
|
- targetFrame >= images.length ? images.length - 1 : targetFrame;
|
|
|
- });
|
|
|
- });
|
|
|
+ int duration = (1000 ~/ widget.frameRate * imageFiles.length ~/ speed);
|
|
|
+
|
|
|
+ debugPrint('frame animation duration: $duration');
|
|
|
+
|
|
|
+ _frameAnimationController.duration = Duration(milliseconds: duration);
|
|
|
+
|
|
|
+ _frameAnimationController.repeat();
|
|
|
}
|
|
|
|
|
|
initializeImageFiles() async {
|
|
|
@@ -255,10 +255,10 @@ class FrameAnimationController {
|
|
|
}
|
|
|
|
|
|
void stop() {
|
|
|
- _state?._timer?.cancel();
|
|
|
+ _state?._frameAnimationController.stop();
|
|
|
}
|
|
|
|
|
|
- bool get isPlaying => _state?._timer?.isActive ?? false;
|
|
|
+ bool get isPlaying => _state?._frameAnimationController.isAnimating ?? false;
|
|
|
|
|
|
void bindState(FrameAnimationViewState state) {
|
|
|
_state = state;
|