在這裏先安利一下fish-redux這個框架。我一直在用這個框架做路由跟開發框架。
fish-redux是阿里鹹魚開源的一個基於 Redux 數據管理的組裝式 flutter 應用框架。
它的特點是配置式組裝。 一方面我們將一個大的頁面,對視圖和數據層層拆解爲互相獨立的 Component|Adapter,上層負責組裝,下層負責實現; 另一方面將 Component|Adapter 拆分爲 View,Reducer,Effect 等相互獨立的上下文無關函數。感興趣的小夥伴可以去了解一下(PS:這個標題怕是沒用過的不會點進來吧)
言歸正傳,今天主要是記錄一下多語言跟多主題切換的實現問題。
首先實現這個功能需要用到一下插件:
#國際化
flutter_localizations:
sdk: flutter
intl: ^0.16.0
intl_translation: ^0.17.2
fish_redux: ^0.3.1
#sp 數據存儲
shared_preferences: ^0.5.2
#數據同步,多線程
synchronized: ^2.2.0
1.多語言功能實現
多語言插件intl的具體使用方法參考另一篇博客:
Flutter 使用intl實現國際化
同樣fish-redux使用這裏也不做介紹了。
2.多主題功能實現
1.創建global_theme_styles.dart文件來定義APP中用到的所有的顏色:
class GlobalThemeStyles{
///默認語言
static Locale themeLocale =Locale('zh', 'CN');
///主題色
static const List<Color> themeColors = <Color>[
_VIOLET,
ORANGE,
];
//基本文字顏色
static const List<Color> baseTitleColor=<Color>[
READ,
BLUE
];
///背景顏色
static const List<Color> backGroundColor=<Color>[
BAC_GRAY,
YELLOW
];
static const Color BAC_GRAY = const Color(0xFFEBEBEB);
static const Color _VIOLET = const Color(0xFF68129A);
static const Color READ = const Color(0xFFDE0A0A);
static const Color ORANGE = const Color(0xFFF9820E);
static const Color YELLOW = const Color(0xFFFFEB8C);
static const Color BLUE = const Color(0xFF256CEC);
}
由於是多主題,所以要根據主題數量的多少配置對應的主題的顏色,在這裏我用list存放每一個顏色,然後根據list的index使用的主題來切換。
2.保存選擇的主題以及語言:
///SharedPreferences 本地存儲
class LocalStorage {
static save(String key, value) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString(key, value);
}
static get(String key) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.get(key);
}
static remove(String key) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.remove(key);
}
///檢查主題是否持久化
static Future checnLocalThemeResources() async {
WidgetsFlutterBinding.ensureInitialized();
await SpUtil.getInstance();
String _localThemeLocale =
SpUtil.getString(Config.LOCAL_THEME_LOCALE_KEY);
int _localThemeColor = SpUtil.getInt(Config.LOCAL_THEME_COLOR_KEY);
if (!BaseTools.isEmpty(_localThemeLocale)) {
if(_localThemeLocale=="en"){
GlobalThemeStyles.themeLocale = Locale('en', 'US');
}else{
GlobalThemeStyles.themeLocale = Locale('zh', 'CN');
}
GlobalStore.store.dispatch(GlobalActionCreator.changeLanguage(_localThemeLocale));
}
try {
if (_localThemeColor != null) {
GlobalStore.store.dispatch(GlobalActionCreator.changeThemeColor(_localThemeColor));
}
} catch (e) {}
}
///持久化主題資源
static void saveLocalThemeResources(var resources) async {
await SpUtil.getInstance();
if (resources != null) {
if (resources is String) {
SpUtil.putString(Config.LOCAL_THEME_LOCALE_KEY, resources);
} else if (resources is int) {
SpUtil.putInt(Config.LOCAL_THEME_COLOR_KEY, resources);
}
}
}
}
其中的Config代碼:
class Config {
///包名
static const APP_PACKAGE_NAME = "com.fish.local";
///國際化本地key
static const LOCAL_THEME_LOCALE_KEY=APP_PACKAGE_NAME+"local_theme_locale_key";
///主題本地key
static const LOCAL_THEME_COLOR_KEY=APP_PACKAGE_NAME+"local_theme_color_key";
}
3.在fish-redux的global_store中定義切換主題以及語言的全局action以及實現:
在action.dart中:
enum GlobalAction { changeThemeColor, changelanguage ,changeAtNight}
class GlobalActionCreator {
///切換主題
static Action changeThemeColor(int i) {
return Action(GlobalAction.changeThemeColor,payload: i);
}
///切換語言
static Action changeLanguage(String language) {
return Action(GlobalAction.changelanguage, payload: language);
}
}
在global_store/reducer.dart中:
Reducer<GlobalState> buildReducer() {
return asReducer(
<Object, Reducer<GlobalState>>{
GlobalAction.changeThemeColor: _changeThemeColor,
GlobalAction.changelanguage: _changeLanguage,
GlobalAction.changeAtNight: _changeAtNight,
},
);
}
GlobalState _changeThemeColor(GlobalState state, prefix0.Action action) {
int _themeIndex = action.payload;
GlobalState _globalState = state.clone();
_globalState.theme=_themeIndex;
//保存目前的主題
LocalStorage.saveLocalThemeResources(_themeIndex);
return _globalState;
}
GlobalState _changeLanguage(GlobalState state, prefix0.Action action) {
GlobalState _globalState = state.clone();
String _lanauage = action.payload;
//如果還有其他語言,做相應的判斷
if(_lanauage=="en"){
_globalState.languageLocale = Locale('en', 'US');
}else{
_globalState.languageLocale = Locale('zh', 'CN');
}
AppLocalizationsDelegate.delegate.load(_globalState.languageLocale );
//保存目前的語言版本
LocalStorage.saveLocalThemeResources(_lanauage);
return _globalState;
}
在global_store/state.dart中:
abstract class GlobalBaseState<T extends Cloneable<T>> implements Cloneable<T> {
int get theme;
set theme(int theme);
Locale get languageLocale;
set languageLocale(Locale languageLocale);
}
class GlobalState implements GlobalBaseState<GlobalState> {
@override
int theme=0;
@override
Locale languageLocale;
@override
GlobalState clone() {
// TODO: implement clone
return GlobalState()
..theme = theme
..languageLocale = languageLocale;
}
}
在自定義的全局路由中添加如下:
///定義一個全局的route
class AppRoute {
static AbstractRoutes _global;
static AbstractRoutes get global {
if (_global == null) {
_global = PageRoutes(
pages: <String, Page<Object, dynamic>>{
},
visitor: (String path, Page<Object, dynamic> page) {
/// 只有特定的範圍的Page才需要建立和AppStore的連接關係
/// 滿足Page<T> T 是GlobalBaseState的之類
if (page.isTypeof<GlobalBaseState>()) {
/// 建立AppStore驅動PageStore的單項數據連接
/// 1. 參數1 AppStore
/// 2. 參數2 當 AppStore.state 變化時, PageStore.state 該如何變化
page.connectExtraStore<GlobalState>(
GlobalStore.store,
(Object pagestate, GlobalState appState) {
final GlobalBaseState p = pagestate;
if (p.theme != appState.theme) {
if (pagestate is Cloneable) {
final Object copy = pagestate.clone();
final GlobalBaseState newState = copy;
newState.theme = appState.theme;
return newState;
}
}
if (p.languageLocale != appState.languageLocale) {
if (pagestate is Cloneable) {
final Object copy = pagestate.clone();
final GlobalBaseState newState = copy;
newState.languageLocale = appState.languageLocale;
return newState;
}
}
return pagestate;
},
);
}
},
);
}
return _global;
}
}
核心代碼是visitor中,這裏的作用是通知所有頁面刷新主題跟語言。
在main.dart中:
void main() =>LocalStorage.checnLocalThemeResources().then((e) =>runApp(MyApp()));
class MyApp extends StatefulWidget {
@override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> with WidgetsBindingObserver{
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
print("--" + state.toString());
switch (state) {
case AppLifecycleState.inactive: // 處於這種狀態的應用程序應該假設它們可能在任何時候暫停。
break;
case AppLifecycleState.resumed:// 應用程序可見,前臺
break;
case AppLifecycleState.paused: // 應用程序不可見,後臺
break;
case AppLifecycleState.detached:
// TODO: Handle this case.
break;
}
}
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'FLEXlend',
debugShowCheckedModeBanner: false,
theme: ThemeData(
platform: TargetPlatform.iOS,
scaffoldBackgroundColor: Colors.white,
appBarTheme: AppBarTheme(
iconTheme: IconThemeData(color: GlobalThemeStyles.WHITE),
textTheme: TextTheme(
title: TextStyle(
fontSize:18,
color: GlobalThemeStyles.WHITE))),
),
localizationsDelegates: [
AppLocalizationsDelegate(), // 我們定義的代理
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
locale: GlobalThemeStyles.themeLocale,
supportedLocales: <Locale>[
const Locale('en', 'US'), // 美國英語
const Locale('zh', 'CN'), // 中文簡體
],
home: AppRoute.global.buildPage(RoutePath.TEST_LIST, null),
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<Object>(builder: (BuildContext context) {
return AppRoute.global.buildPage(settings.name, settings.arguments);
});
},
);
}
}
在main中需要注意的是,啓動函數的改變:
void main() =>LocalStorage.checnLocalThemeResources().then((e) =>runApp(MyApp()));
其中LocalStorage.checnLocalThemeResources() 是爲了在啓動時確定目前APP的語言以及主題。
最後,所有需要使用多主題以及多語言的頁面是state.dart都需要實現GlobalBaseState:
class ThemeDemoState implements GlobalBaseState<ThemeDemoState> {
@override
bool isAtNight;
@override
Locale languageLocale;
@override
int theme;
@override
ThemeDemoState clone() {
return ThemeDemoState()..languageLocale=languageLocale..theme=theme;
}
}
ThemeDemoState initState(Map<String, dynamic> args) {
return ThemeDemoState();
}
在修改主題時調用:
GlobalStore.store.dispatch(GlobalActionCreator.changeThemeColor(0));
修改語言時調用:
GlobalStore.store.dispatch(GlobalActionCreator.changeLanguage("zh"));