本文收錄於
張風捷特烈
的公衆號編程之王
: 文章內存地址f-s-a-04
,
如何獲取更多知識乾糧
,詳見<<編程之王食用規範1.0>>
編程
不少Flutter狀態管理文章都是改計數器,搞得總感受用了反而麻煩。搞太複雜的例子,一篇文章又不現實。就拿主題色切換+國際化開刀吧。本文會說一下
provoder
、BLoC
和redux
的三種實現主題色切換+國際化
的實現方式,因此稱三連擊。redux
provider: ^03.1.0+1
點擊顏色切換按鈕,進行全局主題色切換。bash
既然是狀態管理,首先來看狀態。顏色毋庸置疑,還有一個是顏色的選中索引,用來體現顏色按鈕的選中狀況。繼承自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; //獲取數字
}
複製代碼
狀態管理庫的套路基本一致,將須要管理的部分包裹起來,這裏直接上多個provider的包裹器。爲了好看點,這裏新建一個Wrapper組件來包裹。app
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, //孩子
);
}
}
複製代碼
Provider.of(context).themeData就能夠獲取ThemeData 不過爲了縮小構建的粒度,使用Consumer進行對點消費。less
---->[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了async
點擊側欄按鈕進行語言切換ide
dependencies: # 庫依賴
...
flutter_localizations: #國際化
sdk: flutter
複製代碼
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。",
};
}
複製代碼
運行後自動生成下面的文件:工具
---->[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'];}
}
複製代碼
就一個字段,很簡單,爲了方便使用,這裏定義兩個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 changeLocaleState(LocaleState state){
_locale=state.locale;
notifyListeners();
}
Locale get locale => _locale; //獲取語言
}
複製代碼
若是一個組件有多個狀態值能夠用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.changeLocaleState(LocaleState.zh())
state.changeLocaleState(LocaleState.zh())
複製代碼
這樣就演示了Provider在多狀態的狀況下如何工做。
flutter_redux: ^0.5.3
做爲一個但數據源的全局狀態管理庫,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,));
複製代碼
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的好處在於狀態資源統一管理。層層分封,結構清晰。
flutter_bloc: ^0.22.1
若是是redux是中央集權,地方分權,那麼BloC就是徹底的自由民主。一個BloC也有三大件:
Bloc 業務邏輯單元
、State狀態
、Events事件
狀態類
能夠根據本身的愛好寫出本身的風格。下面是我比較喜歡的風格。將狀態量放在抽象類中,其餘狀態去繼承他來實現狀態的分化。只要你想,也能夠加一些經常使用狀態。
@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();
}
}
}
複製代碼
狀態類
@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();
}
}
}
複製代碼
用起來都極爲類似,外層使用:
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
,期待與你的交流與切磋。另外歡迎關注公衆號編程之王