242 lines
6.3 KiB
Dart
242 lines
6.3 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter_svga/flutter_svga.dart';
|
||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||
|
||
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
|
||
|
||
class SVGAHeadwearWidget extends StatefulWidget {
|
||
// 资源路径(网络URL或本地assets路径)
|
||
final String resource;
|
||
|
||
// 尺寸
|
||
final double? width;
|
||
final double? height;
|
||
|
||
// 动画控制参数
|
||
final bool autoPlay;
|
||
final int loops; // 循环次数(0表示无限循环)
|
||
final bool clearsAfterStop;
|
||
|
||
// 缓存控制
|
||
final bool useCache;
|
||
|
||
// 回调函数
|
||
final VoidCallback? onCompleted;
|
||
final Function(String)? onError;
|
||
final VoidCallback? onStartLoading;
|
||
final VoidCallback? onFinishLoading;
|
||
|
||
const SVGAHeadwearWidget({
|
||
Key? key,
|
||
required this.resource,
|
||
this.width,
|
||
this.height,
|
||
this.autoPlay = true,
|
||
this.loops = 0,
|
||
this.clearsAfterStop = true,
|
||
this.useCache = true,
|
||
this.onCompleted,
|
||
this.onError,
|
||
this.onStartLoading,
|
||
this.onFinishLoading,
|
||
}) : super(key: key);
|
||
|
||
@override
|
||
_SVGAHeadwearWidgetState createState() => _SVGAHeadwearWidgetState();
|
||
}
|
||
|
||
class _SVGAHeadwearWidgetState extends State<SVGAHeadwearWidget>
|
||
with SingleTickerProviderStateMixin {
|
||
SVGAAnimationController? _animationController;
|
||
final Set<String> _retainedSvgaResources = <String>{};
|
||
bool _isLoading = true;
|
||
bool _hasError = false;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_animationController = SVGAAnimationController(vsync: this);
|
||
|
||
if (widget.autoPlay) {
|
||
_loadAnimation();
|
||
}
|
||
|
||
// 添加动画状态监听
|
||
_animationController?.addStatusListener((status) {
|
||
if (status == AnimationStatus.completed && widget.onCompleted != null) {
|
||
widget.onCompleted!();
|
||
}
|
||
});
|
||
}
|
||
|
||
@override
|
||
void didUpdateWidget(covariant SVGAHeadwearWidget oldWidget) {
|
||
super.didUpdateWidget(oldWidget);
|
||
if (oldWidget.resource != widget.resource ||
|
||
oldWidget.useCache != widget.useCache) {
|
||
_loadAnimation();
|
||
}
|
||
}
|
||
|
||
// 加载动画(带缓存)
|
||
Future<void> _loadAnimation() async {
|
||
if (widget.onStartLoading != null) {
|
||
widget.onStartLoading!();
|
||
}
|
||
|
||
setState(() {
|
||
_isLoading = true;
|
||
_hasError = false;
|
||
});
|
||
|
||
final resource = widget.resource.trim();
|
||
var retainedForLoad = false;
|
||
try {
|
||
retainedForLoad = _retainSvgaResourceIfNeeded(resource);
|
||
final videoItem = await SCGiftVapSvgaManager().loadSvgaEntity(
|
||
resource,
|
||
retain: retainedForLoad,
|
||
);
|
||
|
||
if (mounted && widget.resource.trim() == resource) {
|
||
setState(() {
|
||
_animationController?.videoItem = videoItem;
|
||
_isLoading = false;
|
||
});
|
||
_releaseRetainedSvgaResourcesExcept(resource);
|
||
|
||
// 根据循环次数设置播放方式
|
||
if (widget.loops == 0) {
|
||
_animationController?.repeat();
|
||
} else {
|
||
_animationController?.repeat(count: widget.loops);
|
||
}
|
||
|
||
if (widget.onFinishLoading != null) {
|
||
widget.onFinishLoading!();
|
||
}
|
||
} else if (retainedForLoad) {
|
||
_releaseSvgaResource(resource);
|
||
}
|
||
} catch (e) {
|
||
if (retainedForLoad) {
|
||
_releaseSvgaResource(resource);
|
||
}
|
||
if (mounted) {
|
||
setState(() {
|
||
_isLoading = false;
|
||
_hasError = true;
|
||
});
|
||
}
|
||
|
||
if (widget.onError != null) {
|
||
SCTts.show("fail-${e.toString()}");
|
||
widget.onError!(e.toString());
|
||
}
|
||
}
|
||
}
|
||
|
||
bool _retainSvgaResourceIfNeeded(String resource) {
|
||
if (_retainedSvgaResources.contains(resource)) {
|
||
return false;
|
||
}
|
||
_retainedSvgaResources.add(resource);
|
||
return true;
|
||
}
|
||
|
||
void _releaseSvgaResource(String resource, {bool evict = false}) {
|
||
final shouldRelease = _retainedSvgaResources.remove(resource);
|
||
if (shouldRelease || evict) {
|
||
SCGiftVapSvgaManager().removeSvgaEntity(resource, evict: evict);
|
||
}
|
||
}
|
||
|
||
void _releaseRetainedSvgaResourcesExcept(
|
||
String? activeResource, {
|
||
bool evict = false,
|
||
}) {
|
||
final resources = _retainedSvgaResources.toList(growable: false);
|
||
for (final resource in resources) {
|
||
if (resource != activeResource) {
|
||
_releaseSvgaResource(resource, evict: evict);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 播放动画
|
||
void play() {
|
||
if (_animationController?.videoItem != null) {
|
||
_animationController?.reset();
|
||
if (widget.loops == 0) {
|
||
_animationController?.repeat();
|
||
} else {
|
||
_animationController?.repeat(count: widget.loops);
|
||
}
|
||
} else {
|
||
_loadAnimation();
|
||
}
|
||
}
|
||
|
||
// 暂停动画
|
||
void pause() {
|
||
_animationController?.stop();
|
||
}
|
||
|
||
// 停止动画
|
||
void stop() {
|
||
_animationController?.stop();
|
||
if (widget.clearsAfterStop) {
|
||
final resource = widget.resource.trim();
|
||
_animationController?.videoItem = null;
|
||
_releaseSvgaResource(resource, evict: !widget.useCache);
|
||
}
|
||
}
|
||
|
||
// 重新加载动画
|
||
void reload() {
|
||
final resource = widget.resource.trim();
|
||
_animationController?.stop();
|
||
_animationController?.videoItem = null;
|
||
_releaseSvgaResource(resource, evict: true);
|
||
_loadAnimation();
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_animationController?.stop();
|
||
_animationController?.videoItem = null;
|
||
_releaseRetainedSvgaResourcesExcept(null, evict: !widget.useCache);
|
||
_animationController?.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
if (_isLoading && !widget.autoPlay) {
|
||
return Container(
|
||
width: widget.width,
|
||
height: widget.height,
|
||
child: Center(child: CircularProgressIndicator()),
|
||
);
|
||
}
|
||
|
||
if (_hasError) {
|
||
return Container();
|
||
}
|
||
|
||
return Container(
|
||
width: widget.width,
|
||
height: widget.height,
|
||
child:
|
||
_animationController != null &&
|
||
_animationController!.videoItem != null
|
||
? SVGAImage(
|
||
_animationController!,
|
||
fit: BoxFit.fill,
|
||
clearsAfterStop: widget.clearsAfterStop,
|
||
)
|
||
: Container(),
|
||
);
|
||
}
|
||
}
|