這篇文章主要講解了Flutter如何實(shí)現(xiàn)App主題切換,內(nèi)容清晰明了,對(duì)此有興趣的小伙伴可以學(xué)習(xí)一下,相信大家閱讀完之后會(huì)有幫助。
創(chuàng)新互聯(lián)建站主營(yíng)平陰網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,app軟件開(kāi)發(fā)公司,平陰h5小程序開(kāi)發(fā)搭建,平陰網(wǎng)站營(yíng)銷推廣歡迎平陰等地區(qū)企業(yè)咨詢
概述
App主題切換已經(jīng)成為了一種流行的用戶體驗(yàn),豐富了應(yīng)用整體UI視覺(jué)效果。例如,白天夜間模式切換。實(shí)現(xiàn)該功能的思想其實(shí)不難,就是將涉及主題的資源文件進(jìn)行全局替換更新。說(shuō)到這里,我想你肯定能聯(lián)想到一種設(shè)計(jì)模式:觀察者模式。多種觀察對(duì)象(主題資源)來(lái)觀察當(dāng)前主題更新的行為(被觀察對(duì)象),進(jìn)行主題的更新。今天和大家分享在 Flutter 平臺(tái)上如何實(shí)現(xiàn)主題更換。
效果

實(shí)現(xiàn)流程
在 Flutter 項(xiàng)目中,MaterialApp組件為開(kāi)發(fā)者提供了設(shè)置主題的api:
const MaterialApp({
...
this.theme, // 主題
...
})通過(guò) theme 屬性,我們可以設(shè)置在MaterialApp下的主題樣式。theme 是 ThemeData 的對(duì)象實(shí)例:
ThemeData({
Brightness brightness,
MaterialColor primarySwatch,
Color primaryColor,
Brightness primaryColorBrightness,
Color primaryColorLight,
Color primaryColorDark,
...
})ThemeData 中包含了很多主題設(shè)置,我們可以選擇性的改變其中的顏色,字體等等。所以我們可以通過(guò)改變 primaryColor 來(lái)實(shí)現(xiàn)狀態(tài)欄的顏色改變。并通過(guò)Theme來(lái)獲取當(dāng)前 primaryColor 顏色值,將其賦值到其他組件上即可。在觸發(fā)主題更新行為時(shí),通知 ThemeData 的 primaryColor改變行對(duì)應(yīng)顏色值。 有了以上思路,接下來(lái)我們通過(guò)兩種方式來(lái)展示如何實(shí)現(xiàn)主題的全局更新。
主題選項(xiàng)
在實(shí)例中我們以一下主題顏色為主:
/** * 主題選項(xiàng) */ 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 方式實(shí)現(xiàn)
Flutter中EventBus提供了事件總線的功能,以監(jiān)聽(tīng)通知的方式進(jìn)行主體間通信。我們可以在main.dart入口文件下注冊(cè)主題修改的監(jiān)聽(tīng),通過(guò)EventBus發(fā)送通知來(lái)動(dòng)態(tài)修改 theme。核心代碼如下:
@override
void initState() {
super.initState();
Application.eventBus = new EventBus();
themeColor = ThemeList[widget.themeIndex];
this.registerThemeEvent();
}
/**
* 注冊(cè)主題切換監(jiān)聽(tīng)
*/
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(),
);
}然后在更新主題行為的地方來(lái)發(fā)送通知刷新即可:
changeTheme() async {
Application.eventBus.fire(new ThemeChangeEvent(1));
}scoped_model 狀態(tài)管理方式實(shí)現(xiàn)
了解 React、 React Naitve 開(kāi)發(fā)的朋友對(duì)狀態(tài)管理框架肯定都不陌生,例如 Redux 、Mobx、 Flux 等等。狀態(tài)框架的實(shí)現(xiàn)可以幫助我們非常輕松的控制項(xiàng)目中的狀態(tài)邏輯,使得代碼邏輯清晰易維護(hù)。Flutter 借鑒了 React 的狀態(tài)控制,同樣產(chǎn)生了一些狀態(tài)管理框架,例如 flutter_redux、scoped_model、bloc。接下來(lái)我們使用 scoped_model 的方式實(shí)現(xiàn)主題的切換。 關(guān)于 scoped_model 的使用方式可以參考pub倉(cāng)庫(kù)提供的文檔: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 中,定義了對(duì)應(yīng)的主題下標(biāo),changeTheme() 方法為更改主題,并調(diào)用 notifyListeners() 進(jìn)行全局通知。
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 的方式同樣比較簡(jiǎn)單,思路和 EventBus 類似。以上代碼我們實(shí)現(xiàn)了主題的切換,細(xì)心的朋友可以發(fā)現(xiàn),我們還需要對(duì)主題進(jìn)行保存,當(dāng)下次啟動(dòng) App 時(shí),要顯示上次切換的主題。Flutter中提供了 shared_preferences 來(lái)實(shí)現(xiàn)本地持久化存儲(chǔ)。
主題持久化保存
當(dāng)進(jìn)行主題更換時(shí),我們可以對(duì)主題進(jìn)行持久化本地存儲(chǔ)
void changeTheme(int themeIndex) async {
_themeIndex = themeIndex;
SharedPreferences sp = await SharedPreferences.getInstance();
sp.setInt("themeIndex", themeIndex);
}然后在項(xiàng)目啟動(dòng)時(shí),取出本地存儲(chǔ)的主題下標(biāo),設(shè)置在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(),
);
},
)
);
}以上我們通過(guò)兩種方式來(lái)實(shí)現(xiàn)了主題的切換,實(shí)現(xiàn)思想都是通過(guò)通知的方式來(lái)觸發(fā)組件 build 進(jìn)行刷新。那么兩種方式有什么區(qū)別呢?
區(qū)別
從 print log 中,可以發(fā)現(xiàn),當(dāng)使用 eventbus 事件總線進(jìn)行切換主題刷新時(shí),_AppState 下的 build方法 和 home指向的組件界面 整體都會(huì)重新構(gòu)建。而使用scoped_model等狀態(tài)管理工具,_AppState 下的 build方法不會(huì)重新執(zhí)行,只會(huì)刷新使用到了Model的組件,但是home對(duì)應(yīng)的組件依然會(huì)重新執(zhí)行build方法進(jìn)行構(gòu)建。所以我們可以得出以下結(jié)論:
兩者方式都會(huì)導(dǎo)致 home 組件被重復(fù) build。明顯區(qū)別在于使用狀態(tài)管理工具的方式可以避免父組件 build 重構(gòu)。
源碼已上傳到 Github,詳細(xì)代碼可以查看
EventBus 實(shí)現(xiàn)整體代碼:
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 實(shí)現(xiàn)整體代碼:
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);
}看完上述內(nèi)容,是不是對(duì)Flutter如何實(shí)現(xiàn)App主題切換有進(jìn)一步的了解,如果還想學(xué)習(xí)更多內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
分享標(biāo)題:Flutter如何實(shí)現(xiàn)App主題切換
轉(zhuǎn)載注明:http://www.chinadenli.net/article2/pessic.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站維護(hù)、面包屑導(dǎo)航、品牌網(wǎng)站建設(shè)、App開(kāi)發(fā)、企業(yè)網(wǎng)站制作、商城網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)