|
@@ -22,13 +22,14 @@ class FrameAnimationView extends StatefulWidget {
|
|
|
|
|
|
|
|
final double? height;
|
|
final double? height;
|
|
|
|
|
|
|
|
- const FrameAnimationView({super.key,
|
|
|
|
|
- required this.framePath,
|
|
|
|
|
- this.frameRate = 25,
|
|
|
|
|
- this.controller,
|
|
|
|
|
- this.speed,
|
|
|
|
|
- this.width,
|
|
|
|
|
- this.height});
|
|
|
|
|
|
|
+ const FrameAnimationView(
|
|
|
|
|
+ {super.key,
|
|
|
|
|
+ required this.framePath,
|
|
|
|
|
+ this.frameRate = 25,
|
|
|
|
|
+ this.controller,
|
|
|
|
|
+ this.speed,
|
|
|
|
|
+ this.width,
|
|
|
|
|
+ this.height});
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
|
State<StatefulWidget> createState() {
|
|
State<StatefulWidget> createState() {
|
|
@@ -37,10 +38,11 @@ class FrameAnimationView extends StatefulWidget {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
- int _currentFrame = 0;
|
|
|
|
|
- Timer? _timer;
|
|
|
|
|
|
|
+ int _currentFrame = -1;
|
|
|
List<File> imageFiles = [];
|
|
List<File> imageFiles = [];
|
|
|
List<ui.Image> images = [];
|
|
List<ui.Image> images = [];
|
|
|
|
|
+ Timer? _timer;
|
|
|
|
|
+ StreamSubscription? _precacheStreamSubscription;
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
|
void initState() {
|
|
void initState() {
|
|
@@ -49,10 +51,9 @@ class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
loadFrameFromAssets()
|
|
loadFrameFromAssets()
|
|
|
.then((_) => initializeImageFiles())
|
|
.then((_) => initializeImageFiles())
|
|
|
.then((_) => precacheImageFiles())
|
|
.then((_) => precacheImageFiles())
|
|
|
- .then((_) =>
|
|
|
|
|
- widget.controller == null || widget.controller!.autoPlay
|
|
|
|
|
- ? startAnimation()
|
|
|
|
|
- : null)
|
|
|
|
|
|
|
+ .then((_) => widget.controller == null || widget.controller!.autoPlay
|
|
|
|
|
+ ? startAnimation()
|
|
|
|
|
+ : null)
|
|
|
.catchError((error) => debugPrint('FrameAnimationView error: $error'));
|
|
.catchError((error) => debugPrint('FrameAnimationView error: $error'));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -60,6 +61,7 @@ class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
void dispose() {
|
|
void dispose() {
|
|
|
super.dispose();
|
|
super.dispose();
|
|
|
_timer?.cancel();
|
|
_timer?.cancel();
|
|
|
|
|
+ _precacheStreamSubscription?.cancel();
|
|
|
imageFiles.clear();
|
|
imageFiles.clear();
|
|
|
for (var image in images) {
|
|
for (var image in images) {
|
|
|
image.dispose();
|
|
image.dispose();
|
|
@@ -69,7 +71,10 @@ class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
Widget build(BuildContext context) {
|
|
|
- if (images.isEmpty) {
|
|
|
|
|
|
|
+ if (_timer == null ||
|
|
|
|
|
+ _currentFrame < 0 ||
|
|
|
|
|
+ images.isEmpty ||
|
|
|
|
|
+ _currentFrame >= images.length) {
|
|
|
return SizedBox(width: widget.width, height: widget.height);
|
|
return SizedBox(width: widget.width, height: widget.height);
|
|
|
}
|
|
}
|
|
|
return RawImage(
|
|
return RawImage(
|
|
@@ -102,12 +107,8 @@ class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
final Directory cacheDir = await createCacheDirectory();
|
|
final Directory cacheDir = await createCacheDirectory();
|
|
|
|
|
|
|
|
// if the cache directory is not empty and frame images count is equal to the zip file's files count
|
|
// if the cache directory is not empty and frame images count is equal to the zip file's files count
|
|
|
- if (cacheDir
|
|
|
|
|
- .listSync()
|
|
|
|
|
- .isNotEmpty &&
|
|
|
|
|
- cacheDir
|
|
|
|
|
- .listSync()
|
|
|
|
|
- .length == archive.length) {
|
|
|
|
|
|
|
+ if (cacheDir.listSync().isNotEmpty &&
|
|
|
|
|
+ cacheDir.listSync().length == archive.length) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -129,7 +130,7 @@ class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
Future<void> copyToCache() async {
|
|
Future<void> copyToCache() async {
|
|
|
// Read AssetManifest.json file
|
|
// Read AssetManifest.json file
|
|
|
final String manifestContent =
|
|
final String manifestContent =
|
|
|
- await rootBundle.loadString('AssetManifest.json');
|
|
|
|
|
|
|
+ await rootBundle.loadString('AssetManifest.json');
|
|
|
|
|
|
|
|
// Parse JSON string into Map
|
|
// Parse JSON string into Map
|
|
|
final Map<String, dynamic> manifestMap = json.decode(manifestContent);
|
|
final Map<String, dynamic> manifestMap = json.decode(manifestContent);
|
|
@@ -143,12 +144,8 @@ class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
final Directory cacheDir = await createCacheDirectory();
|
|
final Directory cacheDir = await createCacheDirectory();
|
|
|
|
|
|
|
|
// if the cache directory is not empty and frame images count is equal to the zip file's files count
|
|
// if the cache directory is not empty and frame images count is equal to the zip file's files count
|
|
|
- if (cacheDir
|
|
|
|
|
- .listSync()
|
|
|
|
|
- .isNotEmpty &&
|
|
|
|
|
- cacheDir
|
|
|
|
|
- .listSync()
|
|
|
|
|
- .length == filePaths.length) {
|
|
|
|
|
|
|
+ if (cacheDir.listSync().isNotEmpty &&
|
|
|
|
|
+ cacheDir.listSync().length == filePaths.length) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -156,9 +153,7 @@ class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
for (final String path in filePaths) {
|
|
for (final String path in filePaths) {
|
|
|
final ByteData data = await rootBundle.load(path);
|
|
final ByteData data = await rootBundle.load(path);
|
|
|
final List<int> bytes = data.buffer.asUint8List();
|
|
final List<int> bytes = data.buffer.asUint8List();
|
|
|
- final File newFile = File('${cacheDir.path}/${path
|
|
|
|
|
- .split('/')
|
|
|
|
|
- .last}');
|
|
|
|
|
|
|
+ final File newFile = File('${cacheDir.path}/${path.split('/').last}');
|
|
|
if (newFile.existsSync()) {
|
|
if (newFile.existsSync()) {
|
|
|
newFile.deleteSync();
|
|
newFile.deleteSync();
|
|
|
} else {
|
|
} else {
|
|
@@ -183,8 +178,7 @@ class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
|
|
|
|
|
// create unique directory by framePath's md5
|
|
// create unique directory by framePath's md5
|
|
|
final Directory frameDir = Directory(
|
|
final Directory frameDir = Directory(
|
|
|
- '${cacheDir.path}/${md5.convert(utf8.encode(widget.framePath))
|
|
|
|
|
- .toString()}');
|
|
|
|
|
|
|
+ '${cacheDir.path}/${md5.convert(utf8.encode(widget.framePath)).toString()}');
|
|
|
|
|
|
|
|
// Check if the directory exists, if not, create it
|
|
// Check if the directory exists, if not, create it
|
|
|
if (!await frameDir.exists()) {
|
|
if (!await frameDir.exists()) {
|
|
@@ -207,10 +201,13 @@ class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
|
|
|
|
|
_timer = Timer.periodic(
|
|
_timer = Timer.periodic(
|
|
|
Duration(milliseconds: 1000 ~/ (widget.frameRate * speed)), (_) {
|
|
Duration(milliseconds: 1000 ~/ (widget.frameRate * speed)), (_) {
|
|
|
|
|
+ if (images.isEmpty) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
setState(() {
|
|
setState(() {
|
|
|
int targetFrame = (_currentFrame + 1) % imageFiles.length;
|
|
int targetFrame = (_currentFrame + 1) % imageFiles.length;
|
|
|
_currentFrame =
|
|
_currentFrame =
|
|
|
- targetFrame >= images.length ? images.length - 1 : targetFrame;
|
|
|
|
|
|
|
+ targetFrame >= images.length ? images.length - 1 : targetFrame;
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
@@ -222,10 +219,10 @@ class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
.listSync()
|
|
.listSync()
|
|
|
.map((file) => File(file.path))
|
|
.map((file) => File(file.path))
|
|
|
.where((file) =>
|
|
.where((file) =>
|
|
|
- file.path.endsWith('.png') ||
|
|
|
|
|
- file.path.endsWith('.jpg') ||
|
|
|
|
|
- file.path.endsWith('.jpeg') ||
|
|
|
|
|
- file.path.endsWith('.webp'))
|
|
|
|
|
|
|
+ file.path.endsWith('.png') ||
|
|
|
|
|
+ file.path.endsWith('.jpg') ||
|
|
|
|
|
+ file.path.endsWith('.jpeg') ||
|
|
|
|
|
+ file.path.endsWith('.webp'))
|
|
|
.toList();
|
|
.toList();
|
|
|
|
|
|
|
|
if (imageFiles.isEmpty) {
|
|
if (imageFiles.isEmpty) {
|
|
@@ -234,7 +231,8 @@ class FrameAnimationViewState extends State<FrameAnimationView> {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
precacheImageFiles() {
|
|
precacheImageFiles() {
|
|
|
- Stream<File>.fromIterable(imageFiles).asyncMap((file) async {
|
|
|
|
|
|
|
+ _precacheStreamSubscription =
|
|
|
|
|
+ Stream<File>.fromIterable(imageFiles).asyncMap((file) async {
|
|
|
final Uint8List bytes = await file.readAsBytes();
|
|
final Uint8List bytes = await file.readAsBytes();
|
|
|
final ui.Codec codec = await ui.instantiateImageCodec(bytes);
|
|
final ui.Codec codec = await ui.instantiateImageCodec(bytes);
|
|
|
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|
|
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|