Flutter在MaterialApp
中提供了theme
和darkTheme
两个参数配置让我们设置两种模式下的主题颜色和文字样式,配置在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
主体模式分为三种:
- 跟随系统
- 浅色模式
- 深色模式
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();
主题的获取
我们可以在Widget
的build
方法中通过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);