Flutter中如何在Form中协同管理speech_to_text与手动输入?
解决方案
你的问题核心在于语音输入时未保留手动编辑的内容,以及构建方法中错误的文本重置逻辑导致交互冲突。以下是针对性的修改方案:
核心问题分析
- 原代码在启动语音时直接清空
lastWords,并在识别结果中覆盖输入框文本,导致手动编辑的内容丢失。 MyForm的build方法每次触发时都会重置输入框文本,引发不必要的内容覆盖。textChanged变量的状态管理逻辑混乱,导致语音与手动输入的状态冲突。
修改后的完整代码
import 'dart:async'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:speech_to_text/speech_to_text.dart'; import 'package:speech_to_text/speech_recognition_result.dart'; class Speech extends StatefulWidget { const Speech({super.key}); @override State<Speech> createState() => _SpeechState(); } class _SpeechState extends State<Speech> { bool _hasSpeech = false; String lastWords = ''; final SpeechToText speech = SpeechToText(); final TextEditingController _descriptionController = TextEditingController(); // 记录语音开始前的文本内容,用于追加语音识别结果 String _baseText = ''; @override void initState() { super.initState(); _initSpeechState(); } Future<void> _initSpeechState() async { try { bool hasSpeech = await speech.initialize(); if (!mounted) return; setState(() { _hasSpeech = hasSpeech; }); } catch (e) { setState(() { _hasSpeech = false; }); } } @override Widget build(BuildContext context) { return Column(children: [ Consumer<ApplicationState>(builder: (context, appState, _) { return FutureBuilder<Baby>( future: appState.getBabyData(), builder: (BuildContext context, AsyncSnapshot<Baby> snapshot) { if (!snapshot.hasData) { return const Center( child: CircularProgressIndicator(), ); } else { return MyForm( baby: snapshot.requireData, descriptionController: _descriptionController, stopListening: stopListening, ); } }); }), MicrophoneWidget(speech.isNotListening, startListening, stopListening), ]); } void startListening() { // 记录当前输入框的文本作为基础,后续语音内容追加至此 _baseText = _descriptionController.text; speech.listen( onResult: resultListener, // 使用dictation模式支持连续语音输入 listenMode: ListenMode.dictation, ); } void stopListening() { speech.stop(); } void resultListener(SpeechRecognitionResult result) { setState(() { lastWords = result.recognizedWords; // 合并基础文本与语音识别结果,去除多余空格 _descriptionController.text = '$_baseText $lastWords'.trim(); }); } } class MicrophoneWidget extends StatelessWidget { const MicrophoneWidget(this.isNotListening, this.startListening, this.stopListening, {super.key}); final bool isNotListening; final void Function() startListening; final void Function() stopListening; @override Widget build(BuildContext context) { return SizedBox( child: FloatingActionButton( onPressed: isNotListening ? startListening : stopListening, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(80.0)), ), child: Icon(isNotListening ? Icons.mic_off : Icons.mic), ), ); } } class MyForm extends StatefulWidget { const MyForm({ super.key, required this.baby, required this.descriptionController, required this.stopListening, }); final Baby baby; final TextEditingController descriptionController; final void Function() stopListening; @override State<MyForm> createState() => _MyFormState(); } class _MyFormState extends State<MyForm> { final _formKey = GlobalKey<FormState>(debugLabel: 'MyFormState'); @override void initState() { super.initState(); // 初始化输入框为Firestore中存储的描述内容 if (widget.baby.description != null) { widget.descriptionController.text = widget.baby.description!; } } void _saveBabyProfile(appState) async { widget.stopListening(); if (_formKey.currentState!.validate()) { widget.baby.description = widget.descriptionController.text; appState.setBabyData(widget.baby); } } @override Widget build(BuildContext context) { return Form( key: _formKey, child: Column( children: <Widget>[ TextFormField( controller: widget.descriptionController, decoration: const InputDecoration(labelText: '描述'), ), Consumer<ApplicationState>( builder: (context, appState, _) => ElevatedButton( onPressed: () => _saveBabyProfile(appState), child: const Text("保存"), ), ), ], ), ); } }
关键修改说明
语音输入追加逻辑:
- 添加
_baseText变量,每次启动语音时记录当前输入框的文本内容。 - 语音识别结果实时追加到
_baseText后,避免覆盖手动编辑的内容。 - 使用
ListenMode.dictation模式,支持连续自然的语音输入。
- 添加
移除错误的文本重置逻辑:
- 删除
MyForm中build方法内的文本重置代码,改为在initState中初始化输入框内容,避免重复覆盖。
- 删除
简化状态管理:
- 移除冗余的
textChanged变量及相关方法,减少状态冲突点。
- 移除冗余的
测试场景验证
修改后可完美支持你提到的两种交互序列:
- 开启麦克风→语音输入→编辑文本→再次语音输入:语音内容会追加到编辑后的文本末尾。
- 开启麦克风→语音输入→关闭麦克风→编辑文本→重新开启麦克风输入:同样基于编辑后的内容追加语音结果。
内容的提问来源于stack exchange,提问作者Giulia Santoiemma
相关产品推荐
相关产品推荐

