前言
在開始之前的提示:雖然Flutter背靠Google這棵大樹,但是畢竟還是一個年輕的技術,專案還處於初期階段,更新非常快,問題也不是一般的多,使用Flutter的話就意味著必須忍受各種奇怪的bug和沒有豐富中文資料的頭疼,本文不是安利同學們入坑,只是對“極簡詩詞”app的開發過程進行記錄。
另外app已經上架,有興趣的同學可以下載試試:www.coolapk.com/apk/251155
主要介面截圖:
主頁 | 暗黑版主頁 | 詩集 | 詩集瀏覽 |
---|---|---|---|
詩集詳情 | 作者列表 | 作者詳情 | 字型選擇 |
和Django快速開發實踐的文章一樣,本文不講廢話,直接上步驟。
專案檔案結構
先設計好專案檔案結構,不同的專案有不同的需求,按照自己的實際需要來設計結構就好了,以下是我的專案結構,僅供參考:
lib
├── common
├── i10n
├── models
├── routes
├── states
└── widgets
複製程式碼
資料夾 | 作用 |
---|---|
common | 一些工具類,如通用方法類、網路介面類、儲存全域性變數的靜態類等 |
i10n | 存放國際化相關程式碼 |
models | 通過json to models生成的model類檔案都存在這裡 |
routes | 存放專案的所有頁面程式碼 |
states | 儲存app中需要跨元件共享的狀態類 |
widgets | 存放自定義widget |
定義好models
在本專案中,我使用json to models
來自動生成models類,為什麼使用這個呢?原因很簡單,減少工作量,用json定義好app中使用到的模型,生成model類之後可以很方便序列化成json資料進行持久化和或者從配置檔案中讀取json資料反序列化成model物件,還可以直接根據後臺介面返回的json資料生成model類,非常方便。
使用json定義model,例子如下:
在專案根目錄下建立json資料夾,新增要進行轉換的json檔案,內容大概像這樣。 poem.json
{
"strains": [
"平平平仄仄,平仄仄平平。",
"仄仄平平仄,平平仄仄平。",
"平平平仄仄,平仄仄平平。",
"平仄仄平仄,平平仄仄平。"
],
"author": "作者名稱",
"authorObj": "$author",
"paragraphs": [
"秦川雄帝宅,函谷壯皇居。",
"綺殿千尋起,離宮百雉餘。",
"連甍遙接漢,飛觀迥凌虛。",
"雲日隱層闕,風煙出綺疎。"
],
"tags": [
"戰爭",
"生活",
"冬天",
"愛國",
"邊塞"
],
"chapter": "國風",
"section": "周南",
"rhythmic": "玉樓春",
"title": "帝京篇十首 一",
"content": "經傳宜獨坐讀,史鑑宜與友共讀。",
"comment": [
"孫愷似曰:深得此中真趣,固難為不知者道。",
"王景州曰:如無好友,即紅友亦可。"
],
"notes": [
"1.小山--寫女子的隔夜殘妝。小山:女子畫眉的式樣之一。小山重疊:眉暈褪色。金:額黃,在額上塗黃色。金明滅:褪色的額黃明暗不勻。",
"2.鬢雲欲度--鬢髮撩亂如雲,低垂下來。香腮雪:潔白如雪的香腮。",
"3.照花--對鏡簪花。用前鏡、後鏡對照以瞻顧後影。",
"4.雙雙--羅襦上用金線繡的成雙的鷓鴣鳥。反襯自身孤獨。"
],
"anthology": "所屬詩集名稱",
"id": "08e41396-2809-423d-9bbc-1e6fb24c0ca1"
}
複製程式碼
新增依賴:
dev_dependencies:
json_model: ^0.0.2
build_runner: ^1.0.0
json_serializable: ^2.0.0
複製程式碼
好了之後執行:
flutter packages pub run json_model
複製程式碼
這樣就會自動在lib/models
資料夾下面生成models類啦。
有個坑爹的地方是這個json_model
庫只能支援很老版本的build_runner
和json_serializable
,這和我後面要用到的intl
就衝突了啊,每次用這兩個庫的時候我都要不斷註釋切換依賴的版本,真的麻煩 = =....
狀態管理
狀態管理是app中最重要的一部分,也是後面主題切換和國際化的基礎。 本文是快速開發實踐,不過多深入Flutter的狀態管理,想了解的同學可以看看大佬寫的Flutter教程:book.flutterchina.club/chapter7/pr…
我是使用Provider
這個元件來管理app的狀態的,它基於InheritedWidget
實現,用起來挺方便。
先新增依賴:
dependencies:
provider: ^3.2.0
複製程式碼
在lib/states
資料夾下新增共享狀態的models,例如:
import 'package:flutter/material.dart';
import 'package:minimal_poem/common/global.dart';
import 'package:minimal_poem/models/index.dart';
import 'notifier.dart';
class ProfileChangeNotifier extends ChangeNotifier {
Profile get profile => Global.profile;
@override
void notifyListeners() {
// 儲存Profile變更
Global.saveProfile();
Global.saveAllUsers();
super.notifyListeners(); // 通知依賴的Widget更新
}
}
class ThemeModel extends ProfileChangeNotifier {
// 獲取當前主題,如果未設定主題,則預設使用藍色主題
ColorSwatch get theme => Global.themes.firstWhere((e) => e.value == profile.theme, orElse: () => Colors.blue);
// 主題改變後,通知其依賴項,新主題會立即生效
set theme(ColorSwatch color) {
if (color != theme) {
profile.theme = color[500].value;
notifyListeners();
}
}
bool get darkMode => Global.profile.darkMode;
set darkMode(bool value) {
Global.profile.darkMode = value;
notifyListeners();
}
}
複製程式碼
這些model繼承自ProfileChangeNotifier
,可以提供資料或者管理資料的修改和儲存。
在普通的元件裡可以直接使用獲取或儲存資料,配合provider
元件使用可以在model資料改變的時候出發元件的更新動作~
例如我的MyApp
類定義,用到了MultiProvider
和Consumer2
:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: <SingleChildCloneableWidget>[
ChangeNotifierProvider.value(value: ThemeModel()),
ChangeNotifierProvider.value(value: UserModel()),
ChangeNotifierProvider.value(value: LocaleModel()),
],
child: Consumer2<ThemeModel, LocaleModel>(
builder: (BuildContext context, themeModel, localeModel, Widget child) {
return MaterialApp(
theme: ThemeData(
brightness: Global.profile.darkMode ? Brightness.dark : Brightness.light,
primarySwatch: themeModel.theme,
),
onGenerateTitle: (context) {
return DaLocalizations.of(context).title;
},
home: HomeRoute(),
//應用主頁
locale: localeModel.getLocale(),
//我們只支援美國英語和中文簡體
supportedLocales: [
const Locale('zh', 'CN'), // 中文簡體
const Locale('en', 'US'), // 美國英語
//其它Locales
],
localizationsDelegates: [
// 本地化的代理類
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
// EasyRefresh的多語言支援
GlobalEasyRefreshLocalizations.delegate,
// 註冊我們的Delegate
DaLocalizationsDelegate()
],
localeResolutionCallback: (Locale _locale, Iterable<Locale> supportedLocales) {
if (localeModel.getLocale() != null) {
//如果已經選定語言,則不跟隨系統
return localeModel.getLocale();
} else {
Locale locale;
// APP語言跟隨系統語言,如果系統語言不是中文簡體或美國英語,
// 則預設使用美國英語
if (supportedLocales.contains(_locale)) {
locale = _locale;
} else {
locale = Locale('en', 'US');
}
return locale;
}
},
);
},
),
);
}
}
複製程式碼
國際化支援
國際化就是多語言啦,用到了intl
包。
在專案根目錄下建立資料夾i10n-arb
,在lib/i10n
裡建立localization_intl.dart
:
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'messages_all.dart';
class DaLocalizations {
String get userNameOrPasswordWrong => null;
static Future<DaLocalizations> load(Locale locale) {
final String name = locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
final String localeName = Intl.canonicalizedLocale(name);
//2
return initializeMessages(localeName).then((b) {
Intl.defaultLocale = localeName;
return new DaLocalizations();
});
}
static DaLocalizations of(BuildContext context) {
return Localizations.of<DaLocalizations>(context, DaLocalizations);
}
String get auto => Intl.message('auto', name: 'auto', desc: 'set theme mode auto');
}
//Locale代理類
class DaLocalizationsDelegate extends LocalizationsDelegate<DaLocalizations> {
const DaLocalizationsDelegate();
//是否支援某個Local
@override
bool isSupported(Locale locale) => ['en', 'zh'].contains(locale.languageCode);
// Flutter會呼叫此類載入相應的Locale資源類
@override
Future<DaLocalizations> load(Locale locale) {
//3
return DaLocalizations.load(locale);
}
// 當Localizations Widget重新build時,是否呼叫load重新載入Locale資源.
@override
bool shouldReload(DaLocalizationsDelegate old) => false;
}
複製程式碼
執行命令生成arb檔案:
flutter pub pub run intl_translation:extract_to_arb --output-dir=i10n-arb lib/i10n/localization_intl.dart
複製程式碼
之後會在i10n-arb
資料夾下生成intl_messages.arb
檔案,這個本質上是一個json檔案,我們還要為不同的語言版本建立對應的翻譯,比如本app支援中文和英文,那麼需要建立兩個檔案:intl_zh.arb
和intl_en.arb
。
把intl_messages.arb
檔案的內容分別複製到對應語言的翻譯檔案中,修改成對應語言的版本即可。
{
"@@last_modified": "2019-12-17T17:04:43.001945",
"title": "title",
"@title": {
"type": "text",
"placeholders": {}
},
}
複製程式碼
上面這些做完之後執行命令生成對應的類:
# 從arb檔案生成dart程式碼
flutter pub pub run intl_translation:generate_from_arb --output-dir=lib/i10n --no-use-deferred-loading lib/i10n/localization_intl.dart i10n-arb/intl_*.arb
複製程式碼
未完待續
原來有這麼多內容,限制於篇幅,我將在接下來的文章中繼續記錄~
歡迎交流
交流問題請在微信公眾號後臺留言,每一條資訊我都會回覆哈~
- 微信公眾號:畫星星高手
- 打程式碼直播間:live.bilibili.com/11883038
- 知乎:www.zhihu.com/people/deal…
- 簡書:www.jianshu.com/u/965b95853…