上一节创建了项目,并通过 Intl 实现在项目的多语言国际化支持。但是还没有在项目提供多语言的切换功能。

这一节除了实现多语言的选择切换功能,还同时实现项目的深色、浅色主题切换,需要用过以下两个包:

  1. Provider: ProviderGoogle I/O 2019官方推荐的状态管理方式,是一套实现跨组件状态共享的解决方案。

  2. shared_preferences : shared_preferences 是一个轻量级存储类,以键值对的形式保存设置,属性和数据。

准备工作

在项目的 pubspec.yaml 文件中增加以下包依赖:

Provider的最新版本查询:https://pub.dev/packages/provider
shared_preferences的最新版本查询:https://pub.dev/packages/shared_preferences

1
2
3
4
5
6
7
8
9
10
11
12
dependencies:
flutter:
sdk: flutter
# 国际化
flutter_localizations:
sdk: flutter

cupertino_icons: ^1.0.0
intl: ^0.16.1
intl_translation: ^0.17.10+1
provider: ^4.3.3 # 增加这一行
shared_preferences: ^0.5.12+4 # 增加这一行

保存 pubspec.yaml 文件以获取包文件

打开语言文件 lib\l10n\intl_en.arb,编辑其内容如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
{
"appName": "Deep Sky",
"login": "Login",
"tabHome": "Home",
"tabProject": "Project",
"settings": "Settings",
"settingLanguage": "Language",
"autoBySystem": "Auto",
"themeMode": "Theme Mode",
"darkMode": "Dark Mode",
"lightMode":"Light Mode"
}

打开语言文件 lib\l10n\intl_zh.arb,编辑其内容如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
{
"appName": "深处天空",
"login": "登录",
"tabHome": "首页",
"tabProject": "项目",
"settings": "设置",
"settingLanguage": "语言设置",
"autoBySystem": "跟随系统",
"themeMode": "主题模式",
"darkMode": "深色模式",
"lightMode":"浅色模式"
}

主导航页

在项目中新建文件夹: lib\views

在项目中新建文件: lib\views\home_page.dart

在项目中新建文件夹: lib\views\settings

在项目中新建文件:lib\views\settings\settings_page.dart

打开文件 lib\views\home_page.dart , 编辑其内容如以下代码:

1
2
3
4
5
6
7
8
9
10
import 'package:flutter/material.dart';

class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Text('Home'),
);
}
}

打开文件 lib\views\settings\settings_page.dart, 编辑其内容如以下代码:

1
2
3
4
5
6
7
8
9
10
import 'package:flutter/material.dart';

class SettingsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Text('Settings'),
);
}
}

打开文件 lib\views\tab_navigator_page.dart, 编辑其内容如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import 'package:flutter/material.dart';
import 'package:deep_sky_app/generated/l10n.dart';
import 'package:deep_sky_app/views/home_page.dart';
import 'package:deep_sky_app/views/settings/settings_page.dart';

class TabNavigatorPage extends StatefulWidget {
@override
_TabNavigatorPageState createState() => _TabNavigatorPageState();
}

class _TabNavigatorPageState extends State<TabNavigatorPage> {
int _selectedIndex = 0;
List<Widget> widgets = [HomePage(), HomePage(), SettingsPage()];
@override
Widget build(BuildContext context) {
return Container(
child: Scaffold(
body: widgets[_selectedIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: (index) {
setState(() {
_selectedIndex = index;
});
},
currentIndex: _selectedIndex,
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home_outlined),
activeIcon: Icon(Icons.home),
label: S.of(context).tabHome,
),
BottomNavigationBarItem(
icon: Icon(Icons.adb_outlined),
label: S.of(context).tabProject,
),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle_outlined),
activeIcon: Icon(Icons.account_circle),
label: S.of(context).settings,
),
],
),
),
);
}
}

项目运行

添加 Provider 状态管理类

新建文件夹:lib\provider, 然后新建两个状态管理文件:lib\provider\locale_provider.dartlib\provider\theme_provider.dart

主题模式状态管理

打开文件 lib\provider\theme_provider.dart, 编辑其内容如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:deep_sky_app/generated/l10n.dart';

// 参考资料:https://omadijaya.id/flutter-dynamic-dark-mode-with-provider-and-shared-preferences/

class ThemeProvider extends ChangeNotifier {
final String key = 'theme'; // 用于保存当前主题状态的键名
// 定义用于三种主题模式的字典
Map themeModeList = <String, ThemeMode>{
'dark': ThemeMode.dark, // 深色模式
'light': ThemeMode.light, // 浅色模式
'system': ThemeMode.system // 跟随系统
};

SharedPreferences _preferences;
String _themeMode;

// 返回当前的主题模式
String get themeMode => _themeMode;

// 构造方法
ThemeProvider() {
_themeMode = 'system'; // 默认为跟随系统
_loadFromPreferences(); // 读取已有模式设置
}

// 返回用于显示在界面上的主题选项名称
static String getThemeModeName(String mode, context) {
switch (mode) {
case 'dark':
return S.of(context).darkMode;
case 'light':
return S.of(context).lightMode;
default:
return S.of(context).autoBySystem;
}
}

// 返回当前的主题模式
ThemeMode getThemeMode(String mode) {
return themeModeList[mode];
}

// 深色、浅色模式设置
ThemeData getThemeData({bool isDark = false}) {
return ThemeData(brightness: isDark ? Brightness.dark : Brightness.light);
}

// 初始化 SharedPreferences
_initialPreferences() async {
if (_preferences == null)
_preferences = await SharedPreferences.getInstance();
}

// 保存状态
_savePreferences() async {
await _initialPreferences();
_preferences.setString(key, _themeMode);
}

// 读取状态
_loadFromPreferences() async {
await _initialPreferences();
_themeMode = _preferences.getString(key) ?? 'system';
notifyListeners(); // 变更通知,在数据处理完成后执行
}

// 深浅色切换状态
toggleChangeTheme(val) {
_themeMode = val;
print('current theme mode: $_themeMode');
_savePreferences();
notifyListeners(); // 变更通知,在数据处理完成后执行
}
}

多语言状态管理

打开文件 lib\provider\locale_provider.dart, 编辑其内容如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:deep_sky_app/generated/l10n.dart';

class LocaleProvider extends ChangeNotifier {
final String key = 'locale'; // 用于保存当前语言的键名

SharedPreferences _preferences;

String _language; // 当前的语言名称,例如:'zh','en'等

String get language => _language; // 返回当前语言名称

Locale get locale {
if (_language != '') {
return Locale(_language);
}
return null;
}

// 返回用于显示在界面中的语言名字
static String localeName(String lang, context) {
switch (lang) {
case 'en':
return 'English';
case 'zh':
// case 'zh_CN':
return '简体中文';
case '':
return S.of(context).autoBySystem;
}
}

// 构造方法
LocaleProvider() {
_language = ''; // 默认语言跟随系统
_loadFromPreferences();
}

// 初始化 SharedPreferences
_initialPreferences() async {
if (_preferences == null)
_preferences = await SharedPreferences.getInstance();
}

// 保存当前的语言名称
_savePreferences() async {
await _initialPreferences();
_preferences.setString(key, _language);
}

// 读取已保存的语言名称
_loadFromPreferences() async {
await _initialPreferences();
_language = _preferences.getString(key) ?? '';
notifyListeners(); // 变更通知,在数据处理完成后执行
}

// 切换语言
toggleChangeLocale(String language) {
_language = language;
print('current locale: $language');

_savePreferences();
notifyListeners(); // 变更通知,在数据处理完成后执行
}
}

修改入口文件 main.dart

打开文件lib\main.dart, 编辑其内容如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';
import 'package:deep_sky_app/generated/l10n.dart';
import 'package:deep_sky_app/provider/locale_provider.dart';
import 'package:deep_sky_app/provider/theme_provider.dart';
import 'package:deep_sky_app/views/tab_navigator_page.dart';

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

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
// MultiProvider 用于管理多个 Provider
return MultiProvider(
providers: [
// ChangeNotifierProvider 用于对 ChangeNotifier 进行监听
ChangeNotifierProvider<ThemeProvider>(
create: (context) => ThemeProvider()),
// ChangeNotifierProvider 用于对 ChangeNotifier 进行监听
ChangeNotifierProvider<LocaleProvider>(
create: (context) => LocaleProvider()),
],

// 通过 Consumer2 来组合同时监听两个结果,<>内的对象数量与 Consumer 后面的数字一致
child: Consumer2<ThemeProvider, LocaleProvider>(
builder: (context, ThemeProvider themeProvider,
LocaleProvider localeProvider, child) {
return MaterialApp(
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],

// 语言列表方式一:自动获取所有语言列表
// supportedLocales: S.delegate.supportedLocales,

// 语言列表方式二:自动获取并设置一个默认语言
supportedLocales: [
const Locale('en', ''),
...S.delegate.supportedLocales
],
// 语言列表方式三:手动添加语言列表
// supportedLocales: [
// const Locale('zh', 'CN'),
// const Locale('en', 'US'),
// ],

// localeListResolutionCallback: (locale, supportedLocales) {
// print(locale); // 在控制台显示当前语言
// return null;
// },

// 插件目前不完善手动处理简繁体
// 也用于处理 zh 和 zh_CN 这样有两个不同名字的语言
localeResolutionCallback: (locale, supportLocales) {
// 中文 简繁体处理
if (locale?.languageCode == 'zh') {
if (locale?.scriptCode == 'Hant') {
return const Locale('zh', 'HK'); //繁体
} else {
return const Locale('zh', 'CN'); //简体
}
}
return null;
},

// 获取 当前的语言
locale: Provider.of<LocaleProvider>(context, listen: false).locale,

title: 'Deep Sky',
// 生成应用标题
onGenerateTitle: (context) {
return S.of(context).appName;
},
// 深色、浅色主题选择 的第一种方式
// theme: themeProvider.darkMode ? dark_mode : light_mode,

theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),

// 深色、浅色主题选择 的第二种方式的第一个选项
themeMode: themeProvider.getThemeMode(
Provider.of<ThemeProvider>(context, listen: false).themeMode),

// 深色、浅色主题选择 的第二种方式的第二个选项, 少了这个上面的第一个选项不起作用
darkTheme: ThemeData(brightness: Brightness.dark),

// 设置文字大小不随系统设置改变
builder: (context, widget) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
child: widget);
},

// 主页
home: TabNavigatorPage(),
);
},
),
);
}
}

设置页

新建文件夹:lib\views\settings\widgets

然后新建文件:lib\views\settings\widgets\account_info.dartlib\views\settings\widgets\common_settings.dart

设置页的用户信息部分

打开文件lib\views\settings\widgets\account_info.dart, 编辑其内容如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import 'package:flutter/material.dart';
import 'package:deep_sky_app/generated/l10n.dart';

class AccountInfo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.only(top: 80, bottom: 50),
child: CircleAvatar(
backgroundColor: Theme.of(context).primaryColor,
radius: 50,
child: Text(
S.of(context).login,
style: TextStyle(fontSize: 20),
),
),
);
}
}

设置页的 主题模式切换多语言切换部分

打开文件lib\views\settings\widgets\common_settings.dart,编辑其内容如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:deep_sky_app/generated/l10n.dart';
import 'package:deep_sky_app/provider/locale_provider.dart';
import 'package:deep_sky_app/provider/theme_provider.dart';

class CommonSettings extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
// 使用 MediaQuery 获取屏幕宽度
width: MediaQuery.of(context).size.width,
child: Column(
// 交叉轴对齐方式
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(left: 15, top: 10),
child: Text(S.of(context).settings),
),
SizedBox(
height: 10,
),
Container(
child: ExpansionTile(
title: Row(
// 主轴对齐方式
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(S.of(context).themeMode),
Text(ThemeProvider.getThemeModeName(
Provider.of<ThemeProvider>(context).themeMode, context)),
],
),
children: [
// system: 表示主题模式跟随系统
_themeModeItem(Icon(Icons.sync), 'system', context),
// dark: 深色模式
_themeModeItem(Icon(Icons.brightness_2), 'dark', context),
// light:浅色模式
_themeModeItem(Icon(Icons.wb_sunny_outlined), 'light', context),
],
),
),
Container(
child: ExpansionTile(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(S.of(context).settingLanguage),
Text(LocaleProvider.localeName(
Provider.of<LocaleProvider>(context).language, context)),
],
),
children: [
// '': 表示 语言跟随系统
_languageItem('', context),
// 'zh': 表示(简体)中文
_languageItem('zh', context),
// 'en': 表示 English
_languageItem('en', context),
],
),
),
],
),
);
}

// 多语言设置选项
Widget _languageItem(String lang, context) {
return InkWell(
onTap: () {
// Provider 状态修改方式一:
Provider.of<LocaleProvider>(context, listen: false)
.toggleChangeLocale(lang);
},
child: Container(
padding: EdgeInsets.only(
left: 15,
right: 15,
top: 0,
bottom: 0,
),
child: Container(
// 添加 ListTile 选中项的背景颜色
decoration: new BoxDecoration(
color: Provider.of<LocaleProvider>(context).language == lang
? Theme.of(context).buttonColor
: null,
),
child: ListTile(
leading: Icon(Icons.drag_handle),
title: Container(
// 缩小 leading 和 title之的间隔
transform: Matrix4.translationValues(-20, 0.0, 0.0),
child: Text(
LocaleProvider.localeName(lang, context),
// style: TextStyle(
// color: Provider.of<LocaleProvider>(context).language == lang
// ? Theme.of(context).primaryColor
// : null,
// ),
),
),
// title: Text(LocaleProvider.localeName(lang, context)),
// trailing: Opacity(
// opacity:
// Provider.of<LocaleProvider>(context).language == lang ? 1 : 0,
// child: Icon(Icons.done),
// ),
trailing: Provider.of<LocaleProvider>(context).language == lang
? Icon(Icons.done)
: null,
),
),
),
);
}

// 主题选择选项
Widget _themeModeItem(Icon icon, String mode, context) {
// Provider 状态修改方式二:1. Consumer<ThemeProvider>(builder:)
return Consumer<ThemeProvider>(
builder: (context, themeProvider, child) => InkWell(
onTap: () {
// Provider 状态修改方式二:2. 调用
themeProvider.toggleChangeTheme(mode);
},
child: Container(
padding: EdgeInsets.only(
left: 15,
right: 15,
top: 0,
bottom: 0,
),
child: ListTile(
leading: icon,
title: Container(
// 缩小 leading 和 title之的间隔
transform: Matrix4.translationValues(-20, 0.0, 0.0),
child: Text(ThemeProvider.getThemeModeName(mode, context)),
),
// trailing: Opacity(
// opacity: themeProvider.themeMode == mode ? 1 : 0,
// child: Icon(Icons.done),
// ),
trailing: themeProvider.themeMode == mode ? Icon(Icons.done) : null,
),
),
),
);
}
}

编辑设置页

打开文件lib\views\settings\settings_page.dart,编辑其内容如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import 'package:flutter/material.dart';
import 'package:deep_sky_app/views/settings/widgets/account_info.dart';
import 'package:deep_sky_app/views/settings/widgets/common_settings.dart';

class SettingsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Container(
width: MediaQuery.of(context).size.width,
child: Column(
children: [
AccountInfo(),
SizedBox(
height: 5,
),
CommonSettings(),
SizedBox(
height: 5,
),
],
),
),
);
}
}

运行项目

启动项目调试,程序运行结果如下:

简体中文、浅色主题:

English、深色主题:

===END===