diff --git a/assets/images/ic_speaker_play0.png b/assets/images/ic_speaker_play0.png new file mode 100644 index 0000000..b1f6ebb --- /dev/null +++ b/assets/images/ic_speaker_play0.png diff --git a/assets/images/ic_speaker_play1.png b/assets/images/ic_speaker_play1.png new file mode 100644 index 0000000..bcbb965 --- /dev/null +++ b/assets/images/ic_speaker_play1.png diff --git a/assets/images/ic_speaker_play2.png b/assets/images/ic_speaker_play2.png new file mode 100644 index 0000000..5ca55c4 --- /dev/null +++ b/assets/images/ic_speaker_play2.png 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;