距離flutter_deer開源快3個月了,目前為止收穫了1600+的Star,感謝大家的對此專案的認可支援。不過雖然表面看上去光鮮亮麗,但我知道還是有很多不規範不合理的用法及寫法,為了不對初學者造成誤導作用,所以這期間我幾乎每天都在完善優化它(現在應該還不錯吧)。
今天繼續分享一些在Flutter開發中需要注意的點,希望對你有所幫助。本篇的所有例子,都在我開源的flutter_deer中。希望Star、Fork支援,有問題建議可以Issue。附上鍊接:github.com/simplezhli/…
本系列前兩篇:
1.多語言配置(國際化)
預設情況下,Flutter是沒有進行多語言配置。所以無論我們的手機系統環境是否是中文,一些Widget的文字都是英文顯示。比如常見的輸入框(TextField
)的操作選單、日期選擇(showDatePicker
)上的年月日。
既然沒有配置,那我我們新增上即可。
- 在 pubspec.yaml 中新增依賴:
flutter_localizations:
sdk: flutter
複製程式碼
MaterialApp
中配置localizationsDelegates
和supportedLocales
兩個屬性。
import 'package:flutter_localizations/flutter_localizations.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Deer',
home: SplashPage(),
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('zh', 'CH'),
const Locale('en', 'US')
]
);
}
}
複製程式碼
這裡我只配置了中英文兩個語言的支援。有其他語言的需要可以自行新增。
- 如果是iOS平臺,還需要在Info.plist檔案中做如下配置:
其中:Localized resources can be mixed
為 YES 表示允許應用程式獲取框架庫內語言。
如果你完成了上述的配置,那麼在系統為中文環境時,就會自動將英文替換為中文。
其實翻看flutter_localizations的原始碼,你會發現它內建了多國語言的翻譯。如果你覺得文字不恰當,也可以繼承對應的Localizations
去修改。
2.文字字號
預設文字配置是ThemeData
通過Typography(platform: platform).black
方法獲取的。
比如我們常用的Text
文字配置就是TextTheme
中的body1
。Typography
中按照Material的規範將語言分為三大類別:
-
英語類字型(englishLike) 西歐、中歐、東歐和非洲大部分地區的語言通常用拉丁字母書寫。越南語是一個明顯的例外,雖然它使用了拉丁文書寫系統的本地化形式,但它的重音符號可能比西歐語言中的要高得多。希臘語和西里爾語的書寫系統與拉丁文非常相似。
-
高字型(tall) 語言指令碼,需要額外的行高來容納較大的象形文字,包括南亞、東南亞和中東語言,如阿拉伯語、印地語、泰語和越南語。
-
密集字型(dense) 需要額外的行高才能容納更大的象形文字的語言指令碼,包括中文、日語和韓文。
那麼針對這三種型別的語言,預設的文字大小也會有調整。比如下圖中englishLike
與dense
的對比(點選檢視原始碼):
Text
文字配置body1
在切換為中文環境後,文字的預設大小變為了15.0,然而在英文環境下是14.0。
這個問題也是我是在做完多語言配置後發現的一個問題,因為文字變大導致個別頁面造成了文字溢位元件。所以儘量指定文字大小以避免不必要的這類問題。
3.預先快取圖片
在Flutter中,載入本地圖片會存在一個載入過程。比如點選圖示做圖示的切換時,那麼首次會發生閃動的情況。尤其是做類似引導頁這類需求是,通過左右滑動切換圖片時會發生比較明顯的白屏一閃而過。
解決方法很簡單,就是使用 precacheImage,它將影象預存到影象快取中。如果影象稍後被Image
、BoxDecation
或FadeInImage
使用,它會被載入得更快。
precacheImage(AssetImage("assets/logo"), context);
複製程式碼
本問題詳細的程式碼見:點選檢視
4. 螢幕方向
新建的Flutter專案預設並沒有限制螢幕的橫豎屏,所以如果你的專案並沒有適配橫豎屏,需要限制某一方向。我以限制豎屏為例:
Flutter方法:
void main(){
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown
]).then((_){
runApp(MyApp());
});
}
複製程式碼
原生方法:
Android在android -> app -> src-> main -> AndroidManifest.xml
中的activity標籤新增 screenOrientation
屬性。
<activity
...
android:screenOrientation="portrait">
</activity>
複製程式碼
iOS在Runner ->Info.plist
中刪除UISupportedInterfaceOrientations
中的 UIInterfaceOrientationLandscapeLeft
與·UIInterfaceOrientationLandscapeRight
。最終如下:
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
複製程式碼
上面的方法都是針對整個應用的。如果你希望部分頁面可以橫屏或者豎屏,只能使用Flutter的方法在對應的頁面去指定方向。
不過Flutter這個方法在iOS端有點問題,它並不能強制螢幕旋轉(也就是螢幕當前為豎屏,你指定頁面橫屏顯示,它並不會生效)。所以有這方面需求的同學可以使用flutter_orientation這個外掛。
5.拆分widget
在書寫Flutter的頁面時,難免會巢狀的層級很深或者存在許多重複使用widget。所以一般我們都會將一些widget抽離出來。抽離的方法有兩種,一種是直接抽成方法(函式)返回。一種是抽出一個自定義的widget來使用。我下面舉例說明一下:
class _TestPageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Test"),
),
body: Column(
children: <Widget>[
const Text("Android", style: const TextStyle(fontSize: 12.0, fontStyle: FontStyle.italic, color: Colors.blue),),
const Text("iOS", style: const TextStyle(fontSize: 12.0, fontStyle: FontStyle.italic, color: Colors.blue),),
const Text("Flutter", style: const TextStyle(fontSize: 12.0, fontStyle: FontStyle.italic, color: Colors.blue),),
],
),
);
}
}
複製程式碼
上面程式碼中存在著三個樣式一致,只是文字不同的Text。我們將它抽離出來(當然你也可以直接抽離Column)。
使用第一種方式:
Widget _buildText(String text){
return Text(text, style: const TextStyle(fontSize: 12.0, fontStyle: FontStyle.italic, color: Colors.blue),);
}
複製程式碼
使用方式:
Column(
children: <Widget>[
_buildText("Android"),
_buildText("iOS"),
_buildText("Flutter"),
],
)
複製程式碼
第二種方式:
class _MyText extends StatelessWidget {
const _MyText(this.text, {Key key}) : super(key: key);
final String text;
@override
Widget build(BuildContext context) {
return Text(text, style: const TextStyle(fontSize: 12.0, fontStyle: FontStyle.italic, color: Colors.blue),);
}
}
複製程式碼
使用方式:
Column(
children: <Widget>[
const _MyText("Android"),
const _MyText("iOS"),
const _MyText("Flutter"),
],
)
複製程式碼
看起來是第一種很方便,但不知道你有沒有發現第一種方式無法新增 const
關鍵字。其實問題就出在了這裡。在我之前的部落格中就有提到儘量使用const
關鍵字 來定義常量。那麼這是為什麼呢?
我們來再看一個小例子:
我直接呼叫上面抽出的widget,每次點選按鈕setState
重新整理頁面,輸出widget的hashCode。
const
的好習慣會在無形中提升應用的效能,比如常用的Color、TextStyle、分隔塊我們整合起來,直接呼叫就是很不錯的做法。
這個問題是由Provider
的作者Rémi Rousselet提出的,What is the difference between functions and classes to create widgets?。
作者得出的結論是:永遠不要使用方法返回的形式建立可重用的widget,始終將它們封裝到StatelessWidget中。 注意這個結論中的可重用。
在上面的例子中,我們的文字是固定的(可重用),這導致我們可以直接在widget上新增 const
。實際中,我們的展示的資料都是請求的並不是固定的,我們即使抽離出StatelessWidget
,也無法直接新增 const
來使用。所以如果你的widget並無法重用,使用上述兩種方法的哪一種效果都是一樣的。
上述的例子中,即使Text無法使用const
標記,但是Text中的TextStyle
確可以重用。這一切取決於你的widget拆分的顆粒度是否足夠合理,來儘可能的避免這種效能上的浪費。
當然我更推薦StatelessWidget
的方式。正如作者說的,它具備以下優點:
- 允許效能優化(
const
建構函式,更精細粒度的重建) - 有熱過載
- 整合到widget檢查器中(
debugFillProperties
) - 可以定義Key(關於Key的作用可以看這裡的解答)
- 可以方便的使用context
- 規範所有widget以相同的方式使用(始終使用建構函式)
- 可以確保在兩個不同佈局之間切換時,正確的配置資訊(函式可能重用一些以前的狀態)
如果你之前已經寫了大量的方法建立返回widget的程式碼,可以使用Rémi Rousselet的functional_widget來改善這個問題。
6.其他
-
上面有說道抽離出
StatelessWidget
。其實為了避免因重新整理區域性widget呼叫setState
而導致整個頁面重新整理造成的效能損耗,我們可以將區域性重新整理的地方抽離為StatefulWidget
。我曾經寫過一個頁面經過這樣的優化,耗時由25ms降到了6ms,因此控制重新整理範圍是很必要的。 -
一般情況下不建議使用
Offstage
來做隱藏功能,雖說它可以隱藏指定的widget。但是它還是會建立出對應的widget,只是放在了看不見的“後臺”。我之前就將一個CupertinoActivityIndicator()
這樣隱藏了起來,結果在PerformanceOverlay
中就看到頁面不斷在繪製。。。所以如果你需要隱藏widget,可以使用isGone ? const SizedBox() : CupertinoActivityIndicator()
這類三元運算子的方式處理。 -
可以將資料解析放在
isolate
中處理,避免某些效能不好的裝置在解析資料時造成的卡頓。詳細例子可以檢視文件。
這篇斷斷續續寫了半個月,終於完成了!我可以安心的去參加GDD了。碼字不易,希望點贊支援!最後再次奉上Deer的Github地址,順手也可以支援一波!