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<ThemeType, CustomTheme> _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);

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容