Flutter中如何让SpeechToTextProvider仅重建指定的单个Widget
Flutter中如何让SpeechToTextProvider仅重建指定的单个Widget
嘿,我来帮你搞定这个问题!你现在碰到的情况,是因为全局共用的SpeechToTextProvider每次通知状态变化时,所有依赖它的组件都会跟着重建,而且所有麦克风的状态都绑死在一起了对吧?我给你两个实用的解决方案,你可以根据自己的需求来选:
方案一:给Provider加激活标识,用Selector精准控制重建
这个方案适合你想复用同一个SpeechToText实例的场景,不用额外创建多个Provider:
修改SpeechToTextProvider,新增激活输入项的标识
先给你的SpeechToTextProvider类加个变量,用来记录当前正在使用语音输入的输入项ID,再新增一个方法专门启动指定输入的语音识别:class SpeechToTextProvider extends ChangeNotifier { final SpeechToText speech; String? activeInputId; // 新增:记录当前激活的输入ID String? currentTranscript; // 保存当前识别的文本 SpeechToTextProvider(this.speech); // 新增:启动指定输入的语音识别 Future<void> startListeningForInput(String inputId) async { if (!await speech.initialize()) return; activeInputId = inputId; currentTranscript = ''; notifyListeners(); await speech.listen( onResult: (result) { currentTranscript = result.recognizedWords; notifyListeners(); }, ); } // 新增:停止语音识别 void stopListening() { speech.stop(); activeInputId = null; notifyListeners(); } // 保留你原来的initialize方法 Future<void> initialize() async { await speech.initialize(); } }给每个TextFormField分配唯一ID,用Selector筛选重建时机
每个带语音输入的TextFormField都要绑定自己的TextEditingController和唯一ID,然后用Selector来监听Provider的状态——只有当当前输入项是激活状态,且识别文本有变化时,才更新输入框内容并重建组件:class _SpeechEnabledTextField extends StatelessWidget { final String inputId; final TextEditingController controller; final String labelText; const _SpeechEnabledTextField({ required this.inputId, required this.controller, required this.labelText, }); @override Widget build(BuildContext context) { return Selector<SpeechToTextProvider, (String?, String?)>( selector: (context, provider) => (provider.activeInputId, provider.currentTranscript), builder: (context, data, child) { // 只有当前输入项被激活时,才更新输入框内容 if (data.$1 == inputId && data.$2 != null) { controller.text = data.$2!; } return child!; }, child: TextFormField( controller: controller, decoration: InputDecoration( labelText: labelText, suffixIcon: Consumer<SpeechToTextProvider>( builder: (context, provider, child) { return IconButton( icon: Icon( provider.activeInputId == inputId ? Icons.mic : Icons.mic_none, color: provider.activeInputId == inputId ? Colors.red : null, ), onPressed: () { final provider = Provider.of<SpeechToTextProvider>(context, listen: false); if (provider.activeInputId == inputId) { provider.stopListening(); } else { provider.startListeningForInput(inputId); } }, ); }, ), ), ), ); } }构建表单的时候,给每个输入项分配唯一ID和独立的controller就行:
final nameController = TextEditingController(); final emailController = TextEditingController(); @override Widget build(BuildContext context) { return Form( child: Column( children: [ _SpeechEnabledTextField( inputId: 'name_input', controller: nameController, labelText: '姓名', ), _SpeechEnabledTextField( inputId: 'email_input', controller: emailController, labelText: '邮箱', ), ], ), ); }
方案二:为每个输入项使用独立的局部Provider
如果你的每个输入项不需要共享语音识别状态,完全可以给每个输入项单独创建一个SpeechToTextProvider,这样每个麦克风的状态都是独立的,只会重建自己对应的组件,根本不会影响其他输入项:
class _IndependentSpeechTextField extends StatelessWidget { final TextEditingController controller; final String labelText; const _IndependentSpeechTextField({ required this.controller, required this.labelText, }); @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (context) => SpeechToTextProvider(SpeechToText()), child: Consumer<SpeechToTextProvider>( builder: (context, provider, child) { return TextFormField( controller: controller, decoration: InputDecoration( labelText: labelText, suffixIcon: IconButton( icon: Icon(provider.isListening ? Icons.mic : Icons.mic_none), color: provider.isListening ? Colors.red : null, onPressed: () async { if (provider.isListening) { provider.stopListening(); } else { if (!provider.isAvailable) { await provider.initialize(); } await provider.listen( onResult: (result) { controller.text = result.recognizedWords; }, ); } }, ), ), ); }, ), ); } }
使用的时候直接在表单里添加就行,每个输入项的语音识别完全独立:
Form( child: Column( children: [ _IndependentSpeechTextField( controller: TextEditingController(), labelText: '姓名', ), _IndependentSpeechTextField( controller: TextEditingController(), labelText: '邮箱', ), ], ), )
额外注意点
- 不管用哪个方案,每个TextFormField必须绑定独立的
TextEditingController,这样输入内容才不会互相覆盖。 - 记得在组件销毁时调用
controller.dispose(),避免内存泄漏。
备注:内容来源于stack exchange,提问作者Giulia Santoiemma
相关产品推荐
相关产品推荐

