用户使用深色模式的理由有很多,总结其中比较突出的是下面三点:
Google 在 Android P 当中正式推出了自己的深色模式,Apple 也在 iOS 13 正式推出系统级的深色模式(在此之前,macOS 10.14 Mojave 上已经加入了 Dark Mode)。
在 Android 和 iOS 都正式推出系统级的深色模式后,当用户开启系统全局的深色模式后突然出现一个不支持的 App,这个 App 就会显得特别刺眼,甚至有些用户可能会不得不去寻找支持这一模式的替代品。所以,在项目资源(设计师、工程师、工时)允许的情况下,还是建议为 App 适配好 Dark Mode 的。 与之类似的就是 App 的多语言支持,当系统设置某种语言时,应用内的文字也相应变化。
下面主要从技术角度阐述如何在 Flutter App 上适配 Dark Mode,给用户更好的系统一致性体验。
我们后面需要使用 provider 包来作为状态管理工具,先把它添加到 pubspec.yaml 文件中,目前最新版本的添加方式如下:
dependencies:
provider: ^4.3.1
Flutter 在 MaterialApp 中已经提供了theme 与 darkTheme 两个参数让我们设置两种模式下的颜色及文字样式。接收的 ThemeData 值,几乎涵盖了所有 Material Widget 中所使用的颜色及主题,我们只需要在 ThemeData 值中适配好 App 基于不同 theme 需要用到的不同样式即可。而如果使用的 CupertinoApp ,目前最新稳定版 Flutter(v1.17.5) 只提供了 theme 参数,而 darkTheme 尚不支持。GitHub 上也有人提了相关 issue,将来应该也会支持的。
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: DisplayModel()),
ChangeNotifierProvider.value(value: LocaleModel())
],
child: Consumer2<DisplayModel, LocaleModel>(
builder: (context, displayModel, localeModel, _) {
return MaterialApp(
***
theme: displayModel.themeDate(),
darkTheme: displayModel.themeDate(darkTheme: true),
// themeMode 可以用于设置 App 主题是依赖于系统设定,还是手动设定
themeMode: displayModel.themeMode,
***
);
},
),
);
}
主题配置好之后,我们需要建一个主题变更通知类,来实现主题的动态切换。
class DisplayModel extends ChangeNotifier {
// 主题颜色
MaterialColor _materialColor = themeColorMap[SpHelper.getThemeColor()];
// 外观模式
ThemeMode _themeMode = SpHelper.getThemeMode();
// 字体选择
String _fontName = SpHelper.getFontFamily();
get materialColor => _materialColor;
get themeMode => _themeMode;
get fontName => _fontName;
ThemeData themeDate({bool darkTheme = false}) => ThemeData(
primarySwatch: _materialColor,
fontFamily: _fontName,
brightness: darkTheme ? Brightness.dark : Brightness.light,
);
switchColor(String newColor) {
_materialColor = themeColorMap[newColor];
notifyListeners();
SpHelper.sp.setString(SP_THEME_COLOR, newColor);
}
switchThemeMode(ThemeMode newThemeMode) {
_themeMode = newThemeMode;
notifyListeners();
SpHelper.sp.setString(
SP_THEME_MODE,
(_themeMode == ThemeMode.system)
? THEME_MODE_SYSTEM
: (_themeMode == ThemeMode.light
? THEME_MODE_LIGHT
: THEME_MODE_DARK));
}
switchFont(String newFontName) {
_fontName = newFontName;
notifyListeners();
SpHelper.sp.setString(SP_FONT_FAMILY, newFontName);
}
}
这样就配置好了主题获取和变更通知,接下来只需要在 App 设置页面,操作模式切换的地方(通常可以是 RadioListTile、SwitchListTile 等)调用:
onChanged: (newValue) {
setState(() {
_currentAppearance = newValue;
// 触发主题变更
Provider.of<DisplayModel>(context, listen: false)
.switchThemeMode(newValue);
});
},

需要额外注意的一点是,这里实现的跟随系统功能是需要判断设备操作系统版本的。因为如开头所诉,系统级的深色模式,是从 Android P 和 iOS 13 才开始支持的。如果设备系统版本低于它们,则只能实现在深色和浅色两种模式中手动来切换。
放几张适配后的效果图给大家看看:


详细的代码以及实现细节,可以参看 V2LF 项目的代码。
App 技术层面的 Dark Mode 的适配,实现起来其实并不复杂。个人感觉,如果想要把 App 深色模式的体验做好,更多的还是考验设计师的设计能力。甚至还需要研究不同操作系统本身对深色模式给出的视觉规范。