“深色模式(Dark Mode),也被称为暗黑模式,是一种高对比度,或者反色模式的显示模式,开启以后在夜间能够缓解疲劳,更易于阅读,同时也能在必定程度上达到省电的效果。iOS和安卓分别从 iOS 13 和 Android 10(不一样厂商不尽相同,部分 Android 9 也支持) 开始加入深色模式的支持,各大浏览器纷纷开始支持深色模式,强如微信也终于在 iOS 客户端 7.0.十二、Android 客户端 7.0.13 支持了深色模式,等网页端适配深色模式后将更进一步提升用户体验的一致性。html
最近在业余时间开发本身的 App,起初并开始考虑深色模式的适配,到晚上的时候,界面惨不忍睹。虽然能够手动在系统设置里配置外观,可是全局修改也会影响其余 App(很讨厌修改了本身而影响了别人,比较倾向自完备性)。ios
对我来讲,适配深色模式是势在必行的:git
用户能够主动设置深色模式、浅色模式、跟随系统github
要实现这个需求,能够先问几个问题:web
咱们一块儿逐个攻破上面的问题。canvas
Flutter 提供了 Theme 组件,它能够设置 Widget
的主题,Theme
组件能够为 Material
App 定义主题数据(ThemeData
)。Material 组件库里不少组件都使用了主题数据,如导航栏颜色、标题字体、Icon样式等。Theme 内会使用 InheritedWidget
来为其子树共享样式数据。它有两种:数组
全局 Theme 是由应用程序根 MaterialApp
的 Theme
:浏览器
/// 全局主题在MaterialApp的theme属性
/// 全局生效 MaterialApp( title: 'demo', theme: ThemeData( // 这里就是参数 brightness: Brightness.dark, primaryColor: Colors.lightBlue[800], accentColor: Colors.cyan[600], ), ); 复制代码
局部 Theme:微信
/// 假如咱们要给 FloatingActionButton 设置主题样式
/// 直接写个 Theme 包裹 FloatingActionButton 组件 /// 而后设置 data,接收类型依然是 ThemeData,里面填写咱们的参数 /// (若是没有设置局部主题则默认使用全局主题) Theme( data: ThemeData( accentColor: Colors.red, ), child: FloatingActionButton( onPressed: () {}, child: Icon(Icons.add), ), ); 复制代码
扩展父主题时无需覆盖全部的主题属性,能够经过使用 copyWith
方法来实现。markdown
Theme(
data: Theme.of(context).copyWith(accentColor: Colors.yellow), child: FloatingActionButton( onPressed: (){}, child: new Icon(Icons.add), ), ); 复制代码
Theme.of(context)
将查找 Widget
树并返回树中最近的 Theme
。若是 Widget
之上有一个单独的 Theme
定义,则返回该值。若是没有,则返回 App 主题。
咱们也可使用 io 包里的 Platform 来进行判断。
MaterialApp(
theme: defaultTargetPlatform == TargetPlatform.iOS ? iOSTheme : AndroidTheme, title: 'Flutter Theme', home: new MyHomePage(), ) 复制代码
经过 Theme.of(context).brightness
的来判断如今是深色仍是浅色模式。
var isDarkTheme = Theme.of(context).brightness == Brightness.dark;
Text("APP", color : isDarkTheme ? AppColors.darkPink : AppColors.textBlack, ) 复制代码
上面说了这么多主题的使用,可是当咱们真正要进行适配的时候,仍是无从下手,由于咱们不知道设置主题后到底起了哪些样式变化,那么 ThemeData
就是咱们的答案。
ThemeData({
Brightness brightness, // 应用程序总体主题的亮度。 由按钮等 Widget 使用,以肯定在不使用主色或强调色时要选择的颜色 MaterialColor primarySwatch, // 主题颜色样本 Color primaryColor, // 前景色(文本、按钮等) Brightness primaryColorBrightness, // primaryColor 的亮度 Color primaryColorLight, // primaryColor 的较亮版本 Color primaryColorDark, // primaryColor 的较暗版本 Color accentColor, // 前景色(文本、按钮等) Brightness accentColorBrightness, // accentColor的亮度。 用于肯定放置在突出颜色顶部的文本和图标的颜色(例如FloatingButton上的图标) Color canvasColor, // MaterialType.canvas Material 的默认颜色 Color scaffoldBackgroundColor, // 做为Scaffold基础的Material默认颜色,典型Material应用或应用内页面的背景颜色。 Color bottomAppBarColor, // BottomAppBar 的默认颜色 Color cardColor, // Material被用做Card时的颜色 Color dividerColor, // Dividers 和 PopupMenuDividers的颜色,也用于ListTiles中间,和DataTables 的每行中间 Color focusColor, // 焦点获取时的颜色,例如,一些按钮焦点、输入框焦点。 Color hoverColor, // 点击以后徘徊中的颜色,例如,按钮长按,按住以后的颜色 Color highlightColor, // 用于相似墨水喷溅动画或指示菜单被选中的高亮颜色。 Color splashColor, // 墨水喷溅的颜色。 InteractiveInkFeatureFactory splashFactory, // 定义InkWall和InkResponse生成的墨水喷溅的外观。 Color selectedRowColor, // 选中行时的高亮颜色 Color unselectedWidgetColor, // 用于 Widget 处于非活动(但已启用)状态的颜色。 例如,未选中的复选框。 一般与 accentColor 造成对比。 Color disabledColor, // 用于 Widget 无效的颜色,不管任何状态。例如禁用复选框 Color buttonColor, // Material 中 RaisedButtons 使用的默认填充色 ButtonThemeData buttonTheme, // 定义了按钮等控件的默认配置 ToggleButtonsThemeData toggleButtonsTheme, // Flutter 1.9 全新组件 ToggleButtons 的主题 Color secondaryHeaderColor, // 有选定行时 PaginatedDataTable 标题的颜色 Color textSelectionColor, // 文本字段中选中文本的颜色,例如 TextField Color cursorColor, // 输入框光标颜色 Color textSelectionHandleColor, // 用于调整当前文本的哪一个部分的句柄颜色 Color backgroundColor, // 与 primaryColor 对比的颜色(例如 用做进度条的剩余部分) Color dialogBackgroundColor, // Dialog 元素的背景色 Color indicatorColor, // TabBar 中选项选中的指示器颜色。 Color hintColor, // 用于提示文本或占位符文本的颜色,例如在 TextField 中。 Color errorColor, // 用于输入验证错误的颜色,例如在 TextField 中 Color toggleableActiveColor, // 用于突出显示切换Widget(如Switch,Radio和Checkbox)的活动状态的颜色。 String fontFamily, // 字体样式 TextTheme textTheme, // 与卡片和画布对比的文本颜色 TextTheme primaryTextTheme, // 一个与主色对比的文本主题 TextTheme accentTextTheme, // 与突出颜色对照的文本主题 InputDecorationTheme inputDecorationTheme, // InputDecorator,TextField 和 TextFormField 的默认 InputDecoration 值基于此主题 IconThemeData iconTheme, // 与卡片和画布颜色造成对比的图标主题 IconThemeData primaryIconTheme, // 一个与主色对比的图片主题 IconThemeData accentIconTheme, // 与突出颜色对照的图片主题 SliderThemeData sliderTheme, // 用于渲染 Slider 的颜色和形状 TabBarTheme tabBarTheme, // TabBar 的主题样式 TooltipThemeData tooltipTheme, // tooltip 提示的主题样式 CardTheme cardTheme, // 卡片的主题样式 ChipThemeData chipTheme, // 用于渲染Chip的颜色和样式 TargetPlatform platform, // Widget 须要适配的目标类型 MaterialTapTargetSize materialTapTargetSize, // Chip 等组件的尺寸主题设置 bool applyElevationOverlayColor, // 是否应用 elevation 覆盖颜色 PageTransitionsTheme pageTransitionsTheme, // 页面转场主题样式 AppBarTheme appBarTheme, // AppBar 主题样式 BottomAppBarTheme bottomAppBarTheme, // 底部导航主题样式 ColorScheme colorScheme, // scheme组颜色,一组13种颜色,可用于配置大多数组件的颜色属性 DialogTheme dialogTheme, // 对话框主题样式 FloatingActionButtonThemeData floatingActionButtonTheme, // FloatingActionButton 的主题样式,也就是 Scaffold 属性的那个 Typography typography, // 用于配置 TextTheme、primaryTextTheme 和 accentTextTheme的颜色和几何文本主题值 CupertinoThemeData cupertinoOverrideTheme, // cupertino 覆盖的主题样式 SnackBarThemeData snackBarTheme, // 弹出的 snackBar 的主题样式 BottomSheetThemeData bottomSheetTheme, // 底部滑出对话框的主题样式 PopupMenuThemeData popupMenuTheme, // 弹出菜单对话框的主题样式 MaterialBannerThemeData bannerTheme, // Material 材质的 Banner 主题样式 DividerThemeData dividerTheme, // Divider 组件的主题样式,也就是那个横向线条组件 ButtonBarThemeData buttonBarTheme, }) 复制代码
更多完成信息,你们可参阅它的源码注释。
属性非常比较多的,一般咱们用到的 5 ~ 10 个左右,若是要高度定制可能会更多点。
primarySwatch 它是主题颜色的一个 样本色
, 经过这个样本色能够在一些条件下生成一些其它的属性,例如,若是没有指定 primaryColor,而且当前主题不是深色主题,那么 primaryColor 就会默认为primarySwatch 指定的颜色,还有一些类似的属性如 accentColor 、indicatorColor 等也会受primarySwatch 影响。
咱们能够经过 shared_preferences
保存用户设置,经过 Provider
实现状态管理。
添加依赖
provider: ^4.0.5
flustars: ^0.2.6+1 复制代码
// light_color.dart
import 'package:flutter/material.dart'; const MaterialColor lightColor = MaterialColor(_lightColorPrimaryValue, <int, Color>{ 50: Color(0xFFFDEAE7), 100: Color(0xFFFACBC3), 200: Color(0xFFF7A89C), 300: Color(0xFFF48574), 400: Color(0xFFF16B56), 500: Color(_lightColorPrimaryValue), 600: Color(0xFFED4A32), 700: Color(0xFFEB402B), 800: Color(0xFFE83724), 900: Color(0xFFE42717), }); const int _lightColorPrimaryValue = 0xFFEF5138; const MaterialColor lightColorAccent = MaterialColor(_lightColorAccentValue, <int, Color>{ 100: Color(0xFFFFFFFF), 200: Color(_lightColorAccentValue), 400: Color(0xFFFFB4AF), 700: Color(0xFFFF9C96), }); const int _lightColorAccentValue = 0xFFFFE4E2; 复制代码
定义好本身的主题色0xFFEF5138
, 而后经过工具生成。工具地址: mbitson/mcg
// theme_state.dart
class ThemeState with ChangeNotifier { /// 0:浅色模式 1:深色模式 2:跟随系统 int _darkMode; int get darkMode => _darkMode; static const Map<int, String> darkModeMap = {0: '浅色模式', 1: '深色模式', 2: '跟随系统'}; ThemeData get lightTheme => ThemeData(brightness: Brightness.light, primarySwatch: lightColor); ThemeData get darkTheme => ThemeData.dark(); ThemeState() { _init(); } void _init() async { await SpUtil.getInstance(); int localModel = SpUtil.getInt('kDarkMode', defValue: 2); changeMode(localModel); } void changeMode(int darkMode) async { _darkMode = darkMode; notifyListeners(); SpUtil.putInt("kDarkMode", darkMode); } } 复制代码
// theme_page.dart
class ThemePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( elevation: 0, title: Text('主题选择'), leading: GestureDetector( onTap: () { Navigator.of(context).pop(); }, child: Icon(Icons.arrow_back_ios), ), ), body: Consumer<ThemeState>( builder: (context, themeState, child) { Map items = ThemeState.darkModeMap; return ListView.builder( itemBuilder: (context, index) { return ListTile( onTap: () { themeState.changeMode(items.keys.toList()[index]); }, title: Text( items.values.toList()[index], style: TextStyle( color: index == themeState.darkMode ? Colors.red : Color(0xff333333)), ), ); }, itemCount: items.length, ); }, )); } } 复制代码
void main() { runApp(MyApp()); } class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider(create: (ctx) => ThemeState()) ], child: Consumer<ThemeState>( builder: (context, themeState, child) { if (themeState.darkMode == 2) { // 跟随系统 return MaterialApp( title: 'Oldbirds', theme: themeState.lightTheme, darkTheme: themeState.darkTheme, onGenerateRoute: generateRoute, initialRoute: SplashRoute, debugShowCheckedModeBanner: false, ); } else { return MaterialApp( title: 'Oldbirds', theme: themeState.darkMode == 1 // 深色模式 ? themeState.darkTheme : themeState.lightTheme, onGenerateRoute: generateRoute, initialRoute: SplashRoute, debugShowCheckedModeBanner: false, ); } }, )); } } 复制代码
上面的配置完成后,深色适配的功能完成 80% 左右,还有残余的,须要局部按需设置,有些固然还需按设计的色彩进行改动。
全局配置尽可能通用,须要规范专业级别的 ui 设计(由于通常会有设计规范)。
若是不得不改,那么就是 去同存异:
好比指定的文字样式与全局配置相同时,就删除它
若是文字颜色相同,可是字号不一样。那就删除颜色配置信息,保留字号设置
Text(
"仅保留不一样", style: Theme.of(context).textTheme.body1.copyWith(fontSize: 14.0) ) 复制代码
颜色不一样,由于深色模式主要就是颜色变化:
Text(
"仅保留不一样", style: Theme.of(context).textTheme.body1.copyWith(color: Colors.red, fontSize: 14.0) ) 复制代码
更多文章阅读,请搜索微信公众号: OldBirds