Flutter 暗黑模式

在iOS 13 和 android 10 之后的系统上添加了新特性,暗黑模式(darkMode) 可以实现随着系统主题模式的切换而进行app跟随的功能,色调可以在深色和浅色之间相互切换,暗黑模式自从发布以来,便受到广大用户的喜爱和支持。所以越来越多的app都已经接入了暗黑模式,同时苹果也要求app开发者对自己的app进行暗黑模式的适配工作,下面就介绍一下flutter项目在暗黑模式下的适配工作。

接入:

Flutter在MaterialApp中提供了themedarkTheme两个参数配置让我们设置两种模式下的主题颜色和文字样式,配置在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
主体模式分为三种:

  • 跟随系统
  • 浅色模式
  • 深色模式
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,
}

默认是使用系统的模式:

final ThemeMode mode = widget.themeMode ?? ThemeMode.system;

themeData
themeData的参数过多,我们这里就列举几个主要的

ThemeData({
  Brightness brightness, //深色还是浅色
  MaterialColor primarySwatch, //主题颜色样本
  Color primaryColor, //主色,决定导航栏颜色
  Color accentColor, //次级色,决定大多数Widget的颜色,如进度条、开关等。
  Color cardColor, //卡片颜色
  Color dividerColor, //分割线颜色
  ButtonThemeData buttonTheme, //按钮主题
  Color cursorColor, //输入框光标颜色
  Color dialogBackgroundColor,//对话框背景颜色
  String fontFamily, //文字字体
  TextTheme textTheme,// 字体主题,包括标题、body等文字样式
  IconThemeData iconTheme, // Icon的默认样式
  TargetPlatform platform, //指定平台,应用特定平台控件风格
  ...
})

** themeData**
themeData的模式值,在赋值的时候是需要判断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;
   ...

同时提供了工厂方法:

/// 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数据是在顶层

使用全局的主题
在Material 中配置theme和darkTheme的themeData,后续widget使用themeData的数据来设置

new MaterialApp(
    title: title,
    theme: ThemeData(
         primaryColor: Colors.red,
         //...
    ),
);

使用局部的主题
如有一些局部widget需要特殊处理来单独配置theme,则为该widget创建局部theme单独适配:

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在创建的时候主题选择的逻辑:

 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)函数使用它,因为theme的数据数据在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之上有一个单独的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;
    .......
在项目使用

项目中使用,默认是创建了亮色主题:

// init
themeState: ThemeState.initial(),

// ThemeState
factory ThemeState.initial() {
    return ThemeState(
      themeType: ThemeType.light,
    );
  }

// 有dark主题,但是一直没有使用
 static final Map _themes = {
    ThemeType.light: _buildBlueTheme(),
    ThemeType.dark: _buildDarkTheme(),
  };

// 部分颜色提供了 浅色和深色的区别。
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);

项目前期考虑了深浅色适配的问题,后续开发过程用因为没有适配的需求,导致现在要花大量时间来做适配的问题,任重而道远。

你可能感兴趣的:(Flutter 暗黑模式)