Я пытаюсь реализовать окно чата, похожее на WhatsApp, с функцией голосового сообщения точно так же, как в WhatsApp, дизайн пользовательского интерфейса для голосового сообщения работает нормально, но кнопка удаления, отправки, паузы внутри позиционированных виджетов не работает (эти кнопки используются при записи аудио с использованием концепции блокировки именно в 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));
}
}
ОБНОВЛЯТЬ :
Единственный обходной путь, который я нашел до сих пор, — это заключить виджет timerLocked Positioned с помощью Stack,SizedBox,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: [
],
),
),),
),
],),),);
}





Наконец, я нашел решение. Я заменил виджет Positioned на ExpandTapWidget(ExpandTapWidget), и это решило проблему.
Widget timerLocked() {
return ExpandTapWidget(
onTap: () {
print('********** ExpandTapWidget Clicked ********');
},
tapPadding: EdgeInsets.all(1),
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: [
],
),
),),
);
}