我试图实现一个WhatsApp一样的聊天框与语音消息功能完全一样的WhatsApp,语音消息的UI设计工作正常,但删除,发送,暂停按钮内的定位部件不工作(这些按钮在录制音频时使用的锁定概念完全在WhatsApp中)当我点击这些按钮或在定位小部件内键盘弹出底层消息框时,我如何才能避免这种情况,并使按钮工作时,点击。下面是我的代码
主箭头:
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
late AnimationController controller;
@override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 600),
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Audio Chat"),
),
body: Padding(
padding: const EdgeInsets.all(Globals.defaultPadding),
child: Column(
children: [
const Expanded(child: AudioList()),
Row(
mainAxisSize: MainAxisSize.max,
children: [
ChatBox(controller: controller),
const SizedBox(width: 4),
RecordButton(controller: controller),
],
),
],
),
),
);
}
}
record_button.dart:
class _RecordButtonState extends State<RecordButton> {
static const double size = 55;
final double lockerHeight = 200;
double timerWidth = 0;
late Animation<double> buttonScaleAnimation;
late Animation<double> timerAnimation;
late Animation<double> lockerAnimation;
DateTime? startTime;
Timer? timer;
String recordDuration = "00:00";
late Record record;
bool isLocked = false;
bool showLottie = false;
@override
void initState() {
super.initState();
buttonScaleAnimation = Tween<double>(begin: 1, end: 2).animate(
CurvedAnimation(
parent: widget.controller,
curve: const Interval(0.0, 0.6, curve: Curves.elasticInOut),
),
);
widget.controller.addListener(() {
setState(() {});
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
timerWidth = MediaQuery.of(context).size.width - 2 * Globals.defaultPadding - 4 + 5;
timerAnimation =
Tween<double>(begin: timerWidth + Globals.defaultPadding, end: 0)
.animate(
CurvedAnimation(
parent: widget.controller,
curve: const Interval(0.2, 1, curve: Curves.easeIn),
),
);
lockerAnimation =
Tween<double>(begin: lockerHeight + Globals.defaultPadding, end: 0)
.animate(
CurvedAnimation(
parent: widget.controller,
curve: const Interval(0.2, 1, curve: Curves.easeIn),
),
);
}
@override
void dispose() {
record.dispose();
timer?.cancel();
timer = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
children: [
lockSlider(),
cancelSlider(),
audioButton(),
if (isLocked) timerLocked(),
],
);
}
Widget lockSlider() {
return Positioned(
bottom: -lockerAnimation.value,
child: Container(
height: lockerHeight,
width: size,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(Globals.borderRadius),
color: Colors.black,
),
padding: const EdgeInsets.symmetric(vertical: 15),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Icon(FontAwesomeIcons.lock, size: 20),
const SizedBox(height: 8),
FlowShader(
direction: Axis.vertical,
child: Column(
children: const [
Icon(Icons.keyboard_arrow_up),
Icon(Icons.keyboard_arrow_up),
Icon(Icons.keyboard_arrow_up),
],
),
),
],
),
),
);
}
Widget cancelSlider() {
return Positioned(
right: -timerAnimation.value,
child: Container(
height: size,
width: timerWidth,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(Globals.borderRadius),
color: Colors.black,
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: [
showLottie ? const LottieAnimation() : Text(recordDuration),
const SizedBox(width: size),
FlowShader(
child: Row(
children: const [
Icon(Icons.keyboard_arrow_left),
Text("Slide to cancel")
],
),
duration: const Duration(seconds: 3),
flowColors: const [Colors.white, Colors.grey],
),
const SizedBox(width: size),
],
),
),
),
);
}
Widget timerLocked() {
return Positioned(
right: 0,
bottom: 0,
child: Container(
height: 80,
width: timerWidth,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(Globals.borderRadius - 10),
color: Colors.lightBlue,
),
child: Padding(
padding: const EdgeInsets.only(left: 15, right: 25),
child: Column(
children: [
Flexible(
flex: 1,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: [
Text(recordDuration),
FlowShader(
child: const Text("Tap lock to stop"),
duration: const Duration(seconds: 3),
flowColors: const [Colors.white, Colors.grey],
),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () async {
print('Recording finished');
},
child: const Center(
child: Icon(
FontAwesomeIcons.check,
size: 18,
color: Colors.black,
),
),
),
],
),
),
Flexible(
flex: 1,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: [
Center(
child: InkWell(
onTap: () {
print('Action Delete');
},
child: Icon(FontAwesomeIcons.trash,size: 18,color: Colors.black,),
),
),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () async {
print('Action Pause');
},
child: const Center(
child: Icon(
FontAwesomeIcons.pause,
size: 18,
color: Colors.black,
),
),
),
],
),
),
],
),
),
),
);
}
Widget audioButton() {
return GestureDetector(
child: Transform.scale(
scale: buttonScaleAnimation.value,
child: Container(
child: const Icon(Icons.mic),
height: size,
width: size,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).primaryColor,
),
),
),
onLongPressDown: (_) {
widget.controller.forward();
},
onLongPressEnd: (details) async {
if (isCancelled(details.localPosition, context)) {
} else if (checkIsLocked(details.localPosition)) {
widget.controller.reverse();
Vibrate.feedback(FeedbackType.heavy);
setState(() {
isLocked = true;
});
} else {
print('Recording Finished');
}
},
onLongPressCancel: () {
widget.controller.reverse();
},
onLongPress: () async {
Vibrate.feedback(FeedbackType.success);
if (await Record().hasPermission()) {
record = Record();
await record.start(
path: Globals.documentPath +
"audio_${DateTime.now().millisecondsSinceEpoch}.m4a",
encoder: AudioEncoder.aacEld,
bitRate: 128000,
samplingRate: 44100,
);
startTime = DateTime.now();
timer = Timer.periodic(const Duration(seconds: 1), (_) {
final minDur = DateTime.now().difference(startTime!).inMinutes;
final secDur = DateTime.now().difference(startTime!).inSeconds % 60;
String min = minDur < 10 ? "0$minDur" : minDur.toString();
String sec = secDur < 10 ? "0$secDur" : secDur.toString();
setState(() {
recordDuration = "$min:$sec";
});
});
}
},
);
}
bool checkIsLocked(Offset offset) {
return (offset.dy < -35);
}
bool isCancelled(Offset offset, BuildContext context) {
return (offset.dx < -(MediaQuery.of(context).size.width * 0.2));
}
}
更新:
到目前为止,我找到的唯一解决方法是用Stack,SizedBox,Positioned
封装timerLocked Positioned小部件,如下所示。
Widget timerLocked() {
return Positioned(
child: SizedBox(
height: 80,
width: timerWidth,
child: Stack(
children: [
Positioned(
right: 0,
bottom: 0,
height: 80,
width: timerWidth,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(Globals.borderRadius - 10),
color: Colors.lightBlue,
),
child: Padding(
padding: const EdgeInsets.only(left: 15, right: 25),
child: Column(
children: [
],
),
),),
),
],),),);
}
2条答案
按热度按时间ilmyapht1#
要解决这个问题,我们需要用IgnorePointer小部件 Package 按钮,并确保手势不会传递给底层小部件。
6jjcrrmo2#
最后我找到了一个解决方案.我用
ExpandTapWidget
(ExpandTapWidget)替换了Positioned
小部件,解决了问题.这是一个bug/documentaion问题,并发布了here,帮助我解决了这个问题.下面是代码,