「深色模式(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