本文收录于
张风捷特烈
的公众号编程之王
: 文章内存地址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(builder: (_,state,__)=>MaterialApp(//对点消费
title: 'Flutter Demo',
theme: state.themeData,//获取数据
home: MyHomePage(),
));
}
}
---->[pages/home_page.dart]----
children: [
Consumer(builder: (_,state,__) =>
Text( '----海的彼岸,有我未曾见证的风采',
style: TextStyle(color: state.themeData.primaryColor,
fontSize: 18,
fontWeight: FontWeight.bold),
...
复制代码
所以只要有需要颜色的地方,都可以使用这种方法从状态中拿主题色,颜色的切换事件触发也是非常简单。ColorChooser是我自定义的组件,在点击时会将索引和颜色值回调出来,在此触发changeThemeData方法来更新消费者的状态。
var colors = Consumer(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 {
I18nDelegate();
@override
bool isSupported(Locale locale) {
///设置支持的语言
return ['en', 'zh'].contains(locale.languageCode);
}
///加载当前语言下的字符串
@override
Future load(Locale locale) {
return SynchronousFuture( I18N(locale));
}
@override
bool shouldReload(LocalizationsDelegate 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> _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(
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(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((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(( 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(
appReducer,
initialState: AppState.initial(),//初始状态
),
child:child);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StoreBuilder(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(
builder: (_, store) =>Text(
I18N.of(context).subTitle,
style: TextStyle(
color: store.state.themeState.themeData.primaryColor,//通过仓库拿数据
fontSize: 18,
fontWeight: FontWeight.bold),
),),
---->[分发事件]----
var colors = StoreBuilder(
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 {
@override
ThemeState get initialState => InitialThemeState();//初始状态
@override
Stream 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 {
@override
LocaleState get initialState => InitialLocaleState();
@override
Stream 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(builder: (context) => ThemeBloc(),),
BlocProvider(builder: (context) => LocaleBloc(),),
],
child: MyApp()
);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder(builder: (_, theme) =>
BlocBuilder(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
(builder: (_, theme)
--->[获取状态量]----
BlocBuilder(
builder: (_, state) =>Text(
I18N.of(context).subTitle,
style: TextStyle(
color: state.themeData.primaryColor,
fontSize: 18,
fontWeight: FontWeight.bold),
),),
---->[分发事件]----
var colors = BlocBuilder(
builder: (_, state) =>ColorChooser(
colors: Cons.THEME_COLORS,
initialIndex:state.colorIndex,//同步索引状态
onChecked: (i,color) {
ThemeData themeData = ThemeData(primaryColor: color);//颜色
BlocProvider.of(context).add(EventSwitchTheme(i, themeData));//触发事件
},
));
复制代码
总的来说,大同小异。如果Stream流理解地较好,BloC用起来可以感觉是非常优雅的。个人还是比较喜欢redux。Provider作为官宣,也挺好用的。如果hold得住,混用也是可以的。本文理解了,你的Flutter状态管理也只不过刚刚入门。之后还会有很长的路要走...
结语
本文到此接近尾声了,如果想快速尝鲜Flutter,《Flutter七日》会是你的必备佳品;如果想细细探究它,那就跟随我的脚步,完成一次Flutter之旅。
另外本人有一个Flutter微信交流群,欢迎小伙伴加入,共同探讨Flutter的问题,本人微信号:zdl1994328
,期待与你的交流与切磋。另外欢迎关注公众号编程之王