1、增加悬浮按钮的小声波效果[未使用]

2、增加底部按钮的大声波效果
3、更换footer底部图标
This commit is contained in:
2025-08-12 16:47:34 +08:00
parent 130755f9e1
commit 4035804b73
5 changed files with 239 additions and 36 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

View File

@@ -4,34 +4,76 @@ import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../services/message_service.dart'; import '../services/message_service.dart';
import '../utils/common_util.dart'; import '../utils/common_util.dart';
import 'voice_animation.dart'; import 'large_audio_wave.dart';
class ChatFooter extends StatelessWidget { class ChatFooter extends StatefulWidget {
final VoidCallback? onClear; final VoidCallback? onClear;
const ChatFooter({super.key, this.onClear}); const ChatFooter({super.key, this.onClear});
@override
State<ChatFooter> createState() => _ChatFooterState();
}
class _ChatFooterState extends State<ChatFooter>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// 定义颜色常量
const Color buttonColor = Color(0xFF6F72F1); // 主题紫色(应用栏和按钮边框)
return Container( return Container(
color: Colors.transparent, color: Colors.transparent,
child: Row( child: Row(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(left: 12, bottom: 12), padding: const EdgeInsets.only(left: 16, bottom: 12),
child: IconButton( child: GestureDetector(
icon: const Icon(Icons.delete_outline, onTap: widget.onClear,
color: CommonUtil.commonColor), child: Image.asset(
iconSize: 30, 'assets/images/history_mini.png',
tooltip: '清除会话', width: 24,
onPressed: onClear, height: 24,
),
), ),
), ),
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.only(bottom: 12), key: const Key('startBtn'),
padding: const EdgeInsets.only(bottom: 12, left: 16, right: 16),
child: Consumer<MessageService>( child: Consumer<MessageService>(
builder: (context, messageService, child) { builder: (context, messageService, child) {
// 立即响应录音状态变化,无延迟启动动画
WidgetsBinding.instance.addPostFrameCallback((_) {
if (messageService.isRecording) {
if (!_animationController.isAnimating) {
_animationController.repeat();
}
} else {
if (_animationController.isAnimating) {
_animationController.stop();
}
}
});
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
HapticFeedback.heavyImpact(); HapticFeedback.heavyImpact();
@@ -39,6 +81,8 @@ class ChatFooter extends StatelessWidget {
}, },
onLongPress: () { onLongPress: () {
HapticFeedback.mediumImpact(); HapticFeedback.mediumImpact();
// 立即启动动画,无延迟
_animationController.repeat();
Future.delayed(const Duration(milliseconds: 100), () { Future.delayed(const Duration(milliseconds: 100), () {
HapticFeedback.heavyImpact(); HapticFeedback.heavyImpact();
}); });
@@ -46,21 +90,31 @@ class ChatFooter extends StatelessWidget {
}, },
onLongPressUp: () { onLongPressUp: () {
HapticFeedback.lightImpact(); HapticFeedback.lightImpact();
// 立即停止动画
_animationController.stop();
messageService.stopAndProcessVoiceInput(); messageService.stopAndProcessVoiceInput();
}, },
child: AnimatedContainer( child: LayoutBuilder(
duration: const Duration(milliseconds: 150), builder: (context, constraints) {
height: 48, final containerWidth = constraints.maxWidth;
width: double.infinity, final waveWidth = containerWidth * 0.57; // 波形宽度为容器的57%
return Container(
height: 48, // 固定高度 48px
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
color: messageService.isRecording color: const Color(0x14FFFFFF), // 白色背景透明度8%
? Color(0xFF8E24AA).withValues(alpha: 0.42) borderRadius: BorderRadius.circular(8), // 圆角按钮 8px
: Colors.white.withValues(alpha: 0.12), border: Border.all(
borderRadius: BorderRadius.circular(12), color: buttonColor, // 边框颜色 #6F72F1
width: 0.5, // 边框粗细 0.5px
),
), ),
child: messageService.isRecording child: messageService.isRecording
? const VoiceWaveAnimation() ? AudioWaveLarge(
animationController: _animationController,
totalWidth: waveWidth,
)
: Text( : Text(
Intl.getCurrentLocale().startsWith('zh') Intl.getCurrentLocale().startsWith('zh')
? '按 住 说 话' ? '按 住 说 话'
@@ -70,6 +124,8 @@ class ChatFooter extends StatelessWidget {
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold), fontWeight: FontWeight.bold),
), ),
);
},
), ),
); );
}, },
@@ -77,11 +133,14 @@ class ChatFooter extends StatelessWidget {
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.only(bottom: 12, right: 12), padding: const EdgeInsets.only(bottom: 12, right: 16),
child: IconButton( child: GestureDetector(
icon: const Icon(Icons.keyboard, color: CommonUtil.commonColor), onTap: () {},
iconSize: 30, child: Image.asset(
onPressed: () {}, 'assets/images/keyboard_mini.png',
width: 24,
height: 24,
),
), ),
), ),
], ],

View File

@@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import 'dart:math' as math;
/// 大型音频波形动画组件
///
/// 用于显示适合主按钮的波形动画,竖线数量根据总宽度自动计算
class AudioWaveLarge extends StatelessWidget {
/// 动画控制器,用于驱动波形动画
final AnimationController animationController;
/// 波形颜色,默认为白色
final Color waveColor;
/// 波形条间距默认为4.0px
final double barSpacing;
/// 总宽度默认为148.0px
final double totalWidth;
/// 最大高度默认为22.0px
final double maxHeight;
/// 最小高度默认为4.0px
final double minHeight;
/// 构造函数
const AudioWaveLarge({
super.key,
required this.animationController,
this.waveColor = Colors.white,
this.barSpacing = 4.0,
this.totalWidth = 148.0,
this.maxHeight = 22.0,
this.minHeight = 4.0,
});
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
key: const Key('audioWaveAnimation'), // 为音频波形添加key便于引用
animation: animationController,
builder: (context, child) {
// 根据总宽度动态计算竖线数量
// 每个竖线宽度2px + 间距,保持合理的密度
final barWidthWithSpacing = 2 + barSpacing;
final dynamicBarCount = (totalWidth / barWidthWithSpacing).floor();
final actualBarCount = dynamicBarCount > 0 ? dynamicBarCount : 1;
return SizedBox(
width: totalWidth, // 设置动态宽度
height: maxHeight, // 设置固定高度
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, // 垂直居中
children: List.generate(actualBarCount, (index) {
// 为每个竖线创建不同的相位,使动画不同步
final phase = index * 0.3;
// 设置不同的频率增加自然变化
final frequency = 1.0 + (index % 3) * 0.2;
// 计算波形高度
final height = minHeight + (maxHeight - minHeight) * math.sin(
(animationController.value * 2 * math.pi * frequency) + phase
).abs();
// 构建单个波形条
return Padding(
padding: EdgeInsets.symmetric(horizontal: barSpacing / 2), // 左右间距
child: Container(
width: 2, // 波形条宽度2px
height: height, // 高度随动画变化
decoration: BoxDecoration(
color: waveColor,
borderRadius: BorderRadius.circular(2), // 圆角效果
),
),
);
}),
),
);
},
);
}
}

View File

@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'dart:math' as math;
/// 小型按钮波形动画组件
///
/// 用于显示适合小按钮的波形动画包含8个竖线动画效果由外部动画控制器驱动
class AudioWaveLargeMini extends StatelessWidget {
/// 动画控制器,用于驱动波形动画
final AnimationController animationController;
/// 波形颜色,默认为白色
final Color waveColor;
/// 构造函数
const AudioWaveLargeMini({
super.key,
required this.animationController,
this.waveColor = Colors.white,
});
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animationController, // 使用传入的动画控制器
builder: (context, child) {
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: List.generate(8, (index) { // 生成8个波形条
// 为每个竖线设置不同的相位,使动画更自然
final phase = index * 0.4;
// 设置不同的频率变化
final frequency = 1.0 + (index % 3) * 0.2;
// 计算波形高度最小4.0最大14.0
final height = 4.0 + 10.0 * math.sin(
(animationController.value * 2 * math.pi * frequency) + phase
).abs();
// 构建单个波形条
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 2.0), // 左右间隔4px
child: Container(
width: 2, // 波形条宽度2px
height: height, // 高度随动画变化
decoration: BoxDecoration(
color: waveColor,
borderRadius: BorderRadius.circular(2), // 圆角效果
),
),
);
}),
);
},
);
}
}