156 lines
4.3 KiB
Dart
156 lines
4.3 KiB
Dart
import 'dart:async';
|
|
import 'dart:ui' as ui;
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
class SpriteAnimationWidget extends StatefulWidget {
|
|
final String assetPath;
|
|
final double tileWidth;
|
|
final double tileHeight;
|
|
final int frameCount;
|
|
final double scale;
|
|
|
|
const SpriteAnimationWidget({
|
|
super.key,
|
|
required this.assetPath,
|
|
this.tileWidth = 100.0,
|
|
this.tileHeight = 100.0,
|
|
this.frameCount =
|
|
6, // Default guess, will adjust logic to use actual image width if possible
|
|
this.scale = 1.0,
|
|
});
|
|
|
|
@override
|
|
State<SpriteAnimationWidget> createState() => _SpriteAnimationWidgetState();
|
|
}
|
|
|
|
class _SpriteAnimationWidgetState extends State<SpriteAnimationWidget>
|
|
with SingleTickerProviderStateMixin {
|
|
ui.Image? _image;
|
|
late AnimationController _controller;
|
|
bool _isLoading = true;
|
|
int _calculatedFrameCount = 6;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller = AnimationController(
|
|
vsync: this,
|
|
duration: const Duration(milliseconds: 600), // 100ms per frame approx
|
|
);
|
|
_loadImage();
|
|
}
|
|
|
|
Future<void> _loadImage() async {
|
|
try {
|
|
final ByteData data = await rootBundle.load(widget.assetPath);
|
|
final List<int> bytes = data.buffer.asUint8List();
|
|
final Completer<ui.Image> completer = Completer();
|
|
ui.decodeImageFromList(Uint8List.fromList(bytes), (ui.Image img) {
|
|
completer.complete(img);
|
|
});
|
|
final image = await completer.future;
|
|
|
|
if (mounted) {
|
|
setState(() {
|
|
_image = image;
|
|
_isLoading = false;
|
|
// Use provided frameCount, but clamp to available frames in image
|
|
int maxFrames = (image.width / widget.tileWidth).floor();
|
|
_calculatedFrameCount = widget.frameCount > maxFrames
|
|
? maxFrames
|
|
: widget.frameCount;
|
|
|
|
// Adjust duration based on frame count
|
|
_controller.duration = Duration(
|
|
milliseconds: _calculatedFrameCount * 100,
|
|
);
|
|
_controller.repeat();
|
|
});
|
|
}
|
|
} catch (e) {
|
|
debugPrint('Failed to load sprite image: $e');
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (_isLoading || _image == null) {
|
|
return SizedBox(
|
|
width: widget.tileWidth * widget.scale,
|
|
height: widget.tileHeight * widget.scale,
|
|
child: const Center(child: CircularProgressIndicator()),
|
|
);
|
|
}
|
|
|
|
return AnimatedBuilder(
|
|
animation: _controller,
|
|
builder: (context, child) {
|
|
return CustomPaint(
|
|
size: Size(
|
|
widget.tileWidth * widget.scale,
|
|
widget.tileHeight * widget.scale,
|
|
),
|
|
painter: SpriteSheetPainter(
|
|
image: _image!,
|
|
currentFrame:
|
|
(_controller.value * _calculatedFrameCount).floor() %
|
|
_calculatedFrameCount,
|
|
tileWidth: widget.tileWidth,
|
|
tileHeight: widget.tileHeight,
|
|
scale: widget.scale,
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class SpriteSheetPainter extends CustomPainter {
|
|
final ui.Image image;
|
|
final int currentFrame;
|
|
final double tileWidth;
|
|
final double tileHeight;
|
|
final double scale;
|
|
|
|
SpriteSheetPainter({
|
|
required this.image,
|
|
required this.currentFrame,
|
|
required this.tileWidth,
|
|
required this.tileHeight,
|
|
required this.scale,
|
|
});
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
// Correct src rect calculation
|
|
// Assuming horizontal strip for the animation row.
|
|
// Ideally we would want to select which 'row' (Y) to animate, but for now assuming row 0.
|
|
// If the image is a single row, srcY is 0.
|
|
final double srcX = currentFrame * tileWidth;
|
|
final double srcY = 0.0; // Default to first row
|
|
|
|
final Rect src = Rect.fromLTWH(srcX, srcY, tileWidth, tileHeight);
|
|
final Rect dst = Rect.fromLTWH(0, 0, tileWidth * scale, tileHeight * scale);
|
|
|
|
canvas.drawImageRect(
|
|
image,
|
|
src,
|
|
dst,
|
|
Paint()..filterQuality = FilterQuality.none,
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant SpriteSheetPainter oldDelegate) {
|
|
return oldDelegate.currentFrame != currentFrame ||
|
|
oldDelegate.image != image;
|
|
}
|
|
}
|