|
|
@@ -14,6 +14,10 @@ class FrameAnimationView extends StatefulWidget {
|
|
|
|
|
|
final int frameRate;
|
|
|
|
|
|
+ final FrameAnimationController? controller;
|
|
|
+
|
|
|
+ final double? speed;
|
|
|
+
|
|
|
final double? width;
|
|
|
|
|
|
final double? height;
|
|
|
@@ -22,16 +26,18 @@ class FrameAnimationView extends StatefulWidget {
|
|
|
{super.key,
|
|
|
required this.framePath,
|
|
|
this.frameRate = 25,
|
|
|
+ this.controller,
|
|
|
+ this.speed,
|
|
|
this.width,
|
|
|
this.height});
|
|
|
|
|
|
@override
|
|
|
State<StatefulWidget> createState() {
|
|
|
- return _FrameAnimationViewState();
|
|
|
+ return FrameAnimationViewState();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-class _FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
+class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
int _currentFrame = 0;
|
|
|
Timer? _timer;
|
|
|
List<File> imageFiles = [];
|
|
|
@@ -40,10 +46,13 @@ class _FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
@override
|
|
|
void initState() {
|
|
|
super.initState();
|
|
|
+ widget.controller?.bindState(this);
|
|
|
loadFrameFromAssets()
|
|
|
.then((_) => initializeImageFiles())
|
|
|
.then((_) => precacheImageFiles())
|
|
|
- .then((_) => startAnimation())
|
|
|
+ .then((_) => widget.controller == null || widget.controller!.autoPlay
|
|
|
+ ? startAnimation()
|
|
|
+ : null)
|
|
|
.catchError((error) => debugPrint('FrameAnimationView error: $error'));
|
|
|
}
|
|
|
|
|
|
@@ -55,8 +64,8 @@ class _FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
|
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
|
- if (images.isEmpty || widget.width == null || widget.height == null) {
|
|
|
- return Container();
|
|
|
+ if (images.isEmpty) {
|
|
|
+ return SizedBox(width: widget.width, height: widget.height);
|
|
|
}
|
|
|
return RawImage(
|
|
|
image: images[_currentFrame],
|
|
|
@@ -108,31 +117,6 @@ class _FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- Future<Directory> createCacheDirectory() async {
|
|
|
- // Get the temporary directory
|
|
|
- final Directory tempDir = await getTemporaryDirectory();
|
|
|
-
|
|
|
- // Create a new directory within the temporary directory
|
|
|
- final Directory cacheDir =
|
|
|
- Directory('${tempDir.path}/frame_anim_cache');
|
|
|
-
|
|
|
- // Check if the directory exists, if not, create it
|
|
|
- if (!await cacheDir.exists()) {
|
|
|
- await cacheDir.create(recursive: true);
|
|
|
- }
|
|
|
-
|
|
|
- // create unique directory by framePath's md5
|
|
|
- final Directory frameDir = Directory(
|
|
|
- '${cacheDir.path}/${md5.convert(utf8.encode(widget.framePath)).toString()}');
|
|
|
-
|
|
|
- // Check if the directory exists, if not, create it
|
|
|
- if (!await frameDir.exists()) {
|
|
|
- await frameDir.create(recursive: true);
|
|
|
- }
|
|
|
-
|
|
|
- return frameDir;
|
|
|
- }
|
|
|
-
|
|
|
Future<void> copyToCache() async {
|
|
|
// Read AssetManifest.json file
|
|
|
final String manifestContent =
|
|
|
@@ -170,13 +154,43 @@ class _FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- Future<void> startAnimation() async {
|
|
|
+ Future<Directory> createCacheDirectory() async {
|
|
|
+ // Get the temporary directory
|
|
|
+ final Directory tempDir = await getTemporaryDirectory();
|
|
|
+
|
|
|
+ // Create a new directory within the temporary directory
|
|
|
+ final Directory cacheDir = Directory('${tempDir.path}/frame_anim_cache');
|
|
|
+
|
|
|
+ // Check if the directory exists, if not, create it
|
|
|
+ if (!await cacheDir.exists()) {
|
|
|
+ await cacheDir.create(recursive: true);
|
|
|
+ }
|
|
|
+
|
|
|
+ // create unique directory by framePath's md5
|
|
|
+ final Directory frameDir = Directory(
|
|
|
+ '${cacheDir.path}/${md5.convert(utf8.encode(widget.framePath)).toString()}');
|
|
|
+
|
|
|
+ // Check if the directory exists, if not, create it
|
|
|
+ if (!await frameDir.exists()) {
|
|
|
+ await frameDir.create(recursive: true);
|
|
|
+ }
|
|
|
+
|
|
|
+ return frameDir;
|
|
|
+ }
|
|
|
+
|
|
|
+ startAnimation() {
|
|
|
if (imageFiles.isEmpty) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- _timer =
|
|
|
- Timer.periodic(Duration(milliseconds: 1000 ~/ widget.frameRate), (_) {
|
|
|
+ if (_timer != null && _timer!.isActive) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ double speed = widget.speed ?? 1.0;
|
|
|
+
|
|
|
+ _timer = Timer.periodic(
|
|
|
+ Duration(milliseconds: 1000 ~/ (widget.frameRate * speed)), (_) {
|
|
|
setState(() {
|
|
|
int targetFrame = (_currentFrame + 1) % imageFiles.length;
|
|
|
_currentFrame =
|
|
|
@@ -185,7 +199,7 @@ class _FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- Future<void> initializeImageFiles() async {
|
|
|
+ initializeImageFiles() async {
|
|
|
final Directory cacheDir = await createCacheDirectory();
|
|
|
|
|
|
imageFiles = cacheDir
|
|
|
@@ -214,3 +228,25 @@ class _FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+class FrameAnimationController {
|
|
|
+ final bool autoPlay;
|
|
|
+
|
|
|
+ FrameAnimationViewState? _state;
|
|
|
+
|
|
|
+ FrameAnimationController({this.autoPlay = true});
|
|
|
+
|
|
|
+ void play() {
|
|
|
+ _state?.startAnimation();
|
|
|
+ }
|
|
|
+
|
|
|
+ void stop() {
|
|
|
+ _state?._timer?.cancel();
|
|
|
+ }
|
|
|
+
|
|
|
+ bool get isPlaying => _state?._timer?.isActive ?? false;
|
|
|
+
|
|
|
+ void bindState(FrameAnimationViewState state) {
|
|
|
+ _state = state;
|
|
|
+ }
|
|
|
+}
|