Commit 642081ad9b0776a66dcaeee9cf5cf65d83700cb7

Authored by 吴启风
1 parent ae239ac7

feat:lottie动画加载优化-异步加载

lib/common/utils/show_star_reward_dialog.dart
1 1 import 'package:flutter/material.dart';
2   -import 'package:wow_english/common/widgets/star_reward_widget.dart';
  2 +
  3 +import '../widgets/star_reward_widget.dart';
3 4  
4 5 void showStarRewardDialog(BuildContext context, {
5 6 double width = 200,
... ... @@ -13,7 +14,7 @@ void showStarRewardDialog(BuildContext context, {
13 14 return Dialog(
14 15 backgroundColor: Colors.transparent, // 设置对话框背景为透明
15 16 insetPadding: const EdgeInsets.all(0), // 去除对话框的内边距
16   - child: StarRewardAnimation(
  17 + child: StarRewardWidget(
17 18 width: width,
18 19 height: height,
19 20 isPlaying: true,
... ...
lib/common/widgets/record_playback_widget.dart
... ... @@ -22,15 +22,15 @@ class _RecorderPlaybackWidget extends StatefulWidget {
22 22 }) : super(key: key);
23 23  
24 24 @override
25   - __RecorderPlaybackWidgetState createState() =>
26   - __RecorderPlaybackWidgetState();
  25 + _RecorderPlaybackWidgetState createState() =>
  26 + _RecorderPlaybackWidgetState();
27 27 }
28 28  
29   -class __RecorderPlaybackWidgetState extends State<_RecorderPlaybackWidget>
  29 +class _RecorderPlaybackWidgetState extends State<_RecorderPlaybackWidget>
30 30 with SingleTickerProviderStateMixin {
31 31 late final AnimationController _controller;
32 32 late final LottieComposition _composition;
33   - static const String TAG = "_RecorderPlaybackWidget";
  33 + static const String TAG = "RecorderPlaybackWidget";
34 34  
35 35 bool _isPlaying = false;
36 36  
... ...
lib/common/widgets/recorder_widget.dart
... ... @@ -28,7 +28,7 @@ class RecorderWidget extends StatefulWidget {
28 28 class _RecorderWidgetState extends State<RecorderWidget>
29 29 with SingleTickerProviderStateMixin {
30 30 late final AnimationController _controller;
31   - late final LottieComposition _composition;
  31 + late final Future<LottieComposition> _futureComposition;
32 32 static const String TAG = "RecorderWidget";
33 33  
34 34 bool _isPlaying = false;
... ... @@ -38,43 +38,60 @@ class _RecorderWidgetState extends State&lt;RecorderWidget&gt;
38 38 super.initState();
39 39 _controller = AnimationController(vsync: this);
40 40 _loadComposition();
  41 + if (widget.isPlaying) {
  42 + _playInitialAnimation();
  43 + }
41 44  
42 45 _controller.addListener(() {
43 46 // Log.d("$TAG addListener _controller=${_controller.status}");
44 47 });
45 48 }
46 49  
47   - Future<void> _loadComposition() async {
48   - final composition = await AssetLottie('assets/lotties/recorder_input.zip').load();
49   - setState(() {
50   - _composition = composition;
51   - _controller.duration = _composition.duration;
52   - });
53   -
54   - if (widget.isPlaying) {
  50 + @override
  51 + void didUpdateWidget(RecorderWidget oldWidget) {
  52 + super.didUpdateWidget(oldWidget);
  53 + Log.d(
  54 + "$TAG didUpdateWidget widget=${widget.isPlaying} oldWidget=${oldWidget.isPlaying} _isPlaying=$_isPlaying");
  55 + if (widget.isPlaying && !_isPlaying) {
  56 + setState(() {
  57 + _isPlaying = true;
  58 + });
55 59 _playInitialAnimation();
  60 + } else if (!widget.isPlaying && _isPlaying) {
  61 + _playFinalAnimation();
56 62 }
57 63 }
58 64  
  65 + Future<void> _loadComposition() async {
  66 + _futureComposition =
  67 + AssetLottie('assets/lotties/recorder_input.zip').load();
  68 + }
  69 +
59 70 void _playInitialAnimation() {
60 71 _controller.reset();
61   - _controller
62   - .animateTo(22 / _composition.endFrame)
63   - .whenComplete(() => _loopMiddleAnimation());
  72 + _futureComposition.then((composition) {
  73 + _controller.duration = composition.duration;
  74 + _controller
  75 + .animateTo(22 / composition.endFrame)
  76 + .whenComplete(() => _loopMiddleAnimation(composition));
  77 + });
64 78 }
65 79  
66   - void _loopMiddleAnimation() {
  80 + void _loopMiddleAnimation(LottieComposition composition) {
67 81 _controller.repeat(
68   - min: 22 / _composition.endFrame,
69   - max: 37 / _composition.endFrame,
  82 + min: 22 / composition.endFrame,
  83 + max: 37 / composition.endFrame,
70 84 );
71 85 }
72 86  
73 87 void _playFinalAnimation() {
74 88 _controller.stop();
75   - _controller
76   - .animateTo(50 / _composition.endFrame)
77   - .whenComplete(() => _resetAnimation());
  89 + _futureComposition.then((composition) {
  90 + _controller.duration = composition.duration;
  91 + _controller
  92 + .animateTo(50 / composition.endFrame)
  93 + .whenComplete(() => _resetAnimation());
  94 + });
78 95 }
79 96  
80 97 void _resetAnimation() {
... ... @@ -85,20 +102,6 @@ class _RecorderWidgetState extends State&lt;RecorderWidget&gt;
85 102 }
86 103  
87 104 @override
88   - void didUpdateWidget(RecorderWidget oldWidget) {
89   - super.didUpdateWidget(oldWidget);
90   - Log.d("$TAG didUpdateWidget widget=${widget.isPlaying} oldWidget=${oldWidget.isPlaying} _isPlaying=$_isPlaying");
91   - if (widget.isPlaying && !_isPlaying) {
92   - setState(() {
93   - _isPlaying = true;
94   - });
95   - _playInitialAnimation();
96   - } else if (!widget.isPlaying && _isPlaying) {
97   - _playFinalAnimation();
98   - }
99   - }
100   -
101   - @override
102 105 void dispose() {
103 106 _controller.dispose();
104 107 super.dispose();
... ... @@ -109,15 +112,23 @@ class _RecorderWidgetState extends State&lt;RecorderWidget&gt;
109 112 return ThrottledGestureDetector(
110 113 onTap: widget.isClickable ? widget.onTap : null,
111 114 child: Opacity(
112   - opacity: widget.isClickable ? 1.0 : 0.5, // 设置透明度
113   - child: Lottie(
114   - composition: _composition,
115   - controller: _controller,
116   - renderCache: RenderCache.raster,
117   - width: widget.width,
118   - height: widget.height,
119   - ),
120   - ),
  115 + opacity: widget.isClickable ? 1.0 : 0.5, // 设置透明度
  116 + child: FutureBuilder<LottieComposition>(
  117 + future: _futureComposition,
  118 + builder: (context, snapshot) {
  119 + if (snapshot.hasData) {
  120 + final composition = snapshot.data!;
  121 + return Lottie(
  122 + composition: composition,
  123 + controller: _controller,
  124 + renderCache: RenderCache.raster,
  125 + width: widget.width,
  126 + height: widget.height,
  127 + );
  128 + } else {
  129 + return const SizedBox.shrink();
  130 + }
  131 + })),
121 132 );
122 133 }
123 134 }
... ...
lib/common/widgets/speaker_widget.dart
... ... @@ -70,6 +70,8 @@ class _SpeakerWidgetState extends State&lt;SpeakerWidget&gt;
70 70 }
71 71  
72 72 void _startAnimation() {
  73 + Log.d(
  74 + "$TAG _startAnimation widget=${widget.isPlaying} _isPlaying=$_isPlaying _controller.isAnimating=${_controller.isAnimating}");
73 75 _timer = Timer.periodic(const Duration(milliseconds: 300), (Timer timer) {
74 76 setState(() {
75 77 _currentFrame = ((_currentFrame + 1) % 3);
... ... @@ -78,9 +80,15 @@ class _SpeakerWidgetState extends State&lt;SpeakerWidget&gt;
78 80 }
79 81  
80 82 void _stopAnimation() {
  83 + Log.d(
  84 + "$TAG _stopAnimation widget=${widget.isPlaying} _isPlaying=$_isPlaying _controller.isAnimating=${_controller.isAnimating}");
81 85 _timer?.cancel();
  86 + _controller.stop();
  87 + _controller.reset();
  88 +
82 89 setState(() {
83 90 _isPlaying = false;
  91 + // _controller.value = 0;
84 92 _currentFrame = 0;
85 93 });
86 94 }
... ...
lib/common/widgets/star_reward_widget.dart
1 1 import 'package:flutter/material.dart';
2 2 import 'package:lottie/lottie.dart';
3 3  
4   -class StarRewardAnimation extends StatefulWidget {
  4 +import '../../utils/log_util.dart';
  5 +
  6 +class StarRewardWidget extends StatefulWidget {
5 7 final double width;
6 8 final double height;
7 9 final bool isPlaying;
8 10 final int starCount;
9 11 final VoidCallback onAnimationEnd;
10 12  
11   - const StarRewardAnimation({
  13 + const StarRewardWidget({
12 14 Key? key,
13 15 required this.width,
14 16 required this.height,
... ... @@ -18,14 +20,15 @@ class StarRewardAnimation extends StatefulWidget {
18 20 }) : super(key: key);
19 21  
20 22 @override
21   - _StarRewardAnimationState createState() => _StarRewardAnimationState();
  23 + _StarRewardWidgetState createState() => _StarRewardWidgetState();
22 24 }
23 25  
24   -class _StarRewardAnimationState extends State<StarRewardAnimation>
  26 +class _StarRewardWidgetState extends State<StarRewardWidget>
25 27 with SingleTickerProviderStateMixin {
26 28 late final AnimationController _controller;
27   - late final LottieComposition _composition;
  29 + late final Future<LottieComposition> _futureComposition;
28 30 bool _isVisible = false;
  31 + static const String TAG = "StarRewardWidget";
29 32  
30 33 @override
31 34 void initState() {
... ... @@ -35,41 +38,42 @@ class _StarRewardAnimationState extends State&lt;StarRewardAnimation&gt;
35 38 }
36 39  
37 40 @override
38   - void didUpdateWidget(StarRewardAnimation oldWidget) {
  41 + void didUpdateWidget(StarRewardWidget oldWidget) {
39 42 super.didUpdateWidget(oldWidget);
40 43 if (widget.isPlaying && !_controller.isAnimating) {
41 44 _startAnimation();
42 45 }
43 46 }
44 47  
45   - Future<void> _loadComposition() async {
  48 + void _loadComposition() {
46 49 // final composition = await AssetLottie('assets/lotties/recorder_input.zip').load();
47 50 // setState(() {
48 51 // _composition = composition;
49 52 // _controller.duration = _composition.duration;
50 53 // });
51 54  
52   - final composition = await _loadLottieComposition();
53   - setState(() {
54   - _composition = composition;
55   - _controller.duration = _composition.duration;
56   - });
  55 + _futureComposition = _loadLottieComposition();
57 56  
58 57 if (widget.isPlaying) {
59   - _startAnimation();
  58 + _startAnimation();
60 59 }
61 60 }
62 61  
63   - void _startAnimation() async {
  62 + void _startAnimation() {
  63 + Log.d("$TAG _startAnimation");
64 64 setState(() {
65 65 _isVisible = true;
66 66 });
67 67  
68   - _controller.forward().whenComplete(() {
69   - setState(() {
70   - _isVisible = false;
  68 + _futureComposition.then((composition) {
  69 + Log.d("$TAG _futureComposition.then duration=${composition.duration}");
  70 + _controller.duration = composition.duration;
  71 + _controller.forward().whenComplete(() {
  72 + setState(() {
  73 + _isVisible = false;
  74 + });
  75 + widget.onAnimationEnd(); // 调用外部回调函数
71 76 });
72   - widget.onAnimationEnd(); // 调用外部回调函数
73 77 });
74 78 }
75 79  
... ... @@ -89,7 +93,7 @@ class _StarRewardAnimationState extends State&lt;StarRewardAnimation&gt;
89 93 assetPath = 'assets/lotties/star3_reward.zip';
90 94 break;
91 95 }
92   - return await AssetLottie(assetPath).load();
  96 + return await AssetLottie('assets/lotties/reward.zip').load();
93 97 }
94 98  
95 99 @override
... ... @@ -102,16 +106,33 @@ class _StarRewardAnimationState extends State&lt;StarRewardAnimation&gt;
102 106 Widget build(BuildContext context) {
103 107 return Center(
104 108 child: SizedBox(
105   - width: widget.width,
106   - height: widget.height,
107   - child: Lottie(
108   - composition: _composition,
109   - controller: _controller,
110   - renderCache: RenderCache.raster,
111 109 width: widget.width,
112 110 height: widget.height,
113   - ),
114   - ),
  111 + child: FutureBuilder<LottieComposition>(
  112 + future: _futureComposition,
  113 + builder: (context, snapshot) {
  114 + if (snapshot.hasData) {
  115 + final composition = snapshot.data!;
  116 + return Lottie(
  117 + composition: composition,
  118 + controller: _controller,
  119 + renderCache: RenderCache.raster,
  120 + width: widget.width,
  121 + height: widget.height,
  122 + );
  123 + } else {
  124 + return const SizedBox.shrink();
  125 + }
  126 + })
  127 +
  128 + // child: Lottie(
  129 + // composition: _composition,
  130 + // controller: _controller,
  131 + // renderCache: RenderCache.raster,
  132 + // width: widget.width,
  133 + // height: widget.height,
  134 + // ),
  135 + ),
115 136 );
116 137 }
117 138 }
... ...
lib/pages/practice/bloc/topic_picture_bloc.dart
... ... @@ -14,10 +14,10 @@ import &#39;package:wow_english/models/course_process_entity.dart&#39;;
14 14 import 'package:wow_english/pages/section/subsection/base_section/bloc.dart';
15 15 import 'package:wow_english/pages/section/subsection/base_section/event.dart';
16 16 import 'package:wow_english/pages/section/subsection/base_section/state.dart';
17   -import 'package:wow_english/utils/loading.dart';
18 17 import 'package:wow_english/utils/toast_util.dart';
19 18  
20 19 import '../../../common/permission/permissionRequester.dart';
  20 +import '../../../common/utils/show_star_reward_dialog.dart';
21 21 import '../../../route/route.dart';
22 22  
23 23 part 'topic_picture_event.dart';
... ... @@ -239,6 +239,7 @@ class TopicPictureBloc
239 239 _selectItem = event.selectIndex;
240 240 if (checkAnswerRight(_selectItem) == true) {
241 241 _playResultSound(true);
  242 + showStarRewardDialog(context);
242 243 // showToast('恭喜你,答对啦!',duration: const Duration(seconds: 2));
243 244 } else {
244 245 _playResultSound(false);
... ... @@ -299,7 +300,8 @@ class TopicPictureBloc
299 300 final Map args = event.message as Map;
300 301 final result = args['result'] as Map;
301 302 final overall = result['overall'].toString();
302   - showToast('测评成功,分数是$overall', duration: const Duration(seconds: 5));
  303 + showStarRewardDialog(context, starCount: _evaluateScore(overall));
  304 + // showToast('测评成功,分数是$overall', duration: const Duration(seconds: 5));
303 305 _isRecording = false;
304 306 emitter(XSVoiceTestState());
305 307 if (isLastPage()) {
... ... @@ -307,6 +309,24 @@ class TopicPictureBloc
307 309 }
308 310 }
309 311  
  312 + /// 根据得分计算星星数
  313 + int _evaluateScore(String scoreStr) {
  314 + try {
  315 + int score = int.parse(scoreStr);
  316 + if (score > 80) {
  317 + return 3;
  318 + } else if (score > 60) {
  319 + return 2;
  320 + } else {
  321 + return 1;
  322 + }
  323 + } catch (e) {
  324 + // 如果转换失败,可以返回一个默认值或抛出异常
  325 + print('Error parsing score: $e');
  326 + return 1; // 返回一个默认值表示错误
  327 + }
  328 + }
  329 +
310 330 // 暂时没用上
311 331 void _voicePlayStateChange(VoicePlayStateChangeEvent event,
312 332 Emitter<TopicPictureState> emitter) async {
... ...
lib/pages/practice/widgets/shake_widget.dart
... ... @@ -26,6 +26,7 @@ class ShakeWidget extends StatefulWidget {
26 26 class _ShakeWidgetState extends State<ShakeWidget> with SingleTickerProviderStateMixin {
27 27 late AnimationController _controller;
28 28 late Animation<double> _animation;
  29 + static const String TAG = "ShakeWidget";
29 30  
30 31 @override
31 32 void initState() {
... ... @@ -67,10 +68,12 @@ class _ShakeWidgetState extends State&lt;ShakeWidget&gt; with SingleTickerProviderStat
67 68 @override
68 69 void didUpdateWidget(covariant ShakeWidget oldWidget) {
69 70 super.didUpdateWidget(oldWidget);
70   - Log.d("WQF didUpdateWidget widget.shouldShake=${widget.shouldShake} oldWidget.shouldShake=${oldWidget.shouldShake}");
  71 + Log.d("$TAG didUpdateWidget widget.shouldShake=${widget.shouldShake} oldWidget.shouldShake=${oldWidget.shouldShake} isAnimating=${_controller.isAnimating}");
71 72 // if (widget.shouldShake && !oldWidget.shouldShake) {
72   - if (widget.shouldShake) {
  73 + if (widget.shouldShake && !_controller.isAnimating) {
73 74 _controller.forward(from: 0.0);
  75 + } else if (!widget.shouldShake && _controller.isAnimating) {
  76 + _controller.stop();
74 77 }
75 78 }
76 79  
... ...