Commit 642081ad9b0776a66dcaeee9cf5cf65d83700cb7

Authored by 吴启风
1 parent ae239ac7

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

lib/common/utils/show_star_reward_dialog.dart
1 import 'package:flutter/material.dart'; 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 void showStarRewardDialog(BuildContext context, { 5 void showStarRewardDialog(BuildContext context, {
5 double width = 200, 6 double width = 200,
@@ -13,7 +14,7 @@ void showStarRewardDialog(BuildContext context, { @@ -13,7 +14,7 @@ void showStarRewardDialog(BuildContext context, {
13 return Dialog( 14 return Dialog(
14 backgroundColor: Colors.transparent, // 设置对话框背景为透明 15 backgroundColor: Colors.transparent, // 设置对话框背景为透明
15 insetPadding: const EdgeInsets.all(0), // 去除对话框的内边距 16 insetPadding: const EdgeInsets.all(0), // 去除对话框的内边距
16 - child: StarRewardAnimation( 17 + child: StarRewardWidget(
17 width: width, 18 width: width,
18 height: height, 19 height: height,
19 isPlaying: true, 20 isPlaying: true,
lib/common/widgets/record_playback_widget.dart
@@ -22,15 +22,15 @@ class _RecorderPlaybackWidget extends StatefulWidget { @@ -22,15 +22,15 @@ class _RecorderPlaybackWidget extends StatefulWidget {
22 }) : super(key: key); 22 }) : super(key: key);
23 23
24 @override 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 with SingleTickerProviderStateMixin { 30 with SingleTickerProviderStateMixin {
31 late final AnimationController _controller; 31 late final AnimationController _controller;
32 late final LottieComposition _composition; 32 late final LottieComposition _composition;
33 - static const String TAG = "_RecorderPlaybackWidget"; 33 + static const String TAG = "RecorderPlaybackWidget";
34 34
35 bool _isPlaying = false; 35 bool _isPlaying = false;
36 36
lib/common/widgets/recorder_widget.dart
@@ -28,7 +28,7 @@ class RecorderWidget extends StatefulWidget { @@ -28,7 +28,7 @@ class RecorderWidget extends StatefulWidget {
28 class _RecorderWidgetState extends State<RecorderWidget> 28 class _RecorderWidgetState extends State<RecorderWidget>
29 with SingleTickerProviderStateMixin { 29 with SingleTickerProviderStateMixin {
30 late final AnimationController _controller; 30 late final AnimationController _controller;
31 - late final LottieComposition _composition; 31 + late final Future<LottieComposition> _futureComposition;
32 static const String TAG = "RecorderWidget"; 32 static const String TAG = "RecorderWidget";
33 33
34 bool _isPlaying = false; 34 bool _isPlaying = false;
@@ -38,43 +38,60 @@ class _RecorderWidgetState extends State&lt;RecorderWidget&gt; @@ -38,43 +38,60 @@ class _RecorderWidgetState extends State&lt;RecorderWidget&gt;
38 super.initState(); 38 super.initState();
39 _controller = AnimationController(vsync: this); 39 _controller = AnimationController(vsync: this);
40 _loadComposition(); 40 _loadComposition();
  41 + if (widget.isPlaying) {
  42 + _playInitialAnimation();
  43 + }
41 44
42 _controller.addListener(() { 45 _controller.addListener(() {
43 // Log.d("$TAG addListener _controller=${_controller.status}"); 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 _playInitialAnimation(); 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 void _playInitialAnimation() { 70 void _playInitialAnimation() {
60 _controller.reset(); 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 _controller.repeat( 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 void _playFinalAnimation() { 87 void _playFinalAnimation() {
74 _controller.stop(); 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 void _resetAnimation() { 97 void _resetAnimation() {
@@ -85,20 +102,6 @@ class _RecorderWidgetState extends State&lt;RecorderWidget&gt; @@ -85,20 +102,6 @@ class _RecorderWidgetState extends State&lt;RecorderWidget&gt;
85 } 102 }
86 103
87 @override 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 void dispose() { 105 void dispose() {
103 _controller.dispose(); 106 _controller.dispose();
104 super.dispose(); 107 super.dispose();
@@ -109,15 +112,23 @@ class _RecorderWidgetState extends State&lt;RecorderWidget&gt; @@ -109,15 +112,23 @@ class _RecorderWidgetState extends State&lt;RecorderWidget&gt;
109 return ThrottledGestureDetector( 112 return ThrottledGestureDetector(
110 onTap: widget.isClickable ? widget.onTap : null, 113 onTap: widget.isClickable ? widget.onTap : null,
111 child: Opacity( 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,6 +70,8 @@ class _SpeakerWidgetState extends State&lt;SpeakerWidget&gt;
70 } 70 }
71 71
72 void _startAnimation() { 72 void _startAnimation() {
  73 + Log.d(
  74 + "$TAG _startAnimation widget=${widget.isPlaying} _isPlaying=$_isPlaying _controller.isAnimating=${_controller.isAnimating}");
73 _timer = Timer.periodic(const Duration(milliseconds: 300), (Timer timer) { 75 _timer = Timer.periodic(const Duration(milliseconds: 300), (Timer timer) {
74 setState(() { 76 setState(() {
75 _currentFrame = ((_currentFrame + 1) % 3); 77 _currentFrame = ((_currentFrame + 1) % 3);
@@ -78,9 +80,15 @@ class _SpeakerWidgetState extends State&lt;SpeakerWidget&gt; @@ -78,9 +80,15 @@ class _SpeakerWidgetState extends State&lt;SpeakerWidget&gt;
78 } 80 }
79 81
80 void _stopAnimation() { 82 void _stopAnimation() {
  83 + Log.d(
  84 + "$TAG _stopAnimation widget=${widget.isPlaying} _isPlaying=$_isPlaying _controller.isAnimating=${_controller.isAnimating}");
81 _timer?.cancel(); 85 _timer?.cancel();
  86 + _controller.stop();
  87 + _controller.reset();
  88 +
82 setState(() { 89 setState(() {
83 _isPlaying = false; 90 _isPlaying = false;
  91 + // _controller.value = 0;
84 _currentFrame = 0; 92 _currentFrame = 0;
85 }); 93 });
86 } 94 }
lib/common/widgets/star_reward_widget.dart
1 import 'package:flutter/material.dart'; 1 import 'package:flutter/material.dart';
2 import 'package:lottie/lottie.dart'; 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 final double width; 7 final double width;
6 final double height; 8 final double height;
7 final bool isPlaying; 9 final bool isPlaying;
8 final int starCount; 10 final int starCount;
9 final VoidCallback onAnimationEnd; 11 final VoidCallback onAnimationEnd;
10 12
11 - const StarRewardAnimation({ 13 + const StarRewardWidget({
12 Key? key, 14 Key? key,
13 required this.width, 15 required this.width,
14 required this.height, 16 required this.height,
@@ -18,14 +20,15 @@ class StarRewardAnimation extends StatefulWidget { @@ -18,14 +20,15 @@ class StarRewardAnimation extends StatefulWidget {
18 }) : super(key: key); 20 }) : super(key: key);
19 21
20 @override 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 with SingleTickerProviderStateMixin { 27 with SingleTickerProviderStateMixin {
26 late final AnimationController _controller; 28 late final AnimationController _controller;
27 - late final LottieComposition _composition; 29 + late final Future<LottieComposition> _futureComposition;
28 bool _isVisible = false; 30 bool _isVisible = false;
  31 + static const String TAG = "StarRewardWidget";
29 32
30 @override 33 @override
31 void initState() { 34 void initState() {
@@ -35,41 +38,42 @@ class _StarRewardAnimationState extends State&lt;StarRewardAnimation&gt; @@ -35,41 +38,42 @@ class _StarRewardAnimationState extends State&lt;StarRewardAnimation&gt;
35 } 38 }
36 39
37 @override 40 @override
38 - void didUpdateWidget(StarRewardAnimation oldWidget) { 41 + void didUpdateWidget(StarRewardWidget oldWidget) {
39 super.didUpdateWidget(oldWidget); 42 super.didUpdateWidget(oldWidget);
40 if (widget.isPlaying && !_controller.isAnimating) { 43 if (widget.isPlaying && !_controller.isAnimating) {
41 _startAnimation(); 44 _startAnimation();
42 } 45 }
43 } 46 }
44 47
45 - Future<void> _loadComposition() async { 48 + void _loadComposition() {
46 // final composition = await AssetLottie('assets/lotties/recorder_input.zip').load(); 49 // final composition = await AssetLottie('assets/lotties/recorder_input.zip').load();
47 // setState(() { 50 // setState(() {
48 // _composition = composition; 51 // _composition = composition;
49 // _controller.duration = _composition.duration; 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 if (widget.isPlaying) { 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 setState(() { 64 setState(() {
65 _isVisible = true; 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,7 +93,7 @@ class _StarRewardAnimationState extends State&lt;StarRewardAnimation&gt;
89 assetPath = 'assets/lotties/star3_reward.zip'; 93 assetPath = 'assets/lotties/star3_reward.zip';
90 break; 94 break;
91 } 95 }
92 - return await AssetLottie(assetPath).load(); 96 + return await AssetLottie('assets/lotties/reward.zip').load();
93 } 97 }
94 98
95 @override 99 @override
@@ -102,16 +106,33 @@ class _StarRewardAnimationState extends State&lt;StarRewardAnimation&gt; @@ -102,16 +106,33 @@ class _StarRewardAnimationState extends State&lt;StarRewardAnimation&gt;
102 Widget build(BuildContext context) { 106 Widget build(BuildContext context) {
103 return Center( 107 return Center(
104 child: SizedBox( 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 width: widget.width, 109 width: widget.width,
112 height: widget.height, 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,10 +14,10 @@ import &#39;package:wow_english/models/course_process_entity.dart&#39;;
14 import 'package:wow_english/pages/section/subsection/base_section/bloc.dart'; 14 import 'package:wow_english/pages/section/subsection/base_section/bloc.dart';
15 import 'package:wow_english/pages/section/subsection/base_section/event.dart'; 15 import 'package:wow_english/pages/section/subsection/base_section/event.dart';
16 import 'package:wow_english/pages/section/subsection/base_section/state.dart'; 16 import 'package:wow_english/pages/section/subsection/base_section/state.dart';
17 -import 'package:wow_english/utils/loading.dart';  
18 import 'package:wow_english/utils/toast_util.dart'; 17 import 'package:wow_english/utils/toast_util.dart';
19 18
20 import '../../../common/permission/permissionRequester.dart'; 19 import '../../../common/permission/permissionRequester.dart';
  20 +import '../../../common/utils/show_star_reward_dialog.dart';
21 import '../../../route/route.dart'; 21 import '../../../route/route.dart';
22 22
23 part 'topic_picture_event.dart'; 23 part 'topic_picture_event.dart';
@@ -239,6 +239,7 @@ class TopicPictureBloc @@ -239,6 +239,7 @@ class TopicPictureBloc
239 _selectItem = event.selectIndex; 239 _selectItem = event.selectIndex;
240 if (checkAnswerRight(_selectItem) == true) { 240 if (checkAnswerRight(_selectItem) == true) {
241 _playResultSound(true); 241 _playResultSound(true);
  242 + showStarRewardDialog(context);
242 // showToast('恭喜你,答对啦!',duration: const Duration(seconds: 2)); 243 // showToast('恭喜你,答对啦!',duration: const Duration(seconds: 2));
243 } else { 244 } else {
244 _playResultSound(false); 245 _playResultSound(false);
@@ -299,7 +300,8 @@ class TopicPictureBloc @@ -299,7 +300,8 @@ class TopicPictureBloc
299 final Map args = event.message as Map; 300 final Map args = event.message as Map;
300 final result = args['result'] as Map; 301 final result = args['result'] as Map;
301 final overall = result['overall'].toString(); 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 _isRecording = false; 305 _isRecording = false;
304 emitter(XSVoiceTestState()); 306 emitter(XSVoiceTestState());
305 if (isLastPage()) { 307 if (isLastPage()) {
@@ -307,6 +309,24 @@ class TopicPictureBloc @@ -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 void _voicePlayStateChange(VoicePlayStateChangeEvent event, 331 void _voicePlayStateChange(VoicePlayStateChangeEvent event,
312 Emitter<TopicPictureState> emitter) async { 332 Emitter<TopicPictureState> emitter) async {
lib/pages/practice/widgets/shake_widget.dart
@@ -26,6 +26,7 @@ class ShakeWidget extends StatefulWidget { @@ -26,6 +26,7 @@ class ShakeWidget extends StatefulWidget {
26 class _ShakeWidgetState extends State<ShakeWidget> with SingleTickerProviderStateMixin { 26 class _ShakeWidgetState extends State<ShakeWidget> with SingleTickerProviderStateMixin {
27 late AnimationController _controller; 27 late AnimationController _controller;
28 late Animation<double> _animation; 28 late Animation<double> _animation;
  29 + static const String TAG = "ShakeWidget";
29 30
30 @override 31 @override
31 void initState() { 32 void initState() {
@@ -67,10 +68,12 @@ class _ShakeWidgetState extends State&lt;ShakeWidget&gt; with SingleTickerProviderStat @@ -67,10 +68,12 @@ class _ShakeWidgetState extends State&lt;ShakeWidget&gt; with SingleTickerProviderStat
67 @override 68 @override
68 void didUpdateWidget(covariant ShakeWidget oldWidget) { 69 void didUpdateWidget(covariant ShakeWidget oldWidget) {
69 super.didUpdateWidget(oldWidget); 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 // if (widget.shouldShake && !oldWidget.shouldShake) { 72 // if (widget.shouldShake && !oldWidget.shouldShake) {
72 - if (widget.shouldShake) { 73 + if (widget.shouldShake && !_controller.isAnimating) {
73 _controller.forward(from: 0.0); 74 _controller.forward(from: 0.0);
  75 + } else if (!widget.shouldShake && _controller.isAnimating) {
  76 + _controller.stop();
74 } 77 }
75 } 78 }
76 79