激情六月丁香婷婷|亚洲色图AV二区|丝袜AV日韩AV|久草视频在线分类|伊人九九精品视频|国产精品一级电影|久草视频在线99|在线看的av网址|伊人99精品无码|午夜无码视频在线

高校合作1:010-59833514 ?咨詢電話:400-810-1418 服務(wù)與監(jiān)督電話:400-810-1418轉(zhuǎn)接2
當(dāng)前位置:首頁 >UI設(shè)計 >Flutter - 暗模式

Flutter - 暗模式

發(fā)布時間:2024-02-27 20:25:52 瀏覽量:171次

Flutter是基于主題構(gòu)建的,因此處理OS級暗模式非常簡單。但這不是那么簡單。您還必須:



  • 使自定義窗口小部件(或漸變或陰影之類的裝飾)和非平臺顫動窗口小部件(例如來自Google Maps的程序包)在所有主題中都可以接受。
  • 確保自定義窗口小部件(或裝飾)在用戶更改設(shè)備上的主題時正確更新。
  • 對于在較舊版本的操作系統(tǒng)上運行的用戶以及更喜歡在單個應(yīng)用程序上設(shè)置主題的用戶,請進(jìn)行適當(dāng)?shù)慕导墶?/li>

本文將討論如何處理這些問題。我將提供示例代碼。

本文中的信息來自許多來源,包括本文由馬特-卡羅爾,這也解釋了顫如何支持深色模式本身在Android上。我希望在iOS 13發(fā)布后不久,即可在iOS上獲得本機支持。它還包括Matthias Schuyten的這篇文章中的信息,其中詳細(xì)介紹了如何設(shè)置Flutter Google Maps Plugin地圖的樣式。

黑暗模式就在這里;您的應(yīng)用必須調(diào)整

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)行研究:哪種方法最適合我們的用戶?

應(yīng)該如何運作?



除非用戶特別關(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代碼。

(在Android上)暗模式已經(jīng)可以在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擇了什么亮度?

查詢系統(tǒng)亮度

如果用戶使用的是最新版本的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裝飾呢?他們怎么知道什么時候更新?

監(jiān)聽黑暗模式的變化

我們通常必須在應(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。

在嵌入式Google地圖中實現(xiàn)暗模式

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è)置主題?

獨立于系統(tǒng)更改主題



獨立設(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

熱門課程推薦

熱門資訊

請綁定手機號

x

同學(xué)您好!

您已成功報名0元試學(xué)活動,老師會在第一時間與您取得聯(lián)系,請保持電話暢通!
確定