發(fā)布時間:2024-02-27 20:25:52 瀏覽量:171次
Flutter是基于主題構(gòu)建的,因此處理OS級暗模式非常簡單。但這不是那么簡單。您還必須:
本文將討論如何處理這些問題。我將提供示例代碼。
本文中的信息來自許多來源,包括本文由馬特-卡羅爾,這也解釋了顫如何支持深色模式本身在Android上。我希望在iOS 13發(fā)布后不久,即可在iOS上獲得本機支持。它還包括Matthias Schuyten的這篇文章中的信息,其中詳細(xì)介紹了如何設(shè)置Flutter Google Maps Plugin地圖的樣式。
iOS和Android的最新版本均具有暗模式。在測試為家人編寫的Android / iOS NYC公交應(yīng)用程序時,我不得不處理黑暗模式。當(dāng)我第一次編寫它時,系統(tǒng)范圍內(nèi)的暗模式并不是什么問題,而該應(yīng)用程序是海洋或明亮的色彩。雖然該應(yīng)用程序在iPhone和Android上均可正常運行,但在暗模式下的手機上使用該應(yīng)用程序卻令人震驚,尤其是在夜間戶外。盡管Flutter進(jìn)行了自動更新以支持暗模式的應(yīng)用程序,但外部包裝和自定義組件并不容易。在這種情況下,撲朔迷離的Google Maps包不會(也不應(yīng))通過平臺級主題更改自動切換主題。應(yīng)用程序中的彩色小部件需要針對不同的模式進(jìn)行一些其他的重新設(shè)計。
除非您的應(yīng)用是單色的,否則處理淺色或深色主題會很棘手,尤其是在彩色背景下的文本。請記住,當(dāng)用戶選擇深色主題時,他們希望所有主要元素都變暗,而所有對比元素(例如文本和圖標(biāo))都變亮。添加顏色使其變得棘手。我懷疑這就是Google移除應(yīng)用程序所有品牌著色的原因(例如,中等深紅色,#D44638和深紅色輔助口音#B23121表示gmail),這是支持深色模式的第一步。將文本保持黑色以切換為淺色主題會產(chǎn)生難以閱讀的標(biāo)題:
讓我們從確定暗模式對我們的應(yīng)用程序的工作開始。我認(rèn)為最好從一個角度進(jìn)行研究:哪種方法最適合我們的用戶?
除非用戶特別關(guān)心,否則這不是您的用戶應(yīng)該考慮的事情;它應(yīng)該易于理解和以他們期望的方式工作;并且應(yīng)該隱藏任何復(fù)雜的邏輯。我們有一個適用于Google日歷等生產(chǎn)應(yīng)用程序的模型:主題首選項交叉的最簡單版本很清楚:LIGHT,DARK和SYSTEM,默認(rèn)為system。
我建議默認(rèn)為系統(tǒng)有以下三個原因。首先,大多數(shù)關(guān)心明暗模式的用戶已經(jīng)使用該系統(tǒng)選擇了首選模式。用戶的默認(rèn)首選項已定義。其次,對于有選擇權(quán)而又不在乎的用戶,系統(tǒng)的其他每個部分都使用系統(tǒng)值(通常為燈光模式)。第三,在不支持亮/暗主題的較舊版本的OS上運行時,它的作用與其他應(yīng)用一樣。
我們?nèi)绾螌崿F(xiàn)呢?讓我們看一些Flutter代碼。
但是,您必須做一些工作才能啟用它。如果在應(yīng)用程序中使用MaterialApp類,則允許您自定義明暗主題作為應(yīng)用程序定義的一部分。這些主題將鏈接到系統(tǒng)的明/暗模式設(shè)置。
@override Widget build(BuildContext context) { return MaterialApp( title: 'App title', theme: ThemeData(), darkTheme: ThemeData.dark(), home: MyHomePage(), ); }
simple_theme.dart
您可以自定義每個主題的顏色和字體:
Widget build(BuildContext context) { return MaterialApp( title: 'BusWatch', theme: ThemeData( brightness: Brightness.light, primarySwatch: Colors.orange, ), darkTheme: ThemeData( brightness: Brightness.dark, primarySwatch: Colors.orange ), home: MyHomePage(), ); }
modified_theme.dart
如您所見,您可以根據(jù)需要修改默認(rèn)主題,F(xiàn)lutter窗口小部件將進(jìn)行自我更新以匹配系統(tǒng)。但這對自定義和自定義窗口小部件沒有幫助。如果我們有一個帶有深紅色背景的小部件,并且在其頂部有一個文本小部件,該怎么辦?當(dāng)用戶選擇燈光模式時,默認(rèn)的黑色文本將變得不可讀。我們?nèi)绾沃烙脩暨x擇了什么亮度?
如果用戶使用的是最新版本的Android,iOS或Fuschia,則我們想知道設(shè)備在平臺級別上顯示的主題。最安全的方法是使用上下文的媒體查詢數(shù)據(jù)。如果返回Brightness.dark,則表示選擇了暗模式。
final Brightness brightnessValue = MediaQuery.of(context).platformBrightness; bool isDark = brightnessValue == Brightness.dark;
brightness_query.dart
如果要查詢該級別的值,也可以直接從系統(tǒng)窗口獲取平臺亮度。您可以使用類似于以下代碼
Window window = WidgetsBinding.instance.window;
(您永遠(yuǎn)不必真正執(zhí)行此操作。如果您發(fā)現(xiàn)自己直接使用Window,則可能是做錯了,還有一種更好的方法可以簡化模擬和無頭測試。在這種情況下,您可能應(yīng)該使用MediaQuery。)
您可以在小部件初始化上檢查并適當(dāng)設(shè)置小部件值。但是,當(dāng)用戶更改為亮或暗模式(或者它在預(yù)定的時間自動發(fā)生,例如日落)時該怎么辦?大多數(shù)小部件都會自動處理這些問題,但是您的自定義組件又如何,例如嵌入式地圖或?qū)⒉粫碌淖远x裝飾呢?他們怎么知道什么時候更新?
我們通常必須在應(yīng)用程序的主要部分中,在平臺級別上親自聽取主題更改。您可以在有狀態(tài)的小部件中使用WidgetsBinding,該小部件將抽象的WidgetsBindingObserver子類化(作為混合)。
WidgetsBinding使您可以鉤住通常的窗口小部件生命周期方法之外的更改,例如旋轉(zhuǎn)電話或用戶更改設(shè)備字體大?。ɡ?,將手機交給需要較大字體的親戚)。對于這種情況,它為平臺亮度的變化提供了一個契機。您將小部件添加為initState()中的偵聽器。當(dāng)通過覆蓋dispose()從窗口小部件樹中永久刪除窗口小部件的狀態(tài)對象時,這確實需要釋放引用。例如:
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver { @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangePlatformBrightness() { final Brightness brightness = WidgetsBinding.instance.window.platformBrightness; //inform listeners and rebuild widget tree }
theme_listener.dart
Flutter的小部件會自動運行,我們可以安全地設(shè)置自定義小部件的值。但是外部小部件呢?我們?nèi)绾味ㄖ扑鼈儯课覍⑹褂靡粋€常見的軟件包:Google Maps for Flutter。
Flutter最大的優(yōu)勢之一就是其開放的,受支持的軟件包系統(tǒng)。通常,每種需求都有編寫良好且維護(hù)良好的軟件包。最好的之一(我已經(jīng)在包括該項目在內(nèi)的多個項目中使用過)是google maps軟件包。默認(rèn)的地圖視圖非常明亮,不會自動更新為主題更改。幸運的是,地圖允許主題化??梢栽谶@里找到關(guān)于這方面的優(yōu)秀文章。
Google提供了一個主題化網(wǎng)絡(luò)工具,可在
https://mapstyle.withgoogle.com/上生成地圖主題。
我使用該工具生成默認(rèn)的明暗地圖主題。地圖使用json格式保存地圖樣式。您可以使用浮動資產(chǎn)加載實時加載地圖定義。
首先,使用網(wǎng)絡(luò)工具生成淺色和深色json樣式的文件。將它們保存在資產(chǎn)文件夾中。您可以使用rootBundle將文件作為字符串加載:
@override void initState() { super.initState(); rootBundle.loadString('assets/dark_map_style.json').then((string) { _darkMapStyle = string; }); rootBundle.loadString('assets/normal_map_style.json').then((string) { _normalMapStyle = string; });
load_styles.dart
加載地圖并設(shè)置地圖控制器后(通過onMapCreated設(shè)置),您可以在構(gòu)建地圖小部件時安全地使用樣式:
@override Widget build(BuildContext context) { bool isDark = MediaQuery.of(context).platformBrightness == Brightness.dark; if (mapController != null ) { if (isDark) { mapController.setMapStyle(_darkMapStyle); } else { mapController.setMapStyle(_normalMapStyle); } } //...
map_styles.dart
現(xiàn)在,地圖會動態(tài)更新其樣式以匹配系統(tǒng)。
因此,我們已負(fù)責(zé)更新應(yīng)用程序以匹配設(shè)備的主題。但是,想要獨立于系統(tǒng)設(shè)置應(yīng)用程序主題的用戶或使用舊版本操作系統(tǒng)的用戶呢?我們?nèi)绾为毩⒃O(shè)置主題?
獨立設(shè)置主題實際上是兩個選擇(暗/亮),是我上面提到的三個選擇的子集。在這種情況下,簡單的UI效果很好。
但是我們要保存用戶在會話之間的選擇。有幾種方法可以從屬性文件,sqlite或遠(yuǎn)程執(zhí)行此操作。在設(shè)計時就考慮到了這種用例。我建議使用“ 共享首選項”插件。(我不會遠(yuǎn)程存儲亮/暗模式首選項;它是每個設(shè)備的首選項。)
要使用共享首選項,請導(dǎo)入“首選項”插件并創(chuàng)建一種安全獲取引用的方法,您可以在應(yīng)用啟動時實例化該引用(可能在主窗口小部件狀態(tài)的initState()中)。為主題值使用一個枚舉。
其次,如果您想讓系統(tǒng)主題更新的監(jiān)聽器,請定義一種方法來接收這些更新。在這種情況下,我使用了typedef PrefsListener。
在這種情況下,我關(guān)心的首選項是應(yīng)用程序的主題:
static const String THEME_PREF = “AppTheme”;
然后,為其他類創(chuàng)建一個鉤子,以偵聽主題更新并注銷(addListener()和removeListener())。
示例首選項實現(xiàn)如下:
import 'package:shared_preferences/shared_preferences.dart'; enum Themes { DARK, LIGHT, SYSTEM } class Prefs { static const Map<Themes, String> themes = { Themes.DARK: "Dark", Themes.LIGHT : "Light", Themes.SYSTEM : "System" }; Map<String, List<PrefsListener>> _listeners; factory Prefs.singleton() { return _instance; } static final Prefs _instance = Prefs._internal(); SharedPreferences _prefs; bool _initialized = false; static const String THEME_PREF = "AppTheme"; Prefs._internal() { _listeners = Map<String, List<PrefsListener>>(); _getPrefs().then((prefs) { _initialized = true; for (String key in _listeners.keys) { List<PrefsListener> listeners = _listeners[key]; if(listeners != null && listeners.isNotEmpty) { Object value = prefs.get(key); for (PrefsListener listener in listeners) { listener(key, value); } } } }); } void addListenerForPref(String key, PrefsListener listener) { List<PrefsListener> list = _listeners[key]; if (list == null) { list = List<PrefsListener>(); _listeners[key] = list; } list.add(listener); } Future<SharedPreferences> _getPrefs() async { if (_prefs == null) { _prefs = await SharedPreferences.getInstance(); } return _prefs; } String getTheme() { if (_initialized) { String theme = _prefs.getString(THEME_PREF); if (theme != null) { return theme; } else { _prefs.setString(THEME_PREF, themes[Themes.SYSTEM]); //ok not to wait return themes[Themes.SYSTEM]; } } else { return themes[Themes.SYSTEM]; } } //called when the user updates the operating system theme //(by choosing light or dark mode) void systemThemeUpdated(Brightness brightness) { if (isSystemTheme()) { String theme = getTheme(); List<PrefsListener> listenerList = _listeners[THEME_PREF]; if (listenerList != null) { for (PrefsListener listener in listenerList) { listener(THEME_PREF, theme); } } } } ///set the app's theme preference from the ///app's own UI void setTheme(String theme) { _getPrefs().then((prefs) { prefs.setString(THEME_PREF, theme); }); List<PrefsListener> listenerList = _listeners[THEME_PREF]; if (listenerList != null) { for (PrefsListener listener in listenerList) { listener(THEME_PREF, theme); } } } } typedef PrefsListener(String key, Object value);
prefs.dart
翻譯自:https://medium.com/@
pmutisya/dark-mode-in-flutter-3742062f9f59
熱門資訊
1. iPhone6的UI設(shè)計尺寸規(guī)范,原來如此重要!
想要了解iPhone6界面設(shè)計的尺寸規(guī)范嗎?這里為您詳細(xì)介紹iPhone6的UI設(shè)計尺寸規(guī)范,包括界面尺寸、圖標(biāo)尺寸、可點擊高度規(guī)范、搜索欄高度規(guī)范以及界面元素之間的距離規(guī)范。
2. 12個絕佳的UI設(shè)計網(wǎng)站,助力你的創(chuàng)作之旅!
將為大家介紹12個絕佳的UI設(shè)計網(wǎng)站,這些網(wǎng)站不僅可以為你提供靈感,還可以幫助你學(xué)習(xí)新的技巧,助力你的創(chuàng)意之旅!dribbbleDribbble 是一個面向設(shè)計師的...
3. 移動端UI設(shè)計中常見的5種APP界面類型,你get到了嗎?
通過介紹移動端UI設(shè)計中的閃屏頁、引導(dǎo)頁、浮層引導(dǎo)頁、空白頁和首頁等5種APP界面類型,幫助大家更好地了解UI設(shè)計的基本知識
4. 10個免費學(xué)習(xí)UI設(shè)計的網(wǎng)站 提升你的設(shè)計能力
怎樣可以提升你的UI設(shè)計能力!第一個:站酷站酷想必是設(shè)計師都知道的一個網(wǎng)站,里面不止有UI設(shè)計的資源,還有其他設(shè)計的,不如:平面設(shè)計、網(wǎng)頁設(shè)計、字體...
5. 移動端列表頁和表單頁設(shè)計秘訣:讓你的APP點擊率翻倍
在移動端設(shè)計中,列表頁和表單頁是不可或缺的部分。一個好的列表頁和表單頁設(shè)計能夠讓用戶輕松地獲取信息并產(chǎn)生點擊欲望,從而提高點擊率。本文將為你...
6. 推薦10本適合UI設(shè)計師看的書籍,輕松掌握技能!
對于想要提高自己的設(shè)計能力和創(chuàng)造力的小白和UI設(shè)計師來說,這本書是一個很好的選擇。4.《設(shè)計的覺醒》(IKKO TANAKA)推薦理由: 這本書是日本現(xiàn)代平面...
7. 零基礎(chǔ)學(xué)UI設(shè)計要多久?培訓(xùn)完能拿多少工資?
探索零基礎(chǔ)UI設(shè)計培訓(xùn)的時長與薪資前景。了解數(shù)字藝術(shù)教育領(lǐng)域的專業(yè)課程,以及培訓(xùn)后的職業(yè)發(fā)展機會。
8. 物聯(lián)網(wǎng)APP UI設(shè)計:創(chuàng)造智能硬件領(lǐng)域的沉浸式體驗
ui設(shè)計應(yīng)該讓用戶一目了然,能夠快速找到所需的信息和功能。在設(shè)計過程中,應(yīng)盡量使用簡潔的圖標(biāo)、文字和色彩,避免過多的視覺干擾。符合用戶習(xí)慣:ui設(shè)...
9. 武漢UI設(shè)計培訓(xùn)班費用怎么樣?想學(xué)UI設(shè)計要多少錢?
想了解武漢UI設(shè)計培訓(xùn)班的費用是多少嗎?不知道學(xué)UI設(shè)計要花多少錢?不妨看看這篇文章,了解UI設(shè)計培訓(xùn)班的學(xué)費價格以及學(xué)習(xí)內(nèi)容。
10. 設(shè)計中的色彩心理學(xué):淺析中西方色彩的歷史演變與設(shè)計應(yīng)用
摘要:本文探討了色彩的歷史演變和設(shè)計應(yīng)用。通過對色彩在早期文明社會中的實用運用、不同文化背景下色彩觀念的差異、色彩在設(shè)計中的重要性以及新興技...
最新文章
同學(xué)您好!