Flutter 暗黑模式

在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中提供了themedarkTheme兩個(gè)參數(shù)配置讓我們?cè)O(shè)置兩種模式下的主題顏色和文字樣式,配置在App的入口處,所以涵蓋了Material Widget中的顏色和文字樣式(前提是子widget使用了Material提供的themedarkTheme)

具體的入口代碼:

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();

主題的獲取
我們可以在Widgetbuild方法中通過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)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容