為您解碼網(wǎng)站建設(shè)的點(diǎn)點(diǎn)滴滴
發(fā)表日期:2018-05 文章編輯:小燈 瀏覽次數(shù):1972
從 2015 年接觸 Flutter 到現(xiàn)在也有兩年多時間,在這期間我并沒有正真地去了解這個神奇的框架,只是時不時拉取 master 的最新代碼,編一下 flutter_gallery 看看有什么新特性。但隨著此次 GDD 的召開,F(xiàn)lutter 被 Google 帶到了國內(nèi)開發(fā)者的眼前,相信谷歌是已經(jīng)準(zhǔn)備好讓 Flutter 走上移動開發(fā)歷史的舞臺了。
一款好的移動應(yīng)用該具備什么品質(zhì)?戳中用戶痛點(diǎn)的功能,炫酷的 UI 還是流暢的操作體驗(yàn)?這些都很重要,少了其中任何一點(diǎn)都是得不到用戶青睞的。但今天我要說的雖然不是前面這三個中的哪一個,但也是少了它就不行的“應(yīng)用國際化”。
對于開發(fā)者來說,在 Android 和 iOS 開發(fā)中使用國際化已經(jīng)是老掉牙的套路了,那么在 Flutter 中該如何使用國際化呢?是否也想 Android 一樣只要多配置一個 xml 就能搞定了呢?
Flutter 官方鼓勵我們在寫 Flutter 應(yīng)用的時候直接從 MaterialApp 開始,原因是 MaterialApp 為我們集成好了很多 Material Design 所必須的控件,如AnimatedThemen、GridPager 等,另外還通過 MaterialApp 配置了全局路由,方便進(jìn)行頁面的切換。既然如此我們就先從 MaterialApp 開始實(shí)現(xiàn)國際化。國際化涵蓋的不單單只是多國語言,還有文字閱讀方向、時間和日期格式等,但本文僅介紹多國語言的適配,它們幾種還希望讀者自行學(xué)習(xí)和研究。
通常我們新建的 Flutter 應(yīng)用是默認(rèn)不支持多語言的,即使用戶在中文環(huán)境下,顯示的文字仍然是英文,比如下圖所示的日期選擇對話框:
image那么怎么樣將系統(tǒng)的這些組件國際化呢?首先需要在 pubspec.yaml 中添加如下依賴:
flutter_localizations: sdk: flutter
接著運(yùn)行:
flutter packages get
以獲取依賴庫。
當(dāng)上面兩部完成后在 main.dart 中 import 如下:
import 'package:flutter_localizations/flutter_localizations.dart';
然后在 MaterialApp 的構(gòu)造方法中給 localizationsDelegates
和 supportedLocales
兩個可選參數(shù)賦值:
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyHomePage(title: 'Flutter Demo Home Page'), localizationsDelegates: [ //此處 GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: [ //此處 const Locale('zh','CH'), const Locale('en','US'), ], ); } }
暫時先不用理解這兩個參數(shù)是什么意思,此時如果重新運(yùn)行的話結(jié)果如下圖:
image細(xì)心的小伙伴可能發(fā)現(xiàn)這個 Dialog 中的文字是變成中文了,但背景中的 titlebar 的文字還是英文,難道老司機(jī)也翻車了?
其實(shí) titlebar 中的這串文字是屬于我們創(chuàng)建的應(yīng)用的,如下:
home: new MyHomePage(title: 'Flutter Demo Home Page')
Flutter 框架是不知道翻譯這句話。
接下來要做的就是我們自己實(shí)現(xiàn)一個類似 GlobalMaterialLocalizations
的東西,用它來實(shí)現(xiàn)多語言。
首先需要準(zhǔn)備在應(yīng)用中用到的字符串,一個剛新建的 Flutter 應(yīng)用用到了四個字符串,如下
這里為了簡單我們只增加中文,依次對應(yīng)為:
兩種文字準(zhǔn)備后就可以著手寫 Localizations 了,此處的 Localizations 是多國語言資源的匯總。在這里我自定義一個名為 DemoLocalizations 的類,然后將多國資源整合進(jìn)此類:
class DemoLocalizations {final Locale locale;DemoLocalizations(this.locale);static Map<String, Map<String, String>> _localizedValues = { 'en': { 'task title': 'Flutter Demo', 'titlebar title': 'Flutter Demo Home Page', 'click tip': 'You have pushed the button this many times:', 'inc':'Increment' }, 'zh': { 'task title': 'Flutter 示例', 'titlebar title': 'Flutter 示例主頁面', 'click tip': '你一共點(diǎn)擊了這么多次按鈕:', 'inc':'增加' } };get taskTitle{ return _localizedValues[locale.languageCode]['task title']; }get titleBarTitle{ return _localizedValues[locale.languageCode]['titlebar title']; }get clickTop{ return _localizedValues[locale.languageCode]['click tip']; }get inc{ return _localizedValues[locale.languageCode]['inc']; } }
此時只要能拿到 DemoLocalizations 的對象實(shí)例,就可以調(diào)用它的taskTitle
、titleBarTitle
、clickTop
這三個方法來獲取對應(yīng)的字符串。
定義完 DemoLocalizations 以后,我們就需要想這么一個問題,這個類是誰負(fù)責(zé)初始化呢?答案自然不是我們自己主動去初始化,而是需要一個叫做 LocalizationsDelegate
的類來完成,LocalizationsDelegate 是一個抽象類,需要我們?nèi)?shí)現(xiàn)它:
class DemoLocalizationsDelegate extends LocalizationsDelegate<DemoLocalizations>{const DemoLocalizationsDelegate();@override bool isSupported(Locale locale) { return ['en','zh'].contains(locale.languageCode); }@override Future<DemoLocalizations> load(Locale locale) { return new SynchronousFuture<DemoLocalizations>(new DemoLocalizations(locale)); }@override bool shouldReload(LocalizationsDelegate<DemoLocalizations> old) { return false; }static DemoLocalizationsDelegate delegate = const DemoLocalizationsDelegate(); }
注意 load
方法,DemoLocalizations就是在此方法內(nèi)被初始化的。
接著將 DemoLocalizationsDelegate 添加進(jìn) MaterialApp:
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyHomePage(title: 'Flutter Demo Home Page'), localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, DemoLocalizationsDelegate.delegate, //添加在此處 ], supportedLocales: [ const Locale('zh', 'CH'), const Locale('en', 'US'), ], ); } }
DemoLocalizationsDelegate 已經(jīng)被添加進(jìn) MaterialApp,那我們該如何使用 DemoLocalizations 呢?這里就要介紹另一個 Weidget 的子類 Localizations
,注意此處的 Localizations 它是一個貨真價實(shí) Widget。DemoLocalizationsDelegate 這個類的對象雖然被傳入了 MaterialApp,但由于 MaterialApp 會在內(nèi)部嵌套 Localizations 這個 Widget,而 LocalizationsDelegate 正是其構(gòu)造方法必須的參數(shù):
Localizations({ Key key, @required this.locale, @required this.delegates,//此處 this.child, }) : assert(locale != null),assert(delegates != null),assert(delegates.any((LocalizationsDelegate<dynamic> delegate)=> delegate is LocalizationsDelegate<WidgetsLocalizations>)),super(key: key);
而 DemoLocalizations 的實(shí)例也是在 Localizations 中通過 DemoLocalizationsDelegate 實(shí)例化的。所以在應(yīng)用中要使用 DemoLocalizations 的實(shí)例自然是需要通過 Localizations 這個 Widget 來獲取的,代碼如下:
Localizations.of(context, DemoLocalizations);
of
這個靜態(tài)方法就會返回 DemoLocalizations 的實(shí)例,現(xiàn)在先別管其內(nèi)部是如何實(shí)現(xiàn)的。我們將這行代碼放入 DemoLocalizations 中以方便使用:
class DemoLocalizations {final Locale locale;DemoLocalizations(this.locale);static Map<String, Map<String, String>> _localizedValues = { 'en': { 'task title': 'Flutter Demo', 'titlebar title': 'Flutter Demo Home Page', 'click tip': 'You have pushed the button this many times:', 'inc':'Increment' }, 'zh': { 'task title': 'Flutter 示例', 'titlebar title': 'Flutter 示例主頁面', 'click tip': '你一共點(diǎn)擊了這么多次按鈕:', 'inc':'增加' } };get taskTitle{ return _localizedValues[locale.languageCode]['task title']; }get titleBarTitle{ return _localizedValues[locale.languageCode]['titlebar title']; }get clickTop{ return _localizedValues[locale.languageCode]['click tip']; }get inc{ return _localizedValues[locale.languageCode]['inc']; }//此處 static DemoLocalizations of(BuildContext context){ return Localizations.of(context, DemoLocalizations); } }
接下來就是真正使用 DemoLocalizations 的時候了,在代碼中將原來的字符串替換如下:
import 'dart:async';import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter/foundation.dart' show SynchronousFuture;void main() { runApp(new MyApp()); }class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: DemoLocalizations.of(context).taskTitle, // 此處1 theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyHomePage(title: DemoLocalizations.of(context).titleBarTitle), // 此處2 localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, DemoLocalizationsDelegate.delegate, ], supportedLocales: [ const Locale('zh', 'CH'), const Locale('en', 'US'), ], ); } }class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key);final String title;@override _MyHomePageState createState() => new _MyHomePageState(); }class _MyHomePageState extends State<MyHomePage> { int _counter = 0;void _incrementCounter() { showDatePicker(context: context, initialDate: new DateTime.now(), firstDate: new DateTime.now().subtract(new Duration(days: 30)), lastDate: new DateTime.now().add(new Duration(days: 30))).then((v) {}); }@override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(widget.title), ), body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text( DemoLocalizations.of(context).clickTop,// 此處3 ), new Text( '$_counter', style: Theme .of(context) .textTheme .display1, ), ], ), ), floatingActionButton: new FloatingActionButton( onPressed: _incrementCounter, tooltip: DemoLocalizations.of(context).inc, // 此處4 child: new Icon(Icons.add), ), ); } }
運(yùn)行??!
image???
當(dāng)遇到這種突如其來的問題的時候一定要淡定,喝口水,眺望一會遠(yuǎn)方。。。
接著仔細(xì)看報錯信息:The getter 'taskTitle' was called on null.說的很明確,在 1 處出現(xiàn)了空指針,我們沒有像預(yù)想的一樣拿到 DemoLocalizations 對象。那問題一定出在 Localizations.of 方法內(nèi)部,跟進(jìn)去看看:
static T of<T>(BuildContext context, Type type) { assert(context != null); assert(type != null); final _LocalizationsScope scope = context.inheritFromWidgetOfExactType(_LocalizationsScope); // 此處 return scope?.localizationsState?.resourcesFor<T>(type); }
關(guān)鍵在 context.inheritFromWidgetOfExactType
處,繼續(xù)進(jìn)去:
InheritedWidget inheritFromWidgetOfExactType(Type targetType);
很簡單,這是一個抽象 BuildContext 的抽象方法。此時如果再要繼續(xù)追蹤實(shí)現(xiàn)類就比較困難了,通過這個方法的注釋可以知道,它是通過 targetType 來獲取 context 最近父節(jié)點(diǎn)的對象,前提條件是 targetType 對應(yīng)的類必須是 InheriteWidget 的子類。通過查看 _LocalizationsScope
發(fā)現(xiàn)其正是繼承自 InheriteWidget。那就是說沒有從 context 的父節(jié)點(diǎn)中找到 _LocalizationsScope。此時我們再看一下調(diào)用 taskTitle 的地方:
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: DemoLocalizations.of(context).taskTitle, // 此處 theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyHomePage(title: DemoLocalizations.of(context).titleBarTitle), localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, DemoLocalizationsDelegate.delegate, ], supportedLocales: [ const Locale('zh', 'CH'), const Locale('en', 'US'), ], ); } }
仔細(xì)看 taskTitle 處的 context 是從最外層的 build 方法中傳入的,而在之前說過 Localizations 這個組件是在 MaterialApp 中被嵌套的,也就是說能找到 DemoLocalizations 的 context 至少需要是 MaterialApp 內(nèi)部的,而此時的 context 是無法找到 DemoLocalizations 對象的。但這樣進(jìn)入死胡同了,實(shí)現(xiàn)多語言的 DemoLocalizations 需要在 MaterialApp 內(nèi)部才能被找到,而這里的 title 用到的 context 是在 MaterialApp 外部的。
難道多語言在 title 上沒法實(shí)現(xiàn)?
喝口水,眺望下遠(yuǎn)方。
既然如此我們不如看下這個 title 的說明:
/// A one-line description used by the device to identify the app for the user. /// /// On Android the titles appear above the task manager's app snapshots which are /// displayed when the user presses the "recent apps" button. Similarly, on /// iOS the titles appear in the App Switcher when the user double presses the /// home button. /// /// To provide a localized title instead, use [onGenerateTitle]. /// /// This value is passed unmodified to [WidgetsApp.title]. final String title;
請注意這句:To provide a localized title instead, use [onGenerateTitle].
沒想到啊,如果要對 title 進(jìn)行多語言處理還需要 onGenerateTitle
這個屬性。那就簡單了,更改如下:
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( onGenerateTitle: (context){// 此處 return DemoLocalizations.of(context).taskTitle; }, theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyHomePage(title: DemoLocalizations.of(context).titleBarTitle), localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, DemoLocalizationsDelegate.delegate, ], supportedLocales: [ const Locale('zh', 'CH'), const Locale('en', 'US'), ], ); } }
此時運(yùn)行會發(fā)現(xiàn) taskTitle 處已經(jīng)沒問題了,但 titleBarTitle 這邊還是報錯,原因一樣它的 context 使用的是 MaterialApp 外部的 context。但這里的 title 是可以被移動到 MyHomePage 內(nèi)部初始的,所以很好修改,將 MyHomePage 構(gòu)造方法中的 title 參數(shù)移除,直接在 AppBar 內(nèi)部賦值:
class MyHomePage extends StatefulWidget { MyHomePage({Key key}) : super(key: key);@override _MyHomePageState createState() => new _MyHomePageState(); }class _MyHomePageState extends State<MyHomePage> { int _counter = 0;void _incrementCounter() { showDatePicker(context: context, initialDate: new DateTime.now(), firstDate: new DateTime.now().subtract(new Duration(days: 30)), lastDate: new DateTime.now().add(new Duration(days: 30))).then((v) {}); }@override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(DemoLocalizations.of(context).titleBarTitle),// 此處 ), body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text( DemoLocalizations.of(context).clickTop, ), new Text( '$_counter', style: Theme .of(context) .textTheme .display1, ), ], ), ), floatingActionButton: new FloatingActionButton( onPressed: _incrementCounter, tooltip: DemoLocalizations.of(context).inc, child: new Icon(Icons.add), ), ); } }
再運(yùn)行:
image image完美。
上一節(jié)中簡單介紹了如何在 MaterialApp 實(shí)現(xiàn)國際化,各位可能也注意到了最終語言資源的選擇還是留給了 DemoLocalizations,而對語言資源本身是以什么形式存在沒有特別規(guī)定。在上文中我將兩國的語言放到了一個 Map 中,自然也可以將其放在服務(wù)器上,在程序啟動后進(jìn)行拉取,這些都是后話了,在這一節(jié)中我簡單剖析下源碼,看看 DemoLocalizatins 是如何在程序運(yùn)行后被初始化的。
上面已經(jīng)說過官方鼓勵我們使用 MaterialApp 作為程序入口,我們就從 MaterialApp 出發(fā),首先看 MaterialApp 的構(gòu)造方法:
MaterialApp({ // can't be const because the asserts use methods on Map :-( Key key, this.title: '', this.onGenerateTitle, this.color, this.theme, this.home, this.routes: const <String, WidgetBuilder>{}, this.initialRoute, this.onGenerateRoute, this.onUnknownRoute, this.locale, this.localizationsDelegates, this.localeResolutionCallback, this.supportedLocales: const <Locale>[const Locale('en', 'US')], this.navigatorObservers: const <NavigatorObserver>[], this.debugShowMaterialGrid: false, this.showPerformanceOverlay: false, this.checkerboardRasterCacheImages: false, this.checkerboardOffscreenLayers: false, this.showSemanticsDebugger: false, this.debugShowCheckedModeBanner: true })
上面的 localizationsDelegates
是多語言的關(guān)鍵點(diǎn),由于 MaterialApp 是一個 StatefulWidget,所以直接看其對應(yīng)的 State 類 _MaterialAppState
中的 build 方法,代碼有點(diǎn)長:
Widget build(BuildContext context) { final ThemeData theme = widget.theme ?? new ThemeData.fallback(); Widget result = new AnimatedTheme(// 1 data: theme, isMaterialAppTheme: true, child: new WidgetsApp(//2 key: new GlobalObjectKey(this), title: widget.title, onGenerateTitle: widget.onGenerateTitle, textStyle: _errorTextStyle, // blue is the primary color of the default theme color: widget.color ?? theme?.primaryColor ?? Colors.blue, navigatorObservers: new List<NavigatorObserver>.from(widget.navigatorObservers) ..add(_heroController), initialRoute: widget.initialRoute, onGenerateRoute: _onGenerateRoute, onUnknownRoute: _onUnknownRoute, locale: widget.locale, localizationsDelegates: _localizationsDelegates,//3 localeResolutionCallback: widget.localeResolutionCallback, supportedLocales: widget.supportedLocales, showPerformanceOverlay: widget.showPerformanceOverlay, checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers, showSemanticsDebugger: widget.showSemanticsDebugger, debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner, inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) { return new FloatingActionButton( child: const Icon(Icons.search), onPressed: onPressed, mini: true, ); }, ) );assert(() { if (widget.debugShowMaterialGrid) {//此處如果有配置,則會顯示網(wǎng)格 result = new GridPaper( color: const Color(0xE0F9BBE0), interval: 8.0, divisions: 2, subdivisions: 1, child: result, ); } return true; }());return new ScrollConfiguration(// 4 behavior: new _MaterialScrollBehavior(), child: result, ); }
首先在 3 處可以看到 _localizationsDelegates 被賦值給了 WidgetsApp 的 localizationsDelegates 參數(shù)。在看 1、2、4 處分別又在原有的 Widget 上做了包裹,此時的 widget 樹層次如下圖:
[圖片上傳失敗...(image-74770b-1525245587456)]
接著進(jìn)入 WidgetApp ,它也是個 StatefulWidget,直接看它的 State 類 _WidgetsAppState
的 build 方法:
Widget build(BuildContext context) { Widget result = new Navigator(// 1 key: _navigator, initialRoute: widget.initialRoute ?? ui.window.defaultRouteName, onGenerateRoute: widget.onGenerateRoute, onUnknownRoute: widget.onUnknownRoute, observers: widget.navigatorObservers, );if (widget.textStyle != null) { result = new DefaultTextStyle(//2 style: widget.textStyle, child: result, ); }... //此處省略調(diào)試相關(guān)代碼return new MediaQuery(//3 data: new MediaQueryData.fromWindow(ui.window), child: new Localizations( //4 locale: widget.locale ?? _locale, delegates: _localizationsDelegates.toList(), // This Builder exists to provide a context below the Localizations widget. // The onGenerateCallback() can refer to Localizations via its context // parameter. child: new Builder( //5 builder: (BuildContext context) { String title = widget.title; if (widget.onGenerateTitle != null) { title = widget.onGenerateTitle(context); assert(title != null, 'onGenerateTitle must return a non-null String'); } return new Title(//6 title: title, color: widget.color, child: result, ); }, ), ), ); }
在 4 處終于見到了我們熟悉的身影 Localizatins
。_localizationsDelegates 也是被傳遞進(jìn)了 Localizations。此時的 widget 樹層次如下:
[圖片上傳失敗...(image-901681-1525245587456)]
層次如此之多,但我們關(guān)心只是其中的 Localizations,所以拋開其他不看,進(jìn)入 Localizations 看看。
不出意外 Localizations 也是一個 StatefulWidget,此時我們不需要關(guān)心它的 build 方法,而是應(yīng)該關(guān)注其內(nèi)部的 initState
方法,如果有數(shù)據(jù)需要初始化,不出意外就是在這里進(jìn)行。
initState 方法很短:
@override void initState() { super.initState(); load(widget.locale); }
繼續(xù)進(jìn)入 load 方法:
void load(Locale locale) { final Iterable<LocalizationsDelegate<dynamic>> delegates = widget.delegates; // 1 if (delegates == null || delegates.isEmpty) { _locale = locale; return; }Map<Type, dynamic> typeToResources; final Future<Map<Type, dynamic>> typeToResourcesFuture = _loadAll(locale, delegates) //2 .then((Map<Type, dynamic> value) { return typeToResources = value; });...}
1 處的 delegates 即一開始從 MaterialApp 傳入的 delegate 數(shù)組,這里轉(zhuǎn)成立可迭代對象。接著看 2 處的 _loadAll
方法返回的 typeToResourcesFuture ,其中的值類型為 Map<Type, dynamic>
,這里可以推敲出來里邊的 Type 對應(yīng)的就是不同的 Localizations,而 dynamic 則是其實(shí)例。帶著這樣的想法看 _loadAll 方法:
Future<Map<Type, dynamic>> _loadAll(Locale locale, Iterable<LocalizationsDelegate<dynamic>> allDelegates) { final Map<Type, dynamic> output = <Type, dynamic>{}; List<_Pending> pendingList;// Only load the first delegate for each delegate type that supports // locale.languageCode. final Set<Type> types = new Set<Type>(); final List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[]; for (LocalizationsDelegate<dynamic> delegate in allDelegates) { if (!types.contains(delegate.type) && delegate.isSupported(locale)) { types.add(delegate.type); delegates.add(delegate); } }for (LocalizationsDelegate<dynamic> delegate in delegates) { final Future<dynamic> inputValue = delegate.load(locale);// 1 dynamic completedValue; final Future<dynamic> futureValue = inputValue.then<dynamic>((dynamic value) { return completedValue = value; // 2 }); if (completedValue != null) { // inputValue was a SynchronousFuture final Type type = delegate.type; assert(!output.containsKey(type)); output[type] = completedValue; } else { pendingList ??= <_Pending>[]; pendingList.add(new _Pending(delegate, futureValue)); } }// All of the delegate.load() values were synchronous futures, we're done. if (pendingList == null) return new SynchronousFuture<Map<Type, dynamic>>(output);// Some of delegate.load() values were asynchronous futures. Wait for them. return Future.wait<dynamic>(pendingList.map((_Pending p) => p.futureValue)) .then<Map<Type, dynamic>>((List<dynamic> values) { assert(values.length == pendingList.length); for (int i = 0; i < values.length; i += 1) { final Type type = pendingList[i].delegate.type; assert(!output.containsKey(type)); output[type] = values[i]; } return output; }); }
看 1 處,調(diào)用到了 deletegate 的 load 方法,返回一個 Future ,這里為什么不直接返回DemoLocalizations 的實(shí)例而要返回 Future,這個在前面也提到了如果你的資源是放在服務(wù)器上的,那么這就是一個耗時操作,所以在此處用了 Future。
@override Future<DemoLocalizations> load(Locale locale) { return new SynchronousFuture<DemoLocalizations>(new DemoLocalizations(locale)); }
由于這里返回的是 SynchronousFuture ,所以在 2 處的代碼會被順序執(zhí)行,此時 completedValue 就是 DemoLocalizations 的實(shí)例對象了。然后 completedValue 被放入了 output 接著就返回出去了,最后賦值給了 _LocalizationsState 的 _typeToResources 變量。
到目前為止整個多語言的加載就完成了,剩下的就是等著被使用。下面看一下使用的方式:
DemoLocalizations.of(context).taskTitle
簡單粗暴,根本看不出來是怎么拿到 DemoLocalizations 對象的。不多說,看代碼:
return Localizations.of(context, DemoLocalizations);
內(nèi)部調(diào)用的是 Localizations 的 of 靜態(tài)方法,接著看:
static T of<T>(BuildContext context, Type type) { assert(context != null); assert(type != null); final _LocalizationsScope scope = context.inheritFromWidgetOfExactType(_LocalizationsScope); return scope?.localizationsState?.resourcesFor<T>(type); }
前面已經(jīng)講解過 context.inheritFromWidgetOfExactType 的作用,這里的 scope 就是最靠近 context 節(jié)點(diǎn)的 _LocalizationsScope 類型的節(jié)點(diǎn)。但我們看了上面的 widget 樹的層次圖,并沒有看到 _LocalizationsScope 這個 widget,它是在什么時候被添加進(jìn)去的呢?
回到 _LocalizationsState 的 build 方法:
@override Widget build(BuildContext context) { if (_locale == null) return new Container(); return new _LocalizationsScope( key: _localizedResourcesScopeKey, locale: _locale, localizationsState: this, typeToResources: _typeToResources, child: new Directionality( textDirection: _textDirection, child: widget.child, ), ); }
真想(●—●)。在 Localizations 的內(nèi)部,它將它原本的子節(jié)點(diǎn)外又嵌套了 Directionality、_LocalizationsScope、Container 這三層。其中 _LocalizationsScope 就是我們想找的。
接著看:
return scope?.localizationsState?.resourcesFor<T>(type);
此處調(diào)用了 _LocalizationsState 的 resourcesFor 方法:
T resourcesFor<T>(Type type) { assert(type != null); final T resources = _typeToResources[type]; return resources; }
到這差不多就結(jié)束了,這里根據(jù) type 從 _typeToResources 中取出了 DemoLocalizations 的實(shí)例。
最后再把完整的 widget 樹的層次展示一下:
[圖片上傳失敗...(image-d220cd-1525245587456)]
下面我見到介紹一下如何在不切換手機(jī)系統(tǒng)的語言的情況下來切換 Flutter 應(yīng)用內(nèi)的語言。主要用到的是 Localizations 的 override 方法。具體不多介紹,看下面我自定義的 StatefulWidget 類 FreeLocalizations 和它的 State 類 _FreeLocalizations:
class FreeLocalizations extends StatefulWidget{final Widget child;FreeLocalizations({Key key,this.child}):super(key:key);@override State<FreeLocalizations> createState() { return new _FreeLocalizations(); } }class _FreeLocalizations extends State<FreeLocalizations>{Locale _locale = const Locale('zh','CH');changeLocale(Locale locale){ setState((){ _locale = locale; }); }@override Widget build(BuildContext context) { return new Localizations.override( context: context, locale: _locale, child: widget.child, ); } }
上面代碼的意思比較清晰,就是在調(diào)用 changeLocale 方法的時候修改其內(nèi)部 widget 的語言。
下面來如何使用:
void main() { runApp(new MyApp()); }GlobalKey<_FreeLocalizations> freeLocalizationStateKey = new GlobalKey<_FreeLocalizations>(); // 1 class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( onGenerateTitle: (context){ return DemoLocalizations.of(context).taskTitle; }, theme: new ThemeData( primarySwatch: Colors.blue, ), home: new Builder(builder: (context){ return new FreeLocalizations( key: freeLocalizationStateKey, child: new MyHomePage(), ); }), localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, DemoLocalizationsDelegate.delegate, ], supportedLocales: [ const Locale('zh', 'CH'), const Locale('en', 'US'), ], ); } }
注意想要在 FreeLocalizations 外部去調(diào)用其方法需要使用到 GlobalKey 的幫助,用法見 1 處。讓后我們將 MyHomePage 放入 FreeLocalizations 內(nèi)部。
接著在點(diǎn)擊按鈕的時候調(diào)用如下方法:
void changeLocale(){ if(flag){ freeLocalizationStateKey.currentState.changeLocale(const Locale('zh',"CH")); }else{ freeLocalizationStateKey.currentState.changeLocale(const Locale('en',"US")); } flag = !flag; }
效果如下:
image這一小節(jié)我講的比較簡單,但如果你看明白了二、三兩節(jié),那弄明白這里多語言是怎么切換的應(yīng)該是比較容易的。
思維導(dǎo)圖地址:https://my.mindnode.com/7u6RudyGs5bqzX1WrxY5XtZZqUDBzqvL2NioVbrr
文章中出現(xiàn)的代碼的地址:https://github.com/flutter-dev/internationalizing
日期:2018-10 瀏覽次數(shù):7357
日期:2018-12 瀏覽次數(shù):4425
日期:2018-07 瀏覽次數(shù):4960
日期:2018-12 瀏覽次數(shù):4260
日期:2018-09 瀏覽次數(shù):5597
日期:2018-12 瀏覽次數(shù):10014
日期:2018-11 瀏覽次數(shù):4898
日期:2018-07 瀏覽次數(shù):4666
日期:2018-05 瀏覽次數(shù):4952
日期:2018-12 瀏覽次數(shù):4401
日期:2018-10 瀏覽次數(shù):5226
日期:2018-12 瀏覽次數(shù):6300
日期:2018-11 瀏覽次數(shù):4555
日期:2018-08 瀏覽次數(shù):4681
日期:2018-11 瀏覽次數(shù):12746
日期:2018-09 瀏覽次數(shù):5665
日期:2018-12 瀏覽次數(shù):4933
日期:2018-10 瀏覽次數(shù):4267
日期:2018-11 瀏覽次數(shù):4616
日期:2018-12 瀏覽次數(shù):6151
日期:2018-06 瀏覽次數(shù):4096
日期:2018-08 瀏覽次數(shù):5540
日期:2018-10 瀏覽次數(shù):4538
日期:2018-12 瀏覽次數(shù):4624
日期:2018-07 瀏覽次數(shù):4451
日期:2018-12 瀏覽次數(shù):4601
日期:2018-06 瀏覽次數(shù):4483
日期:2018-11 瀏覽次數(shù):4459
日期:2018-12 瀏覽次數(shù):4340
日期:2018-12 瀏覽次數(shù):5361
Copyright ? 2013-2018 Tadeng NetWork Technology Co., LTD. All Rights Reserved.