本文收錄於
張風捷特烈
的公眾號程式設計之王
: 文章記憶體地址f-s-a-04
,
如何獲取更多知識乾糧
,詳見<<程式設計之王食用規範1.0>>
很多Flutter狀態管理文章都是改計數器,搞得總感覺用了反而麻煩。搞太複雜的例子,一篇文章又不現實。就拿主題色切換+國際化開刀吧。本文會說一下
provoder
、BLoC
和redux
的三種實現主題色切換+國際化
的實現方式,所以稱三連擊。
一.provoder實現主題切換和國際化:provider: ^03.1.0+1
1-主題色切換
點選顏色切換按鈕,進行全域性主題色切換。
1.1- 狀態類
既然是狀態管理,首先來看狀態。顏色毋庸置疑,還有一個是顏色的選中索引,用來體現顏色按鈕的選中情況。繼承自ChangeNotifier,將狀態量作為屬性,使用changeThemeData來方法改變狀態量,並通知需要小夥伴們,讓它們重新整理。
---->[provider/theme_state.dart]----
class ThemeState extends ChangeNotifier{
ThemeData _themeData;//主題
int _colorIndex;//主題
ThemeState(this._colorIndex,this._themeData,);
void changeThemeData(int colorIndex,ThemeData themeData){
_themeData = themeData;
_colorIndex = colorIndex;
notifyListeners();
}
ThemeData get themeData => _themeData; //獲取主題
int get colorIndex => _colorIndex; //獲取數字
}
複製程式碼
1.2- 頂上包裹
狀態管理庫的套路基本一致,將需要管理的部分包裹起來,這裡直接上多個provider的包裹器。為了好看點,這裡新建一個Wrapper元件來包裹。
void main() => runApp(Wrapper(child:MyApp()));
class Wrapper extends StatelessWidget {
final Widget child;
Wrapper({this.child});
@override
Widget build(BuildContext context) {
final initThemeData= ThemeData( //初始主題
primaryColor: Colors.blue,
);
final initIndex=4;//初始索引
return MultiProvider(
providers: [
ChangeNotifierProvider(builder: (_) => ThemeState(initIndex,initThemeData)), //在這提供provider
],
child: child, //孩子
);
}
}
複製程式碼
1.3- 使用狀態和呼叫方法
Provider.of(context).themeData就可以獲取ThemeData 不過為了縮小構建的粒度,使用Consumer進行對點消費。
---->[main.dart]----
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<ThemeState>(builder: (_,state,__)=>MaterialApp(//對點消費
title: 'Flutter Demo',
theme: state.themeData,//獲取資料
home: MyHomePage(),
));
}
}
---->[pages/home_page.dart]----
children: <Widget>[
Consumer<ThemeState>(builder: (_,state,__) =>
Text( '----海的彼岸,有我未曾見證的風采',
style: TextStyle(color: state.themeData.primaryColor,
fontSize: 18,
fontWeight: FontWeight.bold),
...
複製程式碼
所以只要有需要顏色的地方,都可以使用這種方法從狀態中拿主題色,顏色的切換事件觸發也是非常簡單。ColorChooser是我自定義的元件,在點選時會將索引和顏色值回撥出來,在此觸發changeThemeData方法來更新消費者的狀態。
var colors = Consumer<ThemeState>(builder: (_,state,__)=>ColorChooser(
colors: Cons.THEME_COLORS,
initialIndex: state.colorIndex,//同步索引狀態
onChecked: (i,color) {
ThemeData themeData = ThemeData(primaryColor: color);//顏色
state.changeThemeData(i,themeData);//觸發事件
},
));
複製程式碼
這樣主題切換色切換就OK了
2-語言切換切換
點選側欄按鈕進行語言切換
dependencies: # 庫依賴
...
flutter_localizations: #國際化
sdk: flutter
複製程式碼
2.1-首先準備資料
class Data{
static final EN={
"title":"ZF·G·Toly ",
"subTitle":"---- You are nothing at all",
"content":"public: The King Of Coder",
"sideTitle":"I Have a Dream",
"step1":"Unified the Earth",
"step2":"Unified the Solar System",
"step3":"Unified the Galaxy",
"step4":"Unified the Universe",
"step5":"Unified All Universe",
"step4SubTitle":"To be the king of Universe" ,
"step4Info":"A.D. 34679,toly unified the Universe,be the first omniscient。",
"btn2CN":"切換中文。",
"btn2EN":"To English。",
};
static final ZN={
"title":"張風捷特烈 ",
"subTitle":"----海的彼岸,有我未曾見證的風采",
"content":"公眾號:程式設計之王",
"sideTitle":"列一個小目標",
"step1":"統一地球",
"step2":"統一太陽系",
"step3":"統一銀河系",
"step4":"統一宇宙",
"step5":"統一平行宇宙",
"step4SubTitle":"成為宇宙之王",
"step4Info":"公元34679年,捷特統一已知宇宙,成為第一個全知。",
"btn2CN":"切換中文。",
"btn2EN":"To English。",
};
}
複製程式碼
2.2-然後我寫了個工具類一鍵生成相關程式碼
執行後自動生成下面的檔案:
---->[I18N代理相關]----
///Power By 張風捷特烈--- Generated file. Do not edit.
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'i18n.dart';
///多語言代理類
class I18nDelegate extends LocalizationsDelegate<I18N> {
I18nDelegate();
@override
bool isSupported(Locale locale) {
///設定支援的語言
return ['en', 'zh'].contains(locale.languageCode);
}
///載入當前語言下的字串
@override
Future<I18N> load(Locale locale) {
return SynchronousFuture<I18N>( I18N(locale));
}
@override
bool shouldReload(LocalizationsDelegate<I18N> old) {
return false;
}
///全域性靜態的代理
static I18nDelegate delegate = I18nDelegate();
}
---->[I18N使用類]----
/// Power By 張風捷特烈--- Generated file. Do not edit.
import 'package:flutter/material.dart';
import 'data.dart';
class I18N {
final Locale locale;
I18N(this.locale);
static Map<String, Map<String,String>> _localizedValues = {
'en': Data.EN,//英文
'zh': Data.ZN,//中文
};
static I18N of(BuildContext context) {
return Localizations.of(context, I18N);
}
get title {
return _localizedValues[locale.languageCode]['title'];}
get subTitle {
return _localizedValues[locale.languageCode]['subTitle'];}
get content {
return _localizedValues[locale.languageCode]['content'];}
get sideTitle {
return _localizedValues[locale.languageCode]['sideTitle'];}
get step1 {
return _localizedValues[locale.languageCode]['step1'];}
get step2 {
return _localizedValues[locale.languageCode]['step2'];}
get step3 {
return _localizedValues[locale.languageCode]['step3'];}
get step4 {
return _localizedValues[locale.languageCode]['step4'];}
get step5 {
return _localizedValues[locale.languageCode]['step5'];}
get step4SubTitle {
return _localizedValues[locale.languageCode]['step4SubTitle'];}
get step4Info {
return _localizedValues[locale.languageCode]['step4Info'];}
get btn2CN {
return _localizedValues[locale.languageCode]['btn2CN'];}
get btn2EN {
return _localizedValues[locale.languageCode]['btn2EN'];}
}
複製程式碼
2.3-狀態類
就一個欄位,很簡單,為了方便使用,這裡定義兩個factory來快速生成物件。
class LocaleState extends ChangeNotifier{
Locale _locale;//主題
LocaleState(this._locale);
factory LocaleState.zh()=>
LocaleState(Locale('zh', 'CH'));
factory LocaleState.en()=>
LocaleState(Locale('en', 'US'));
void changeThemeData(LocaleState state){
_locale=state.locale;
notifyListeners();
}
Locale get locale => _locale; //獲取語言
}
複製程式碼
2.4-使用
如果一個元件有多個狀態值可以用Consumer2,最多有6個。
另外這裡層級不深,也可以直接使用Provider.of(context)
來獲取狀態類
---->[main.dart 新增提供器]----
return MultiProvider(
providers: [
ChangeNotifierProvider(builder: (_) => ThemeState(initIndex,initThemeData)), //在這提供provider
ChangeNotifierProvider(builder: (_) => LocaleState.zh()), //在這提供provider
],
child: child, //孩子
);
---->[MaterialApp中進行國際化配置]----
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer2<ThemeState, LocaleState>(
builder: (_, themeState, localeState, __) =>
MaterialApp( //對點消費
title: 'Flutter Demo',
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
I18nDelegate.delegate, //新增
],
locale: localeState.locale,
supportedLocales: [
localeState.locale
],
theme: themeState.themeData, //獲取資料
home: MyHomePage(),
));
}
}
---->[國際化的使用]----
Consumer<ThemeState>(builder: (_,state,__) => Text(
I18N.of(context).subTitle,//獲取字串
style: TextStyle(
color: state.themeData.primaryColor,
fontSize: 18,
fontWeight: FontWeight.bold),
),),
---->[行為觸發]----
state.changeThemeData(LocaleState.zh())
state.changeThemeData(LocaleState.zh())
複製程式碼
這樣就演示了Provider在多狀態的情況下如何工作。
二、redux實現主題切換和國際化:flutter_redux: ^0.5.3
作為一個但資料來源的全域性狀態管理庫,redux採取標準的分封制。總狀態作為天子,再將任務細化分給各大諸侯,諸侯同樣也細化分給卿大夫。當每個人都管理好自己的責任,那麼就天下太平,生生不息。這裡只用兩個狀態來說,也就是主題色和國際化。
1-redux三大件
點選顏色切換按鈕,進行全域性主題色切換。思路是極為一致的,讓我們看看有哪些不同,首先要說的是rudux的三大件:
狀態State
,行為Action
和處理器Reducer
。所有狀態由倉庫統一管理,天子狀態AppState向下分封。
在定義redux狀態時,我習慣定義一個初始狀態,方便使用。當然你也可以不用,直接在使用時來構建。
---->[全域性redux]----
class AppState {
final ThemeState themeState;//左翼護衛主題管理大臣
final LocaleState localeState;//右翼護衛語言管理大臣
AppState({this.themeState, this.localeState});
factory AppState.initial()=> AppState(
themeState: ThemeState.initial(),
localeState: LocaleState.initial()
);
}
//總處理器--分封職責
AppState appReducer(AppState prev, dynamic action)=>
AppState(
themeState:themeDataReducer(prev.themeState, action),
localeState: localReducer(prev.localeState, action),);
複製程式碼
---->[主題redux]----
//切換主題狀態
class ThemeState extends ChangeNotifier {
ThemeData themeData; //主題
int colorIndex; //數字
ThemeState(this.colorIndex,
this.themeData,);
factory ThemeState.initial()=> ThemeState(4, ThemeData(primaryColor: Colors.blue,));
}
//切換主題行為
class ActionSwitchTheme {
final ThemeData themeData;
final int colorIndex;
ActionSwitchTheme(this.colorIndex, this.themeData);
}
//切換主題理器
var themeDataReducer = TypedReducer<ThemeState, ActionSwitchTheme>((state, action) =>
ThemeState(action.colorIndex, action.themeData,));
複製程式碼
---->[國際化redux]----
//切換語言狀態
class LocaleState extends ChangeNotifier{
Locale locale;//主題
LocaleState(this.locale);
factory LocaleState. initial()=> LocaleState(Locale('zh', 'CH'));
}
//切換語言行為
class ActionSwitchLocal {
final Locale locale;
ActionSwitchLocal(this.locale);
factory ActionSwitchLocal.zh()=> ActionSwitchLocal(Locale('zh', 'CH'));
factory ActionSwitchLocal.en()=> ActionSwitchLocal(Locale('en', 'US'));
}
//切換語言處理器
var localReducer = TypedReducer<LocaleState, ActionSwitchLocal>(( state, action) =>
LocaleState(action.locale,));
複製程式碼
2-redux的屬性使用
redux需要用StoreProvider進行包裹,其中在store屬性下進行倉庫的配置。
StoreBuilder就像Provider中的Consumer一樣的存在,只不過泛型都是統一的天子AppState。
void main() => runApp(Wrapper(child: MyApp()));
class Wrapper extends StatelessWidget {
final Widget child;
Wrapper({this.child});
@override
Widget build(BuildContext context) {
return StoreProvider(
store: Store<AppState>(
appReducer,
initialState: AppState.initial(),//初始狀態
),
child:child);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StoreBuilder<AppState>(builder: (context, store) => MaterialApp( //對點消費
title: 'Flutter Demo',
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
I18nDelegate.delegate, //新增
],
locale: store.state.localeState.locale,
supportedLocales: [
store.state.localeState.locale
],
theme: store.state.themeState.themeData, //獲取資料
home: MyHomePage(),
));
}
}
複製程式碼
在使用時無論是狀態,還是事件分發,統一由倉庫進行管理,結果是一致的:
---->[獲取狀態量]----
StoreBuilder<AppState>(
builder: (_, store) =>Text(
I18N.of(context).subTitle,
style: TextStyle(
color: store.state.themeState.themeData.primaryColor,//通過倉庫拿資料
fontSize: 18,
fontWeight: FontWeight.bold),
),),
---->[分發事件]----
var colors = StoreBuilder<AppState>(
builder: (_, store) =>ColorChooser(
colors: Cons.THEME_COLORS,
initialIndex: store.state.themeState.colorIndex,//同步索引狀態
onChecked: (i,color) {
ThemeData themeData = ThemeData(primaryColor: color);//顏色
store.dispatch(ActionSwitchTheme(i,themeData));//觸發事件
},
));
複製程式碼
redux的好處在於狀態資源統一管理。層層分封,結構清晰。
三、BLoC實現主題切換和國際化:flutter_bloc: ^0.22.1
如果是redux是中央集權,地方分權,那麼BloC就是完全的自由民主。一個BloC也有三大件:
Bloc 業務邏輯單元
、State狀態
、Events事件
1.主題色的BloC
狀態類
可以根據自己的愛好寫出自己的風格。下面是我比較喜歡的風格。將狀態量放在抽象類中,其他狀態去繼承他來實現狀態的分化。只要你想,也可以加一些常用狀態。
@immutable
abstract class ThemeState {
final ThemeData themeData; //主題
final int colorIndex;//數字
ThemeState( this.colorIndex,this.themeData);
}
class InitialThemeState extends ThemeState {
InitialThemeState() : super(4, ThemeData(primaryColor: Colors.blue,));
}
class ThemeStateImpl extends ThemeState {
ThemeStateImpl(int colorIndex, ThemeData themeData) : super(colorIndex, themeData);
}
複製程式碼
事件類
定義Bloc可執行的事件,比如這裡直接傳兩參切換和重置狀態
@immutable
abstract class ThemeEvent {}
class EventSwitchTheme extends ThemeEvent{
final ThemeData themeData; //主題
final int colorIndex;//數字
EventSwitchTheme( this.colorIndex,this.themeData);
}
class EventResetTheme extends ThemeEvent{}
複製程式碼
業務邏輯單元類
這是Bloc的核心,主要通過事件去生成狀態。
class ThemeBloc extends Bloc<ThemeEvent, ThemeState> {
@override
ThemeState get initialState => InitialThemeState();//初始狀態
@override
Stream<ThemeState> mapEventToState(ThemeEvent event,) async* {//使用非同步生成器
if(event is EventSwitchTheme){//如果是切換主題事件,生成對應的ThemeState
yield ThemeStateImpl(event.colorIndex,event.themeData);
}
if(event is EventResetTheme){//如果是重置主題事件,生成initialState
yield InitialThemeState();
}
}
}
複製程式碼
2.國際化的BloC
狀態類
@immutable
abstract class LocaleState {
final Locale locale;
LocaleState(this.locale);
}
class InitialLocaleState extends CnLocaleState {}
class CnLocaleState extends LocaleState {
CnLocaleState() : super(Locale('zh', 'CH'));
}
class EnLocaleState extends LocaleState {
EnLocaleState() : super(Locale('en', 'US'));
}
複製程式碼
事件類
@immutable
abstract class LocaleEvent {}
class EventSwitch2CN extends LocaleEvent{}
class EventSwitch2EN extends LocaleEvent{}
複製程式碼
業務邏輯單元類
class LocaleBloc extends Bloc<LocaleEvent, LocaleState> {
@override
LocaleState get initialState => InitialLocaleState();
@override
Stream<LocaleState> mapEventToState(LocaleEvent event,) async* {
if(event is EventSwitch2CN){//如果是切換到CN,生成CnLocaleState
yield CnLocaleState();
}
if(event is EventSwitch2EN){//如果是重置主題事件,生成EnLocaleState
yield EnLocaleState();
}
}
}
複製程式碼
3.Bloc的使用
用起來都極為相似,外層使用:
MultiBlocProvider
void main() => runApp(Wrapper(child: MyApp()));
class Wrapper extends StatelessWidget {
final Widget child;
Wrapper({this.child});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<ThemeBloc>(builder: (context) => ThemeBloc(),),
BlocProvider<LocaleBloc>(builder: (context) => LocaleBloc(),),
],
child: MyApp()
);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<ThemeBloc, ThemeState>(builder: (_, theme) =>
BlocBuilder<LocaleBloc, LocaleState>(builder: (_, local) =>
MaterialApp( //對點消費
title: 'Flutter Demo',
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
I18nDelegate.delegate, //新增
],
locale: local.locale,
supportedLocales: [
local.locale
],
theme: theme.themeData, //獲取資料
home: MyHomePage(),
)));
}
}
複製程式碼
狀態的獲取通過
BlocBuilder<XXXBloc, XXXState>(builder: (_, theme)
--->[獲取狀態量]----
BlocBuilder<ThemeBloc, ThemeState>(
builder: (_, state) =>Text(
I18N.of(context).subTitle,
style: TextStyle(
color: state.themeData.primaryColor,
fontSize: 18,
fontWeight: FontWeight.bold),
),),
---->[分發事件]----
var colors = BlocBuilder<ThemeBloc, ThemeState>(
builder: (_, state) =>ColorChooser(
colors: Cons.THEME_COLORS,
initialIndex:state.colorIndex,//同步索引狀態
onChecked: (i,color) {
ThemeData themeData = ThemeData(primaryColor: color);//顏色
BlocProvider.of<ThemeBloc>(context).add(EventSwitchTheme(i, themeData));//觸發事件
},
));
複製程式碼
總的來說,大同小異。如果Stream流理解地較好,BloC用起來可以感覺是非常優雅的。個人還是比較喜歡redux。Provider作為官宣,也挺好用的。如果hold得住,混用也是可以的。本文理解了,你的Flutter狀態管理也只不過剛剛入門。之後還會有很長的路要走...
結語
本文到此接近尾聲了,如果想快速嚐鮮Flutter,《Flutter七日》會是你的必備佳品;如果想細細探究它,那就跟隨我的腳步,完成一次Flutter之旅。
另外本人有一個Flutter微信交流群,歡迎小夥伴加入,共同探討Flutter的問題,本人微訊號:zdl1994328
,期待與你的交流與切磋。另外歡迎關注公眾號程式設計之王