您好,登錄后才能下訂單哦!
這篇文章主要講解了Flutter如何實現App主題切換,內容清晰明了,對此有興趣的小伙伴可以學習一下,相信大家閱讀完之后會有幫助。
概述
App主題切換已經成為了一種流行的用戶體驗,豐富了應用整體UI視覺效果。例如,白天夜間模式切換。實現該功能的思想其實不難,就是將涉及主題的資源文件進行全局替換更新。說到這里,我想你肯定能聯想到一種設計模式:觀察者模式。多種觀察對象(主題資源)來觀察當前主題更新的行為(被觀察對象),進行主題的更新。今天和大家分享在 Flutter 平臺上如何實現主題更換。
效果
實現流程
在 Flutter 項目中,MaterialApp組件為開發者提供了設置主題的api:
const MaterialApp({ ... this.theme, // 主題 ... })
通過 theme 屬性,我們可以設置在MaterialApp下的主題樣式。theme 是 ThemeData 的對象實例:
ThemeData({ Brightness brightness, MaterialColor primarySwatch, Color primaryColor, Brightness primaryColorBrightness, Color primaryColorLight, Color primaryColorDark, ... })
ThemeData 中包含了很多主題設置,我們可以選擇性的改變其中的顏色,字體等等。所以我們可以通過改變 primaryColor 來實現狀態欄的顏色改變。并通過Theme來獲取當前 primaryColor 顏色值,將其賦值到其他組件上即可。在觸發主題更新行為時,通知 ThemeData 的 primaryColor改變行對應顏色值。 有了以上思路,接下來我們通過兩種方式來展示如何實現主題的全局更新。
主題選項
在實例中我們以一下主題顏色為主:
/** * 主題選項 */ import 'package:flutter/material.dart'; final List<Color> themeList = [ Colors.black, Colors.red, Colors.teal, Colors.pink, Colors.amber, Colors.orange, Colors.green, Colors.blue, Colors.lightBlue, Colors.purple, Colors.deepPurple, Colors.indigo, Colors.cyan, Colors.brown, Colors.grey, Colors.blueGrey ];
EventBus 方式實現
Flutter中EventBus提供了事件總線的功能,以監聽通知的方式進行主體間通信。我們可以在main.dart入口文件下注冊主題修改的監聽,通過EventBus發送通知來動態修改 theme。核心代碼如下:
@override void initState() { super.initState(); Application.eventBus = new EventBus(); themeColor = ThemeList[widget.themeIndex]; this.registerThemeEvent(); } /** * 注冊主題切換監聽 */ void registerThemeEvent() { Application.eventBus.on<ThemeChangeEvent>().listen((ThemeChangeEvent onData)=> this.changeTheme(onData)); } /** * 刷新主題樣式 */ void changeTheme(ThemeChangeEvent onData) { setState(() { themeColor = themeList[onData.themeIndex]; }); } @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( primaryColor: themeColor ), home: HomePage(), ); }
然后在更新主題行為的地方來發送通知刷新即可:
changeTheme() async { Application.eventBus.fire(new ThemeChangeEvent(1)); }
scoped_model 狀態管理方式實現
了解 React、 React Naitve 開發的朋友對狀態管理框架肯定都不陌生,例如 Redux 、Mobx、 Flux 等等。狀態框架的實現可以幫助我們非常輕松的控制項目中的狀態邏輯,使得代碼邏輯清晰易維護。Flutter 借鑒了 React 的狀態控制,同樣產生了一些狀態管理框架,例如 flutter_redux、scoped_model、bloc。接下來我們使用 scoped_model 的方式實現主題的切換。 關于 scoped_model 的使用方式可以參考pub倉庫提供的文檔:https://pub.dartlang.org/packages/scoped_model
1. 首先定義主題 Model
/** * 主題Model * Create by Songlcy */ import 'package:scoped_model/scoped_model.dart'; abstract class ThemeStateModel extends Model { int _themeIndex; get themeIndex => _themeIndex; void changeTheme(int themeIndex) async { _themeIndex = themeIndex; notifyListeners(); } }
在 ThemeStateModel 中,定義了對應的主題下標,changeTheme() 方法為更改主題,并調用 notifyListeners() 進行全局通知。
2. 注入Model
@override Widget build(BuildContext context) { return ScopedModel<MainStateModel>( model: MainStateModel(), child: ScopedModelDescendant<MainStateModel>( builder: (context, child, model) { return MaterialApp( theme: ThemeData( primaryColor: themeList[model.themeIndex] ), home: HomePage(), ); }, ) ); }
3. 修改主題
changeTheme(int index) async { int themeIndex = index; MainStateModel().of(context).changeTheme(themeIndex); }
可以看到,使用 scoped_model
的方式同樣比較簡單,思路和 EventBus 類似。以上代碼我們實現了主題的切換,細心的朋友可以發現,我們還需要對主題進行保存,當下次啟動 App 時,要顯示上次切換的主題。Flutter中提供了 shared_preferences
來實現本地持久化存儲。
主題持久化保存
當進行主題更換時,我們可以對主題進行持久化本地存儲
void changeTheme(int themeIndex) async { _themeIndex = themeIndex; SharedPreferences sp = await SharedPreferences.getInstance(); sp.setInt("themeIndex", themeIndex); }
然后在項目啟動時,取出本地存儲的主題下標,設置在theme上即可
void main() async { int themeIndex = await getTheme(); runApp(App(themeIndex)); } Future<int> getTheme() async { SharedPreferences sp = await SharedPreferences.getInstance(); int themeIndex = sp.getInt("themeIndex"); if(themeIndex != null) { return themeIndex; } return 0; } @override Widget build(BuildContext context) { return ScopedModel<MainStateModel>( model: mainStateModel, child: ScopedModelDescendant<MainStateModel>( builder: (context, child, model) { return MaterialApp( theme: ThemeData( primaryColor: themeList[model.themeIndex != null ? model.themeIndex : widget.themeIndex] ), home: HomePage(), ); }, ) ); }
以上我們通過兩種方式來實現了主題的切換,實現思想都是通過通知的方式來觸發組件 build 進行刷新。那么兩種方式有什么區別呢?
區別
從 print log 中,可以發現,當使用 eventbus 事件總線進行切換主題刷新時,_AppState 下的 build方法 和 home指向的組件界面 整體都會重新構建。而使用scoped_model等狀態管理工具,_AppState 下的 build方法不會重新執行,只會刷新使用到了Model的組件,但是home對應的組件依然會重新執行build方法進行構建。所以我們可以得出以下結論:
兩者方式都會導致 home 組件被重復 build。明顯區別在于使用狀態管理工具的方式可以避免父組件 build 重構。
源碼已上傳到 Github,詳細代碼可以查看
EventBus 實現整體代碼:
import 'package:flutter/material.dart'; import 'package:event_bus/event_bus.dart'; import './config/application.dart'; import './pages/home_page.dart'; import './events/theme_event.dart'; import './constants/theme.dart'; import 'package:shared_preferences/shared_preferences.dart'; void main() async { int themeIndex = await getDefaultTheme(); runApp(App(themeIndex)); } Future<int> getDefaultTheme() async { // 從shared_preferences中獲取上次切換的主題 SharedPreferences sp = await SharedPreferences.getInstance(); int themeIndex = sp.getInt("themeIndex"); print(themeIndex); if(themeIndex != null) { return themeIndex; } return 0; } class App extends StatefulWidget { int themeIndex; App(this.themeIndex); @override State<StatefulWidget> createState() => AppState(); } class AppState extends State<App> { Color themeColor; @override void initState() { super.initState(); Application.eventBus = new EventBus(); themeColor = ThemeList[widget.themeIndex]; this.registerThemeEvent(); } void registerThemeEvent() { Application.eventBus.on<ThemeChangeEvent>().listen((ThemeChangeEvent onData)=> this.changeTheme(onData)); } void changeTheme(ThemeChangeEvent onData) { setState(() { themeColor = ThemeList[onData.themeIndex]; }); } @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( primaryColor: themeColor ), home: HomePage(), ); } @override void dispose() { super.dispose(); Application.eventBus.destroy(); } } changeTheme() async { SharedPreferences sp = await SharedPreferences.getInstance(); sp.setInt("themeIndex", 1); Application.eventBus.fire(new ThemeChangeEvent(1)); }
scoped_model 實現整體代碼:
import 'package:flutter/material.dart'; import 'package:event_bus/event_bus.dart'; import 'package:scoped_model/scoped_model.dart'; import 'package:shared_preferences/shared_preferences.dart'; import './config/application.dart'; import './pages/home_page.dart'; import './constants/theme.dart'; import './models/state_model/main_model.dart'; void main() async { int themeIndex = await getTheme(); runApp(App(themeIndex)); } Future<int> getTheme() async { SharedPreferences sp = await SharedPreferences.getInstance(); int themeIndex = sp.getInt("themeIndex"); if(themeIndex != null) { return themeIndex; } return 0; } class App extends StatefulWidget { final int themeIndex; App(this.themeIndex); @override _AppState createState() => _AppState(); } class _AppState extends State<App> { @override void initState() { super.initState(); Application.eventBus = new EventBus(); } @override Widget build(BuildContext context) { return ScopedModel<MainStateModel>( model: MainStateModel(), child: ScopedModelDescendant<MainStateModel>( builder: (context, child, model) { return MaterialApp( theme: ThemeData( primaryColor: ThemeList[model.themeIndex != null ? model.themeIndex : widget.themeIndex] ), home: HomePage(), ); }, ) ); } } changeTheme() async { int themeIndex = MainStateModel().of(context).themeIndex == 0 ? 1 : 0; SharedPreferences sp = await SharedPreferences.getInstance(); sp.setInt("themeIndex", themeIndex); MainStateModel().of(context).changeTheme(themeIndex); }
看完上述內容,是不是對Flutter如何實現App主題切換有進一步的了解,如果還想學習更多內容,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。