很多Flutter状态管理文章都是改计数器,搞得总感觉用了反而麻烦。搞太复杂的例子,一篇文章又不现实。就拿主题色切换+国际化开刀吧。本文会说一下provoder、BLoC和redux的三种实现主题色切换+国际化的实现方式,所以称三连击。
一.provoder实现主题切换和国际化:provider: ^03.1.0+11-主题色切换点击颜色切换按钮,进行全局主题色切换。
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 changeLocaleState(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.changeLocaleState(LocaleState.zh()) state.changeLocaleState(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{ 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状态管理也只不过刚刚入门。之后还会有很长的路要走...
---来自腾讯云社区的---张风捷特烈
微信扫一扫打赏
支付宝扫一扫打赏