如果習慣亮色就會使用 Light Mode,如果是習慣暗色就會使用 Dark Mode,像上圖左就是亮色模式,上圖右就是暗色模式。
那麼,在 Flutter 中要如何去因為模式的切換而改變主題顯色呢?可以想見的第一步是我們必須要取得當前手機的亮暗色模式,在 Flutter 中,我們會使用 MediaQuery.of(context).platformBrightness
。
Android
在 Android Studio 中,我們會這樣去 Debug 是在 Dark Mode 還是 Light Mode,例如在 Fragment 中加入下面這段:
override fun onResume() {
super.onResume()
val nightModeFlags: Int = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
when (nightModeFlags) {
Configuration.UI_MODE_NIGHT_YES -> print("Dark mode is active")
Configuration.UI_MODE_NIGHT_NO -> print("Light mode is active")
}
}
這樣就可在 Fragment Resume 後,確認修改的 Mode,透過 resources.configuration.uiMode
、Configuration.UI_MODE_NIGHT_MASK
來
確認現在的 Mode,放在 onResume() 裡,就可以即時修改,在 Debug 模式下追蹤 Function 跑到哪裡了。
iOS
在 iOS 中,我們可以透過點擊按鈕來去檢查現在的顏色模式,如下:
@IBAction func buttonTapped(_ sender: UIButton) {
let style = traitCollection.userInterfaceStyle
switch style {
case .dark: print("Dark Mode")
case .light: print("Light Mode")
case .unspecified: return
@unknown default: return
}
}
使用的是 traitCollection.userInterfaceStyle
,下面我們就來使用 Flutter 的 MediaQuery
。
繼續閱讀|回目錄
Flutter
在 Flutter 中,想要透過手機切換 Dark / Light Mode,來變更我們的 APP 主題配色,首先,新增一個 AppTheme
class 如下:
class AppTheme {
ThemeData themeData;
// 工廠方法根據 brightness 返回不同的主題
factory AppTheme(Brightness brightness) {
if (brightness == Brightness.dark) {
return AppTheme._internal(_buildDarkTheme());
} else {
return AppTheme._internal(_buildLightTheme());
}
}
AppTheme._internal(this.themeData);
// 深色模式的主題
static ThemeData _buildDarkTheme() {
return ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.grey[900],
hintColor: Colors.blue[200],
textTheme: const TextTheme(
bodyLarge: TextStyle(color: Colors.white),
), colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.dark
),
);
}
// 淺色模式的主題
static ThemeData _buildLightTheme() {
return ThemeData(
brightness: Brightness.light,
primaryColor: Colors.white,
hintColor: Colors.blue[700],
textTheme: const TextTheme(
bodyLarge: TextStyle(color: Colors.black),
), colorScheme: ColorScheme.fromSeed(
seedColor: Colors.white,
brightness: Brightness.light
),
);
}
}
APP 透過存取 themeData
來更改主題色。
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
final appTheme = AppTheme(MediaQuery.of(context).platformBrightness);
return MaterialApp(
title: 'Dark/Light Mode App',
theme: appTheme.themeData, // 使用 AppTheme 中的 ThemeData
home: const SignUpPage(),
);
}
}
這個 themeData 就是 AppTheme 實例中的屬性,如果是 Dark Mode,就用 Dark Mode 的 function 來建構 Theme,反之亦然,也就是建構參數 Brightness,看你傳入的是 .dark 或不是,來決定用哪一個 function,不同的 function 建構出不同的主題配色
。
而我們取得現在 Mode 使用了 MediaQuery.of(context).platformBrightness,MediaQuery 是 class MediaQuery extends InheritedModel<_MediaQueryAspect> { },而從 _MediaQueryAspect 可以看到如下:
enum _MediaQueryAspect {
/// Specifies the aspect corresponding to [MediaQueryData.size].
size,
/// Specifies the aspect corresponding to [MediaQueryData.orientation].
orientation,
/// Specifies the aspect corresponding to [MediaQueryData.devicePixelRatio].
devicePixelRatio,
/// Specifies the aspect corresponding to [MediaQueryData.textScaleFactor].
textScaleFactor,
/// Specifies the aspect corresponding to [MediaQueryData.textScaler].
textScaler,
/// Specifies the aspect corresponding to [MediaQueryData.platformBrightness].
platformBrightness,
/// Specifies the aspect corresponding to [MediaQueryData.padding].
padding,
/// Specifies the aspect corresponding to [MediaQueryData.viewInsets].
viewInsets,
/// Specifies the aspect corresponding to [MediaQueryData.systemGestureInsets].
systemGestureInsets,
/// Specifies the aspect corresponding to [MediaQueryData.viewPadding].
viewPadding,
/// Specifies the aspect corresponding to [MediaQueryData.alwaysUse24HourFormat].
alwaysUse24HourFormat,
/// Specifies the aspect corresponding to [MediaQueryData.accessibleNavigation].
accessibleNavigation,
/// Specifies the aspect corresponding to [MediaQueryData.invertColors].
invertColors,
/// Specifies the aspect corresponding to [MediaQueryData.highContrast].
highContrast,
/// Specifies the aspect corresponding to [MediaQueryData.onOffSwitchLabels].
onOffSwitchLabels,
/// Specifies the aspect corresponding to [MediaQueryData.disableAnimations].
disableAnimations,
/// Specifies the aspect corresponding to [MediaQueryData.boldText].
boldText,
/// Specifies the aspect corresponding to [MediaQueryData.navigationMode].
navigationMode,
/// Specifies the aspect corresponding to [MediaQueryData.gestureSettings].
gestureSettings,
/// Specifies the aspect corresponding to [MediaQueryData.displayFeatures].
displayFeatures,
/// Specifies the aspect corresponding to [MediaQueryData.supportsShowingSystemContextMenu].
supportsShowingSystemContextMenu,
}
也就是可以透過 MediaQuery 得到 size
、orientation
,而這次要的就是 platformBrightness
,以 size 來說,在 iOS 中,我們常用 UIScreen.main.bounds
去取得手機相關大小資訊,在 Android 則是 DisplayMetrics。
在 Android 我們會這樣做:
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
val windowMetrics = windowManager.currentWindowMetrics
val bounds = windowMetrics.bounds
val widthPixels = bounds.width()
val heightPixels = bounds.height()
Log.e("widthPixels", "onCreate: $widthPixels")
Log.e("heightPixels", "onCreate: $heightPixels")
同樣地要取得 bounds
之前要先取得 windowManager,才能夠獲取 current window metrics。
iOS
那麼,接下來是 Flutter 的成果,我們先看 iOS 的部份: