在iOS 13 和 android 10 之后的系統(tǒng)上添加了新特性,暗黑模式(darkMode) 可以實(shí)現(xiàn)隨著系統(tǒng)主題模式的切換而進(jìn)行app跟隨的功能,色調(diào)可以在深色和淺色之間相互切換,暗黑模式自從發(fā)布以來,便受到廣大用戶的喜愛和支持。所以越來越多的app都已經(jīng)接入了暗黑模式,同時(shí)蘋果也要求app開發(fā)者對(duì)自己的app進(jìn)行暗黑模式的適配工作,下面就介紹一下flutter項(xiàng)目在暗黑模式下的適配工作。
接入:
Flutter在MaterialApp中提供了theme和darkTheme兩個(gè)參數(shù)配置讓我們?cè)O(shè)置兩種模式下的主題顏色和文字樣式,配置在App的入口處,所以涵蓋了Material Widget中的顏色和文字樣式(前提是子widget使用了Material提供的theme和darkTheme)
具體的入口代碼:
MaterialApp(
title: 'Flutter Demo',
themeMode: ThemeMode.system,
darkTheme: ThemeData(
primarySwatch: Colors.red,
),
theme: ThemeData(
primarySwatch: Colors.blue,
),
highContrastTheme: ThemeData(),
highContrastDarkTheme: ThemeData(),
home: MyHomePage(title: 'Flutter Demo Home Page'),
)
themeMode
主體模式分為三種:
- 跟隨系統(tǒng)
- 淺色模式
- 深色模式
enum ThemeMode {
/// Use either the light or dark theme based on what the user has selected in
/// the system settings.
system,
/// Always use the light mode regardless of system preference.
light,
/// Always use the dark mode (if available) regardless of system preference.
dark,
}
默認(rèn)是使用系統(tǒng)的模式:
final ThemeMode mode = widget.themeMode ?? ThemeMode.system;
themeData
themeData的參數(shù)過多,我們這里就列舉幾個(gè)主要的
ThemeData({
Brightness brightness, //深色還是淺色
MaterialColor primarySwatch, //主題顏色樣本
Color primaryColor, //主色,決定導(dǎo)航欄顏色
Color accentColor, //次級(jí)色,決定大多數(shù)Widget的顏色,如進(jìn)度條、開關(guān)等。
Color cardColor, //卡片顏色
Color dividerColor, //分割線顏色
ButtonThemeData buttonTheme, //按鈕主題
Color cursorColor, //輸入框光標(biāo)顏色
Color dialogBackgroundColor,//對(duì)話框背景顏色
String fontFamily, //文字字體
TextTheme textTheme,// 字體主題,包括標(biāo)題、body等文字樣式
IconThemeData iconTheme, // Icon的默認(rèn)樣式
TargetPlatform platform, //指定平臺(tái),應(yīng)用特定平臺(tái)控件風(fēng)格
...
})
** themeData**
themeData的模式值,在賦值的時(shí)候是需要判斷isdark來判斷的
assert(colorScheme?.brightness == null || brightness == null || colorScheme.brightness == brightness);
final Brightness _brightness = brightness ?? colorScheme?.brightness ?? Brightness.light;
final bool isDark = _brightness == Brightness.dark;
visualDensity ??= const VisualDensity();
primarySwatch ??= Colors.blue;
primaryColor ??= isDark ? Colors.grey[900] : primarySwatch;
...
同時(shí)提供了工廠方法:
/// A default light blue theme.
///
/// This theme does not contain text geometry. Instead, it is expected that
/// this theme is localized using text geometry using [ThemeData.localize].
factory ThemeData.light() => ThemeData(brightness: Brightness.light);
/// A default dark theme with a teal secondary [ColorScheme] color.
///
/// This theme does not contain text geometry. Instead, it is expected that
/// this theme is localized using text geometry using [ThemeData.localize].
factory ThemeData.dark() => ThemeData(brightness: Brightness.dark);
theme的使用策略
theme或者darkTheme數(shù)據(jù)是在頂層
使用全局的主題
在Material 中配置theme和darkTheme的themeData,后續(xù)widget使用themeData的數(shù)據(jù)來設(shè)置
new MaterialApp(
title: title,
theme: ThemeData(
primaryColor: Colors.red,
//...
),
);
使用局部的主題
如有一些局部widget需要特殊處理來單獨(dú)配置theme,則為該widget創(chuàng)建局部theme單獨(dú)適配:
new Theme(
data: ThemeData(
accentColor: Colors.yellow,
//...
),
child: Text('Hello World'),
);
如果只是想修改主題中的部分樣式,可以使用copyWith的方法來繼承:
new Theme(
data: Theme.of(context).copyWith(accentColor: Colors.yellow),
child: Text('copyWith method'),
);
Material App在創(chuàng)建的時(shí)候主題選擇的邏輯:
Widget _materialBuilder(BuildContext context, Widget? child) {
// Resolve which theme to use based on brightness and high contrast.
final ThemeMode mode = widget.themeMode ?? ThemeMode.system;
final Brightness platformBrightness = MediaQuery.platformBrightnessOf(context);
final bool useDarkTheme = mode == ThemeMode.dark
|| (mode == ThemeMode.system && platformBrightness == ui.Brightness.dark);
final bool highContrast = MediaQuery.highContrastOf(context);
ThemeData? theme;
if (useDarkTheme && highContrast && widget.highContrastDarkTheme != null) {
theme = widget.highContrastDarkTheme;
} else if (useDarkTheme && widget.darkTheme != null) {
theme = widget.darkTheme;
} else if (highContrast && widget.highContrastTheme != null) {
theme = widget.highContrastTheme;
}
theme ??= widget.theme ?? ThemeData.light();
主題的獲取
我們可以在Widget的build方法中通過Theme.of(context)函數(shù)使用它,因?yàn)閠heme的數(shù)據(jù)數(shù)據(jù)在InheritedTheme中,查詢其父類是InheritedWidget.
static ThemeData of(BuildContext context, { bool shadowThemeOnly = false }) {
final _InheritedTheme inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedTheme>();
if (shadowThemeOnly) {
if (inheritedTheme == null || inheritedTheme.theme.isMaterialAppTheme)
return null;
return inheritedTheme.theme.data;
}
// _InheritedTheme
class _InheritedTheme extends InheritedTheme
// InheritedTheme
abstract class InheritedTheme extends InheritedWidget
Theme.of(context)將查找Widget樹并返回樹中最近的Theme。如果我們的Widget之上有一個(gè)單獨(dú)的Theme定義,則返回該值。如果不是,則返回App主題。
舉例:FloatingActionButton使用theme:
final ThemeData theme = Theme.of(context);
final FloatingActionButtonThemeData floatingActionButtonTheme = theme.floatingActionButtonTheme;
// Applications should no longer use accentIconTheme's color to configure
// the foreground color of floating action buttons. For more information, see
// https://flutter.dev/go/remove-fab-accent-theme-dependency.
if (this.foregroundColor == null && floatingActionButtonTheme.foregroundColor == null) {
final bool accentIsDark = theme.accentColorBrightness == Brightness.dark;
final Color defaultAccentIconThemeColor = accentIsDark ? Colors.white : Colors.black;
final Color foregroundColor = this.foregroundColor
?? floatingActionButtonTheme.foregroundColor
?? theme.colorScheme.onSecondary;
final Color backgroundColor = this.backgroundColor
?? floatingActionButtonTheme.backgroundColor
?? theme.colorScheme.secondary;
final Color focusColor = this.focusColor
?? floatingActionButtonTheme.focusColor
?? theme.focusColor;
.......
在項(xiàng)目使用
項(xiàng)目中使用,默認(rèn)是創(chuàng)建了亮色主題:
// init
themeState: ThemeState.initial(),
// ThemeState
factory ThemeState.initial() {
return ThemeState(
themeType: ThemeType.light,
);
}
// 有dark主題,但是一直沒有使用
static final Map<ThemeType, CustomTheme> _themes = {
ThemeType.light: _buildBlueTheme(),
ThemeType.dark: _buildDarkTheme(),
};
// 部分顏色提供了 淺色和深色的區(qū)別。
static bool get isLight => _theme.isLight;
static ButtonStyle get bsOutlineAuto => isLight ? bsOutline : bsOutlineDark;
// 但是適配的顏色較少,且大量顏色沒有做深淺色適配。
static Color get black => Colors.black.withOpacity(a1);
static Color get black2 => Colors.black.withOpacity(a2);
項(xiàng)目前期考慮了深淺色適配的問題,后續(xù)開發(fā)過程用因?yàn)闆]有適配的需求,導(dǎo)致現(xiàn)在要花大量時(shí)間來做適配的問題,任重而道遠(yuǎn)。