Flutter实现多语言切换
适用于flutter App 切换多语言场景:应用内一键切换中/英文,切换结果持久化保存,重启 App 后依然生效;同时附带主题模式切换作为全局状态管理的完整示例。
一、前言
公司割草机项目使用 Flutter 开发,需要在 App 内支持中英文切换(面向欧洲、美国、澳洲等海外市场)。本文基于一个「商城 + 设备 + 购物车 + 我的」四 Tab Demo,完整讲解 Flutter 多语言切换的实现方式,包括:
自定义多语言文案资源与 LocalizationsDelegate;
全局状态管理(语言、主题)与 SharedPreferences 持久化;
在 MaterialApp 中注册本地化代理与支持语言列表;
排查并解决「切换语言后界面不刷新」的典型问题。
二、项目目录结构
标准 Flutter 工程结构,多语言相关代码集中在 lib/main.dart:
flutter-setttings/ ├── android/ ├── ios/ ├── lib/ │ └── main.dart # 多语言、状态管理、页面与入口全部在此文件 ├── linux/ ├── macos/ ├── test/ ├── web/ ├── windows/ ├── .gitignore ├── analysis_options.yaml ├── LICENSE ├── pubspec.lock ├── pubspec.yaml └── README.md三、依赖配置
编辑 pubspec.yaml,引入 flutter_localizations(官方组件本地化)与 shared_preferences(语言/主题持久化):
name: flutter_settings description: A Flutter demo with theme and in-app language switching. publish_to: 'none' version: 1.0.0+1 environment: sdk: ^3.0.0 dependencies: flutter: sdk: flutter # 官方组件本地化(按钮、日期、选择框等内置文案) flutter_localizations: sdk: flutter # 本地持久化:保存用户选择的语言与主题 shared_preferences: ^2.2.2 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^3.0.0 flutter: uses-material-design: true执行依赖安装:
flutter pub get四、整体实现思路
整体链路如下:
用静态 Map 维护中文、英文两套文案,通过 AppLocalizations 对外提供 get(key) 取值;
自定义 AppLocalizationsDelegate,交给 Flutter 本地化框架在当前 Locale 下加载对应的 AppLocalizations 实例;
AppState 继承 ChangeNotifier,集中管理 ThemeMode 与 Locale,变更时写入 SharedPreferences 并通知监听者;
手写轻量 ChangeNotifierProvider(基于 InheritedWidget)在树根注入 AppState,Consumer 包裹 MaterialApp,状态变化时重建 App 根节点;
MaterialApp 通过 locale、supportedLocales、localizationsDelegates 完成本地化注册;
页面中统一通过 AppLocalizations.of(context).get(‘key’) 获取文案,切换语言后随根节点重建而刷新。
五、核心代码实现
5.1 多语言文案资源 AppLocalizations
AppLocalizations 持有当前 Locale,内部维护语言码 → 文案 key → 文案内容 的两层 Map;of(context) 从本地化容器中取出当前实例:
import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:shared_preferences/shared_preferences.dart'; // ---------------------- 多语言文本 ---------------------- class AppLocalizations { AppLocalizations(this.locale); final Locale locale; static AppLocalizations of(BuildContext context) { return Localizations.of<AppLocalizations>(context, AppLocalizations)!; } static const Map<String, Map<String, String>> _localizedValues = { 'zh': { 'tab_home': '商城', 'tab_device': '设备', 'tab_cart': '购物车', 'tab_mine': '我的', 'theme_title': '主题设置', 'theme_light': '亮色模式', 'theme_dark': '暗色模式', 'theme_system': '跟随系统', 'lang_title': '语言设置', 'lang_zh': '中文', 'lang_en': 'English', 'home_page': '商城页面', 'device_page': '设备页面', 'cart_page': '购物车页面', 'mine_page': '个人中心', }, 'en': { 'tab_home': 'Shop', 'tab_device': 'Device', 'tab_cart': 'Cart', 'tab_mine': 'Mine', 'theme_title': 'Theme Setting', 'theme_light': 'Light', 'theme_dark': 'Dark', 'theme_system': 'System Default', 'lang_title': 'Language', 'lang_zh': 'Chinese', 'lang_en': 'English', 'home_page': 'Shop Page', 'device_page': 'Device Page', 'cart_page': 'Cart Page', 'mine_page': 'Mine Page', }, }; String get(String key) { return _localizedValues[locale.languageCode]![key]!; } }5.2 本地化代理 AppLocalizationsDelegate
LocalizationsDelegate 负责告诉 Flutter 框架:支持哪些语言、如何加载本地化实例、语言资源是否需要重新加载:
class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> { const AppLocalizationsDelegate(); // 当前工程支持的语言列表 @override bool isSupported(Locale locale) => ['zh', 'en'].contains(locale.languageCode); // 加载当前语言对应的本地化实例(本示例文案为内存静态资源,无需异步加载文件) @override Future<AppLocalizations> load(Locale locale) async { return AppLocalizations(locale); } // 资源无变化,重建时不重新加载 @override bool shouldReload(AppLocalizationsDelegate old) => false; }5.3 全局状态管理 AppState(主题 + 语言持久化)
AppState 统一持有主题模式与当前语言:
locale 为 null 时表示跟随系统语言;
loadSaved() 在 App 启动时从 SharedPreferences 读取上次保存的主题与语言;
setThemeMode() / setLocale() 更新状态、写回本地存储,并 notifyListeners() 通知所有监听者。
// ---------------------- 全局状态管理(主题、语言) ---------------------- class AppState extends ChangeNotifier { ThemeMode _themeMode = ThemeMode.system; Locale? _locale; ThemeMode get themeMode => _themeMode; Locale? get locale => _locale; Future<void> loadSaved() async { final sp = await SharedPreferences.getInstance(); final themeIndex = sp.getInt('theme_mode'); if (themeIndex != null) { _themeMode = ThemeMode.values[themeIndex]; } final langCode = sp.getString('locale'); if (langCode != null) { _locale = Locale(langCode); } notifyListeners(); } Future<void> setThemeMode(ThemeMode mode) async { _themeMode = mode; final sp = await SharedPreferences.getInstance(); await sp.setInt('theme_mode', mode.index); notifyListeners(); } Future<void> setLocale(Locale? loc) async { _locale = loc; final sp = await SharedPreferences.getInstance(); if (loc == null) { await sp.remove('locale'); } else { await sp.setString('locale', loc.languageCode); } notifyListeners(); } }5.4 四个 Tab 页面
四个页面结构一致,通过 AppLocalizations.of(context).get(…) 取当前语言文案:
// ---------------------- 4 个 Tab 页面 ---------------------- class ShopPage extends StatelessWidget { const ShopPage({super.key}); @override Widget build(BuildContext context) { return Center( child: Text( AppLocalizations.of(context).get('home_page'), style: const TextStyle(fontSize: 22), ), ); } } class DevicePage extends StatelessWidget { const DevicePage({super.key}); @override Widget build(BuildContext context) { return Center( child: Text( AppLocalizations.of(context).get('device_page'), style: const TextStyle(fontSize: 22), ), ); } } class CartPage extends StatelessWidget { const CartPage({super.key}); @override Widget build(BuildContext context) { return Center( child: Text( AppLocalizations.of(context).get('cart_page'), style: const TextStyle(fontSize: 22), ), ); } }5.5 MinePage:主题与语言选择弹窗
「我的」页面包含两个 ListTile:主题设置、语言设置。点击分别弹出 AlertDialog 选择项;选中项展示对勾标记。
注意:ChangeNotifierProvider.of(context) 用于读取全局状态;弹窗内同样通过 AppLocalizations 取文案,保证弹窗标题、选项本身也跟随语言切换。
class MinePage extends StatelessWidget { const MinePage({super.key}); @override Widget build(BuildContext context) { final state = ChangeNotifierProvider.of<AppState>(context); final loc = AppLocalizations.of(context); return ListView( children: [ ListTile( title: Text(loc.get('theme_title')), subtitle: Text([ loc.get('theme_system'), loc.get('theme_light'), loc.get('theme_dark'), ][state.themeMode.index]), onTap: () => _showThemeDialog(context, state), ), ListTile( title: Text(loc.get('lang_title')), subtitle: Text( state.locale?.languageCode == 'zh' ? loc.get('lang_zh') : loc.get('lang_en'), ), onTap: () => _showLangDialog(context, state), ), ], ); } void _showThemeDialog(BuildContext context, AppState state) { final loc = AppLocalizations.of(context); showDialog( context: context, builder: (ctx) { return AlertDialog( title: Text(loc.get('theme_title')), content: Column( mainAxisSize: MainAxisSize.min, children: [ _themeItem(loc.get('theme_system'), ThemeMode.system, state), _themeItem(loc.get('theme_light'), ThemeMode.light, state), _themeItem(loc.get('theme_dark'), ThemeMode.dark, state), ], ), ); }, ); } Widget _themeItem(String title, ThemeMode mode, AppState state) { return ListTile( title: Text(title), onTap: () { state.setThemeMode(mode); Navigator.pop(context); }, trailing: state.themeMode == mode ? const Icon(Icons.check) : null, ); } void _showLangDialog(BuildContext context, AppState state) { final loc = AppLocalizations.of(context); showDialog( context: context, builder: (ctx) { return AlertDialog( title: Text(loc.get('lang_title')), content: Column( mainAxisSize: MainAxisSize.min, children: [ _langItem(loc.get('lang_zh'), Locale('zh'), state), _langItem(loc.get('lang_en'), Locale('en'), state), ], ), ); }, ); } Widget _langItem(String title, Locale loc, AppState state) { return ListTile( title: Text(title), onTap: () { state.setLocale(loc); Navigator.pop(context); }, trailing: state.locale?.languageCode == loc.languageCode ? const Icon(Icons.check) : null, ); } }5.6 底部导航 MainHome
MainHome 维护当前选中的 Tab 索引,底部导航栏的四个标签文案同样来自 AppLocalizations:
// ---------------------- 主页面,底部 Tab ---------------------- class MainHome extends StatefulWidget { const MainHome({super.key}); @override State<MainHome> createState() => _MainHomeState(); } class _MainHomeState extends State<MainHome> { int _currentIndex = 0; final List<Widget> _pages = const [ ShopPage(), DevicePage(), CartPage(), MinePage(), ]; @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context); return Scaffold( body: _pages[_currentIndex], bottomNavigationBar: BottomNavigationBar( currentIndex: _currentIndex, onTap: (idx) { setState(() => _currentIndex = idx); }, type: BottomNavigationBarType.fixed, items: [ BottomNavigationBarItem( icon: const Icon(Icons.storefront), label: loc.get('tab_home'), ), BottomNavigationBarItem( icon: const Icon(Icons.devices), label: loc.get('tab_device'), ), BottomNavigationBarItem( icon: const Icon(Icons.shopping_cart), label: loc.get('tab_cart'), ), BottomNavigationBarItem( icon: const Icon(Icons.person), label: loc.get('tab_mine'), ), ], ), ); } }5.7 应用入口 MyApp 与 MaterialApp 注册
MyApp 在 initState 中创建 AppState 并异步读取本地缓存;加载完成前展示 Loading。随后用 ChangeNotifierProvider 注入全局状态,Consumer 监听状态变化并重建 MaterialApp:
locale: state.locale:应用内强制使用当前语言;为 null 时跟随系统;
supportedLocales:声明本工程支持的语言;
localizationsDelegates:注册自定义代理与官方三个全局代理,保证 Material、Widget、Cupertino 组件内置文案(日期、按钮、选中提示等)同步切换语言。
// ---------------------- main 入口 ---------------------- void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { late AppState _appState; bool _loaded = false; @override void initState() { super.initState(); _appState = AppState(); _appState.loadSaved().then((_) { setState(() => _loaded = true); }); } @override Widget build(BuildContext context) { if (!_loaded) { return const MaterialApp( home: Scaffold(body: Center(child: CircularProgressIndicator())), ); } return ChangeNotifierProvider.value( value: _appState, child: Consumer<AppState>( builder: (ctx, state, child) { return MaterialApp( title: '商城Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), useMaterial3: true, ), darkTheme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Colors.blue, brightness: Brightness.dark, ), useMaterial3: true, ), themeMode: state.themeMode, locale: state.locale, localizationsDelegates: const [ AppLocalizationsDelegate(), GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: const [Locale('zh'), Locale('en')], home: const MainHome(), ); }, ), ); } }5.8 手写状态管理:ChangeNotifierProvider 与 Consumer
本 Demo 未引入第三方状态管理库,直接基于 InheritedWidget 手写一套最简 Provider:
ChangeNotifierProvider:在 initState 中监听状态对象,收到通知后 setState 重建自身;
_InheritedProvider:真正的 InheritedWidget,向下层暴露状态对象;
Consumer:读取状态对象后调用 builder 重建对应子树;
of() 通过 dependOnInheritedWidgetOfExactType 建立依赖关系。
// 每次 build 重新生成 InheritedWidget class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget { final T value; final Widget child; const ChangeNotifierProvider.value({ super.key, required this.value, required this.child, }); @override State<ChangeNotifierProvider<T>> createState() => _ChangeNotifierProviderState<T>(); static T of<T>(BuildContext context) { return context .dependOnInheritedWidgetOfExactType<_InheritedProvider<T>>()! .value; } } class _ChangeNotifierProviderState<T extends ChangeNotifier> extends State<ChangeNotifierProvider<T>> { @override void initState() { super.initState(); widget.value.addListener(_notify); } void _notify() { setState(() {}); } @override void dispose() { widget.value.removeListener(_notify); super.dispose(); } @override Widget build(BuildContext context) { // 每次 build 都新建 InheritedProvider 实例,触发更新 return _InheritedProvider<T>(value: widget.value, child: widget.child); } } class _InheritedProvider<T> extends InheritedWidget { final T value; const _InheritedProvider({required this.value, required super.child}); @override bool updateShouldNotify(_InheritedProvider<T> oldWidget) { return true; // 只要重建就通知所有依赖 } } class Consumer<T> extends StatelessWidget { final Widget Function(BuildContext context, T value, Widget? child) builder; const Consumer({super.key, required this.builder}); @override Widget build(BuildContext context) { final val = ChangeNotifierProvider.of<T>(context); return builder(context, val, null); } }六、问题排查:多语言切换不生效
6.1 现象
在语言弹窗中切换中文 / 英文后,AppState.setLocale() 已执行、SharedPreferences 中也保存了新语言码,但页面底部 Tab、页面标题等文案没有刷新,需要重启 App 才生效。
6.2 原因分析
InheritedWidget 的更新通知机制有两个关键环节:
状态对象调用 notifyListeners() 后,ChangeNotifierProvider 监听器执行 setState,_InheritedProvider 被重新构建;
InheritedWidget 重建后,会通过 updateShouldNotify 判断是否通知所有依赖它的子 Widget:返回 false 时,即使组件被重建,下游依赖方也不会收到变更通知,不会触发 didChangeDependencies 与重新构建。
本 Demo 中 AppState 只是内部状态(_locale)发生变化,传入 Provider 的 value 对象引用并未改变;如果 updateShouldNotify 使用默认的 false 比较逻辑,依赖该 Provider 的 Consumer(即 MaterialApp 及其子树)就不会重建,locale 参数不会更新,本地化框架自然不会重新加载文案。
6.3 解决方案
在 _InheritedProvider 中强制 updateShouldNotify 返回 true:每次 InheritedWidget 重建都通知所有依赖方,触发 MaterialApp 与整棵页面树重建,从而应用新的 locale:
class _InheritedProvider<T> extends InheritedWidget { final T value; const _InheritedProvider({required this.value, required super.child}); @override bool updateShouldNotify(_InheritedProvider<T> oldWidget) { return true; // 只要重建就通知所有依赖 } }配合 ChangeNotifierProvider 监听器中的 setState(() {}),形成完整闭环:
void _notify() { setState(() {}); }状态变更通知链路:
点击语言选项 └─ AppState.setLocale() ├─ 更新 _locale 并写入 SharedPreferences └─ notifyListeners() └─ ChangeNotifierProvider._notify() → setState() └─ _InheritedProvider 重建 └─ updateShouldNotify = true,通知 Consumer └─ MaterialApp 以新 locale 重建 └─ AppLocalizationsDelegate 重新加载文案 └─ 全部页面 Text 刷新为新语言七、实现效果
web端:
Android:
八、关键要点总结
注册三件套缺一不可:locale、supportedLocales、localizationsDelegates 必须配置在 MaterialApp 上,自定义 AppLocalizationsDelegate 与官方 Global*Localizations.delegate 同时注册。
取值方式统一:页面中始终使用 AppLocalizations.of(context).get(‘key’),不要硬编码中/英文案。
切换语言要重建根节点:通过状态管理包裹 MaterialApp,语言变化后重建 App 根节点,本地化框架才会按新 locale 重新加载文案。
状态通知必须真正触达下游:手写 InheritedWidget 状态管理时,updateShouldNotify 需返回 true,否则状态变更无法通知到依赖子树。
持久化时机:写入 SharedPreferences 与 notifyListeners() 在同一个 setLocale / setThemeMode 方法内完成,避免状态与本地存储不一致。
九、后续扩展建议
文案文件分离:文案量增大后,将 _localizedValues 拆分到 strings_zh.dart、strings_en.dart 等独立文件;更规范的做法是使用 Flutter 官方 gen-l10n 配合 .arb 资源文件,自动生成强类型取值代码。
Key 缺失兜底:get(key) 中对语言码或 key 不存在的情况返回默认值(如返回 key 本身或英文兜底),避免 ! 空断言抛出异常。
语言选项动态化:根据 supportedLocales 动态生成语言列表,并补充「跟随系统」选项;当前 state.locale == null 时副标题会默认展示 English,可一并修正。
引入成熟状态管理库:生产项目建议使用 provider 或 Riverpod 替代手写 Provider,减少 updateShouldNotify 这类底层细节带来的隐患。
新增语言成本:新增一种语言时,只需在 _localizedValues 中增加映射、在 isSupported 与 supportedLocales 中注册对应 Locale 即可。
附录:main.dart 完整代码
import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:shared_preferences/shared_preferences.dart'; // ---------------------- 多语言文本 ---------------------- class AppLocalizations { AppLocalizations(this.locale); final Locale locale; static AppLocalizations of(BuildContext context) { return Localizations.of<AppLocalizations>(context, AppLocalizations)!; } static const Map<String, Map<String, String>> _localizedValues = { 'zh': { 'tab_home': '商城', 'tab_device': '设备', 'tab_cart': '购物车', 'tab_mine': '我的', 'theme_title': '主题设置', 'theme_light': '亮色模式', 'theme_dark': '暗色模式', 'theme_system': '跟随系统', 'lang_title': '语言设置', 'lang_zh': '中文', 'lang_en': 'English', 'home_page': '商城页面', 'device_page': '设备页面', 'cart_page': '购物车页面', 'mine_page': '个人中心', }, 'en': { 'tab_home': 'Shop', 'tab_device': 'Device', 'tab_cart': 'Cart', 'tab_mine': 'Mine', 'theme_title': 'Theme Setting', 'theme_light': 'Light', 'theme_dark': 'Dark', 'theme_system': 'System Default', 'lang_title': 'Language', 'lang_zh': 'Chinese', 'lang_en': 'English', 'home_page': 'Shop Page', 'device_page': 'Device Page', 'cart_page': 'Cart Page', 'mine_page': 'Mine Page', }, }; String get(String key) { return _localizedValues[locale.languageCode]![key]!; } } class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> { const AppLocalizationsDelegate(); @override bool isSupported(Locale locale) => ['zh', 'en'].contains(locale.languageCode); @override Future<AppLocalizations> load(Locale locale) async { return AppLocalizations(locale); } @override bool shouldReload(AppLocalizationsDelegate old) => false; } // ---------------------- 全局状态管理(主题、语言) ---------------------- class AppState extends ChangeNotifier { ThemeMode _themeMode = ThemeMode.system; Locale? _locale; ThemeMode get themeMode => _themeMode; Locale? get locale => _locale; Future<void> loadSaved() async { final sp = await SharedPreferences.getInstance(); final themeIndex = sp.getInt('theme_mode'); if (themeIndex != null) { _themeMode = ThemeMode.values[themeIndex]; } final langCode = sp.getString('locale'); if (langCode != null) { _locale = Locale(langCode); } notifyListeners(); } Future<void> setThemeMode(ThemeMode mode) async { _themeMode = mode; final sp = await SharedPreferences.getInstance(); await sp.setInt('theme_mode', mode.index); notifyListeners(); } Future<void> setLocale(Locale? loc) async { _locale = loc; final sp = await SharedPreferences.getInstance(); if (loc == null) { await sp.remove('locale'); } else { await sp.setString('locale', loc.languageCode); } notifyListeners(); } } // ---------------------- 4 个 Tab 页面 ---------------------- class ShopPage extends StatelessWidget { const ShopPage({super.key}); @override Widget build(BuildContext context) { return Center( child: Text( AppLocalizations.of(context).get('home_page'), style: const TextStyle(fontSize: 22), ), ); } } class DevicePage extends StatelessWidget { const DevicePage({super.key}); @override Widget build(BuildContext context) { return Center( child: Text( AppLocalizations.of(context).get('device_page'), style: const TextStyle(fontSize: 22), ), ); } } class CartPage extends StatelessWidget { const CartPage({super.key}); @override Widget build(BuildContext context) { return Center( child: Text( AppLocalizations.of(context).get('cart_page'), style: const TextStyle(fontSize: 22), ), ); } } class MinePage extends StatelessWidget { const MinePage({super.key}); @override Widget build(BuildContext context) { final state = ChangeNotifierProvider.of<AppState>(context); final loc = AppLocalizations.of(context); return ListView( children: [ ListTile( title: Text(loc.get('theme_title')), subtitle: Text([ loc.get('theme_system'), loc.get('theme_light'), loc.get('theme_dark'), ][state.themeMode.index]), onTap: () => _showThemeDialog(context, state), ), ListTile( title: Text(loc.get('lang_title')), subtitle: Text( state.locale?.languageCode == 'zh' ? loc.get('lang_zh') : loc.get('lang_en'), ), onTap: () => _showLangDialog(context, state), ), ], ); } void _showThemeDialog(BuildContext context, AppState state) { final loc = AppLocalizations.of(context); showDialog( context: context, builder: (ctx) { return AlertDialog( title: Text(loc.get('theme_title')), content: Column( mainAxisSize: MainAxisSize.min, children: [ _themeItem(loc.get('theme_system'), ThemeMode.system, state), _themeItem(loc.get('theme_light'), ThemeMode.light, state), _themeItem(loc.get('theme_dark'), ThemeMode.dark, state), ], ), ); }, ); } Widget _themeItem(String title, ThemeMode mode, AppState state) { return ListTile( title: Text(title), onTap: () { state.setThemeMode(mode); Navigator.pop(context); }, trailing: state.themeMode == mode ? const Icon(Icons.check) : null, ); } void _showLangDialog(BuildContext context, AppState state) { final loc = AppLocalizations.of(context); showDialog( context: context, builder: (ctx) { return AlertDialog( title: Text(loc.get('lang_title')), content: Column( mainAxisSize: MainAxisSize.min, children: [ _langItem(loc.get('lang_zh'), Locale('zh'), state), _langItem(loc.get('lang_en'), Locale('en'), state), ], ), ); }, ); } Widget _langItem(String title, Locale loc, AppState state) { return ListTile( title: Text(title), onTap: () { state.setLocale(loc); Navigator.pop(context); }, trailing: state.locale?.languageCode == loc.languageCode ? const Icon(Icons.check) : null, ); } } // ---------------------- 主页面,底部 Tab ---------------------- class MainHome extends StatefulWidget { const MainHome({super.key}); @override State<MainHome> createState() => _MainHomeState(); } class _MainHomeState extends State<MainHome> { int _currentIndex = 0; final List<Widget> _pages = const [ ShopPage(), DevicePage(), CartPage(), MinePage(), ]; @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context); return Scaffold( body: _pages[_currentIndex], bottomNavigationBar: BottomNavigationBar( currentIndex: _currentIndex, onTap: (idx) { setState(() => _currentIndex = idx); }, type: BottomNavigationBarType.fixed, items: [ BottomNavigationBarItem( icon: const Icon(Icons.storefront), label: loc.get('tab_home'), ), BottomNavigationBarItem( icon: const Icon(Icons.devices), label: loc.get('tab_device'), ), BottomNavigationBarItem( icon: const Icon(Icons.shopping_cart), label: loc.get('tab_cart'), ), BottomNavigationBarItem( icon: const Icon(Icons.person), label: loc.get('tab_mine'), ), ], ), ); } } // ---------------------- main 入口 ---------------------- void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { late AppState _appState; bool _loaded = false; @override void initState() { super.initState(); _appState = AppState(); _appState.loadSaved().then((_) { setState(() => _loaded = true); }); } @override Widget build(BuildContext context) { if (!_loaded) { return const MaterialApp( home: Scaffold(body: Center(child: CircularProgressIndicator())), ); } return ChangeNotifierProvider.value( value: _appState, child: Consumer<AppState>( builder: (ctx, state, child) { return MaterialApp( title: '商城Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), useMaterial3: true, ), darkTheme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Colors.blue, brightness: Brightness.dark, ), useMaterial3: true, ), themeMode: state.themeMode, locale: state.locale, localizationsDelegates: const [ AppLocalizationsDelegate(), GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: const [Locale('zh'), Locale('en')], home: const MainHome(), ); }, ), ); } } // 每次 build 重新生成 InheritedWidget class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget { final T value; final Widget child; const ChangeNotifierProvider.value({ super.key, required this.value, required this.child, }); @override State<ChangeNotifierProvider<T>> createState() => _ChangeNotifierProviderState<T>(); static T of<T>(BuildContext context) { return context .dependOnInheritedWidgetOfExactType<_InheritedProvider<T>>()! .value; } } class _ChangeNotifierProviderState<T extends ChangeNotifier> extends State<ChangeNotifierProvider<T>> { @override void initState() { super.initState(); widget.value.addListener(_notify); } void _notify() { setState(() {}); } @override void dispose() { widget.value.removeListener(_notify); super.dispose(); } @override Widget build(BuildContext context) { // 每次 build 都新建 InheritedProvider 实例,触发更新 return _InheritedProvider<T>(value: widget.value, child: widget.child); } } class _InheritedProvider<T> extends InheritedWidget { final T value; const _InheritedProvider({required this.value, required super.child}); @override bool updateShouldNotify(_InheritedProvider<T> oldWidget) { return true; // 只要重建就通知所有依赖 } } class Consumer<T> extends StatelessWidget { final Widget Function(BuildContext context, T value, Widget? child) builder; const Consumer({super.key, required this.builder}); @override Widget build(BuildContext context) { final val = ChangeNotifierProvider.of<T>(context); return builder(context, val, null); } }