键盘开合时ChangeNotifierProvider多次调用致页面重复刷新求助
问题原因
键盘弹出或关闭时,Flutter会更新MediaQuery的viewInsets参数。你的ChangeNotifierProvider直接嵌套在Scaffold的body中,当MediaQuery变化时,Provider所在的上下文会触发重建,导致其builder方法反复执行,进而引发整个页面重载,最终造成视频控件实例频繁销毁重建、功能失效。
解决方案
1. 将Provider提升到更高层级
把ChangeNotifierProvider移到MaterialApp或页面路由的父级(比如Navigator之外),这样MediaQuery的变化不会影响到Provider的创建和builder调用,从根源避免不必要的重建。
示例代码:
// 在页面路由的父级配置中(比如MaterialApp的home或路由注册处) ChangeNotifierProvider<CreateRouteStep2ViewModel>( create: (context) => CreateRouteStep2ViewModel(), child: YourCurrentPage(), )
当前页面的Scaffold无需再嵌套Provider,直接通过Consumer或Provider.of获取实例即可。
2. 用Consumer精准控制重建范围
不要将整个页面包裹在Consumer中,仅对需要响应Provider状态变化的组件使用Consumer包裹,缩小重建范围。
示例代码(拆分重建区域):
return Scaffold( backgroundColor: ColorManager.colorBackgroundGray, body: SizedBox( height: double.infinity, width: double.infinity, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, children: [ Stack( children: [ // 仅地图区域响应viewType变化 Consumer<CreateRouteStep2ViewModel>( builder: (context, provider, c) { return provider.viewType == CreateRouteStep2ViewType.map ? SizedBox( height: 300, child: GoogleMapForCreateRoute( modelProvider: provider), ) : const SizedBox(); }, ), // 其他无需响应Provider的组件直接放置 ], ), Expanded( flex: 1, child: Container( color: ColorManager.colorWhite, child: SingleChildScrollView( // 仅列表区域响应listRouteName变化 child: Consumer<CreateRouteStep2ViewModel>( builder: (context, provider, c) { return ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.only(top: 10), itemCount: provider.listRouteName.length, itemBuilder: (BuildContext context, index) { return CreateStepsItem( routeModel: provider.listRouteName[index], modelProvider: provider, index: index, removeIndex: () { provider.removeIndexFromPinAndList(index); }, edtLocationNameFocus: () { provider.focusMap(index); }, edtAudioFocus: (mainPosition, subPosition) { provider.focusAudioComponent(index, subPosition); }, edtVideoFocus: (mainPosition, subPosition) { provider.focusVideoComponent(index, subPosition); }, ); }, scrollDirection: Axis.vertical, ); }, ), ), ), ), // 底部按钮仅依赖MediaQuery,无需关联Provider Visibility( visible: MediaQuery.of(context).viewInsets.bottom == 0, child: Align( alignment: Alignment.bottomCenter, child: Padding( padding: const EdgeInsets.only(left: 24.0, right: 24.0, bottom: 20.0), child: CommonButton( onPressed: () {}, lable: AppStrings.constTourSpichern, isEnable: false, ), ), ), ) ], ), ), const SizedBox(height: 20,) ], ), ), );
3. 避免直接传递Provider实例
子组件(如GoogleMapForCreateRoute、CreateStepsItem)不要通过外部参数接收modelProvider,而是在组件内部通过Provider.of<CreateRouteStep2ViewModel>(context)或Consumer获取实例,避免因外部实例传递导致的状态不一致问题。
4. 优化Provider的通知时机
检查CreateRouteStep2ViewModel中的notifyListeners()调用逻辑,确保仅在真正需要更新UI的状态变化时触发通知,避免在键盘弹出等无关操作时误触发重建。
内容的提问来源于stack exchange,提问作者Mits Patel

