You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

键盘开合时ChangeNotifierProvider多次调用致页面重复刷新求助

Flutter键盘触发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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 21:54:14