From 56b412f55b434738abfb155887550af442202b7f Mon Sep 17 00:00:00 2001 From: wuqifeng <540416539@qq.com> Date: Tue, 30 Jul 2024 23:59:24 +0800 Subject: [PATCH] feat:扬声器播放动画组件封装 --- assets/images/ic_speaker_play0.png | Bin 0 -> 12998 bytes assets/images/ic_speaker_play1.png | Bin 0 -> 13923 bytes assets/images/ic_speaker_play2.png | Bin 0 -> 15377 bytes lib/common/widgets/speaker_widget.dart | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/pages/practice/topic_picture_page.dart | 14 +++++++++----- lib/pages/practice/widgets/shake_widget.dart | 1 + 6 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 assets/images/ic_speaker_play0.png create mode 100644 assets/images/ic_speaker_play1.png create mode 100644 assets/images/ic_speaker_play2.png create mode 100644 lib/common/widgets/speaker_widget.dart diff --git a/assets/images/ic_speaker_play0.png b/assets/images/ic_speaker_play0.png new file mode 100644 index 0000000..b1f6ebb Binary files /dev/null and b/assets/images/ic_speaker_play0.png differ diff --git a/assets/images/ic_speaker_play1.png b/assets/images/ic_speaker_play1.png new file mode 100644 index 0000000..bcbb965 Binary files /dev/null and b/assets/images/ic_speaker_play1.png differ diff --git a/assets/images/ic_speaker_play2.png b/assets/images/ic_speaker_play2.png new file mode 100644 index 0000000..5ca55c4 Binary files /dev/null and b/assets/images/ic_speaker_play2.png differ diff --git a/lib/common/widgets/speaker_widget.dart b/lib/common/widgets/speaker_widget.dart new file mode 100644 index 0000000..1851fd9 --- /dev/null +++ b/lib/common/widgets/speaker_widget.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:wow_english/common/extension/string_extension.dart'; + +/// 扬声器播放组件,帧动画 +class SpeakerWidget extends StatefulWidget { + final bool isPlaying; + final bool isClickable; + final double width; + final double height; + final VoidCallback? onTap; + + SpeakerWidget({ + super.key, + this.isPlaying = false, + this.isClickable = true, + required this.width, + required this.height, + this.onTap, + }); + + @override + _SpeakerWidgetState createState() => _SpeakerWidgetState(); +} + +class _SpeakerWidgetState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + int _currentFrame = 0; + Timer? _timer; + final List _speakerImagePaths = [ + 'ic_speaker_play2'.assetPng, + 'ic_speaker_play0'.assetPng, + 'ic_speaker_play1'.assetPng, + ]; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(milliseconds: 300), // 每帧的持续时间 + vsync: this, + ); + + if (widget.isPlaying) { + _startAnimation(); + } + } + + @override + void didUpdateWidget(SpeakerWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.isPlaying != oldWidget.isPlaying) { + if (widget.isPlaying) { + _startAnimation(); + } else { + _stopAnimation(); + } + } + } + + void _startAnimation() { + _timer = Timer.periodic(const Duration(milliseconds: 300), (Timer timer) { + setState(() { + _currentFrame = ((_currentFrame + 1) % 3); + }); + }); + } + + void _stopAnimation() { + _timer?.cancel(); + setState(() { + _currentFrame = 0; + }); + } + + @override + void dispose() { + _controller.dispose(); + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: widget.isClickable ? widget.onTap : null, + child: Image.asset( + _speakerImagePaths[_currentFrame], + width: widget.width, + height: widget.height, + fit: BoxFit.cover, + ), + ); + } +} diff --git a/lib/pages/practice/topic_picture_page.dart b/lib/pages/practice/topic_picture_page.dart index 5d1c554..1090904 100644 --- a/lib/pages/practice/topic_picture_page.dart +++ b/lib/pages/practice/topic_picture_page.dart @@ -11,7 +11,9 @@ import 'package:wow_english/pages/practice/widgets/shake_widget.dart'; import 'package:wow_english/route/route.dart'; import 'package:wow_english/utils/toast_util.dart'; +import '../../common/widgets/speaker_widget.dart'; import '../../common/widgets/throttledGesture_gesture_detector.dart'; +import '../../utils/log_util.dart'; import 'bloc/topic_picture_bloc.dart'; import 'widgets/practice_header_widget.dart'; @@ -513,12 +515,14 @@ class _TopicPicturePage extends StatelessWidget { }, child: Row( children: [ - Image.asset( - bloc.voicePlayState == VoicePlayState.playing - ? 'reade_answer'.assetGif - : 'voice'.assetPng, - height: 45.h, + SpeakerWidget( + isPlaying: bloc.voicePlayState == VoicePlayState.playing, // 控制动画播放 + isClickable: true, // 控制是否可点击 width: 45.w, + height: 45.w, + onTap: () { + Log.d("Speaker tapped"); + }, ), 10.horizontalSpace, Text(topics?.word ?? '') diff --git a/lib/pages/practice/widgets/shake_widget.dart b/lib/pages/practice/widgets/shake_widget.dart index 7336fd1..80ee342 100644 --- a/lib/pages/practice/widgets/shake_widget.dart +++ b/lib/pages/practice/widgets/shake_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import '../../../utils/log_util.dart'; +///左右晃动组件,用于答错场景 class ShakeWidget extends StatefulWidget { final Widget child; final int shakeCount; -- libgit2 0.22.2