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

使用ThemeProvider切换主题导致Widget状态重置问题求助

问题描述

开发小型测验应用时,添加明暗模式切换开关后遇到问题:点击开关切换主题时,整个Widget会重建(打印哈希值每次都不同),页面的状态和变量也会被重置。由于页面加载了测验题目资源,还会更新题目数量等文本内容,这些数据都会丢失,希望仅切换主题,保留Widget及其变量的状态。


涉及代码

main.dart

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => ChangeNotifierProvider(
      create: (context) => ThemeProvider(),
      builder: (context, _) {
        final themeProvider = Provider.of<ThemeProvider>(context);

        return MaterialApp(
          title: 'ROquiz',
          themeMode: themeProvider.themeMode,
          theme: MyThemes.themeLight,
          darkTheme: MyThemes.themeDark,
          home: ViewMenu(),
        );
      });
}

themes.dart

class ThemeProvider extends ChangeNotifier {
  ThemeMode themeMode = ThemeMode.light;

  bool get isDarkMode => themeMode == ThemeMode.dark;

  void toggleTheme(bool isOn) {
    themeMode = isOn ? ThemeMode.dark : ThemeMode.light;
    notifyListeners();
  }
}

class MyThemes {
  static final themeLight = ThemeData(
    colorSchemeSeed: Colors.blue,
    brightness: Brightness.light,
    //iconTheme: IconThemeData(color: Colors.blue[900]),
    elevatedButtonTheme: ElevatedButtonThemeData(
        style: ButtonStyle(
            shape: MaterialStateProperty.all<RoundedRectangleBorder>(
                RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(30.0),
    )))),
  );

  static final themeDark = ThemeData(
      scaffoldBackgroundColor: Colors.indigo[700],
      brightness: Brightness.dark,
      colorScheme: const ColorScheme.dark(),
      elevatedButtonTheme: ElevatedButtonThemeData(
          style: ButtonStyle(
              shape: MaterialStateProperty.all<RoundedRectangleBorder>(
                  RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(30.0),
                      side: BorderSide(color: Colors.blue))))));
}

change_theme_button_widget.dart

class ChangeThemeButtonWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final themeProvider = Provider.of<ThemeProvider>(context);

    return Switch.adaptive(
        value: themeProvider.isDarkMode,
        onChanged: (value) {
          final provider = Provider.of<ThemeProvider>(context, listen: false);

          provider.toggleTheme(value);
        });
  }
}

ViewMenu.dart

class ViewMenu extends StatefulWidget {
  // [...]
}

class ViewMenuState extends State<ViewMenu> {
  @override
  Widget build(BuildContext context) {
    final themeProvider = Provider.of<ThemeProvider>(context);
    
    return Scaffold(
      // [...]
      ChangeThemeButtonWidget(),
      // [...]
    );
  }
}

解决方案

问题根源

ViewMenuState的build方法中调用了Provider.of<ThemeProvider>(context)(默认listen: true),导致ViewMenu会监听ThemeProvider的所有变化。每次切换主题时,ThemeProvider调用notifyListeners(),ViewMenu会被强制重建,进而重置其内部状态和变量。

修改步骤

  1. 移除ViewMenu对ThemeProvider的不必要监听
    修改ViewMenuState的build方法,去掉对ThemeProvider的监听(如果不需要在ViewMenu中主动使用主题数据),或者将监听设置为false:

    class ViewMenuState extends State<ViewMenu> {
      @override
      Widget build(BuildContext context) {
        // 不需要监听主题变化则删除此行,或改为listen: false
        // final themeProvider = Provider.of<ThemeProvider>(context, listen: false);
        
        return Scaffold(
          // [...]
          ChangeThemeButtonWidget(),
          // [...]
        );
      }
    }
    
  2. 确认ChangeThemeButtonWidget的监听逻辑
    ChangeThemeButtonWidget需要监听ThemeProvider来更新开关状态,现有代码逻辑正确,无需修改。它只会在主题变化时自身重建,不会影响父Widget的状态。

  3. 验证效果
    修改完成后,点击主题开关时,只有ChangeThemeButtonWidget会因主题变化重建,ViewMenu的状态和内部变量会被完整保留,测验题目、计数等数据不会重置。


内容的提问来源于stack exchange,提问作者mikyll98

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 20:48:22