Flutter開發中的一些Tips(三)

距離flutter_deer開源快3個月了,目前爲止收穫了1600+的Star,感謝大家的對此項目的認可支持。不過雖然表面看上去光鮮亮麗,但我知道還是有很多不規範不合理的用法及寫法,爲了不對初學者造成誤導作用,所以這期間我幾乎每天都在完善優化它(現在應該還不錯吧)。

今天繼續分享一些在Flutter開發中需要注意的點,希望對你有所幫助。本篇的所有例子,都在我開源的flutter_deer中。希望Star、Fork支持,有問題建議可以Issue。附上鍊接:https://github.com/simplezhli...

本系列前兩篇:

Flutter開發中的一些Tips

Flutter開發中的一些Tips(二)

在這裏插入圖片描述

1.多語言配置(國際化)

默認情況下,Flutter是沒有進行多語言配置。所以無論我們的手機系統環境是否是中文,一些Widget的文字都是英文顯示。比如常見的輸入框(TextField)的操作菜單、日期選擇(showDatePicker)上的年月日。

在這裏插入圖片描述

既然沒有配置,那我我們添加上即可。

  1. 在 pubspec.yaml 中添加依賴:
  flutter_localizations:
    sdk: flutter
  1. MaterialApp 中配置 localizationsDelegatessupportedLocales兩個屬性。
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')
      ]
    );
  }
}

這裏我只配置了中英文兩個語言的支持。有其他語言的需要可以自行添加。

  1. 如果是iOS平臺,還需要在Info.plist文件中做如下配置:

在這裏插入圖片描述

其中:Localized resources can be mixed 爲 YES 表示允許應用程序獲取框架庫內語言。

如果你完成了上述的配置,那麼在系統爲中文環境時,就會自動將英文替換爲中文。

在這裏插入圖片描述

其實翻看flutter_localizations的源碼,你會發現它內置了多國語言的翻譯。如果你覺得文字不恰當,也可以繼承對應的Localizations去修改。在這裏插入圖片描述
本問題詳細的代碼見:點擊查看

2.文字字號

默認文字配置是ThemeData通過Typography(platform: platform).black方法獲取的。

在這裏插入圖片描述

比如我們常用的Text文字配置就是TextTheme中的body1Typography中按照Material的規範將語言分爲三大類別

  1. 英語類字體(englishLike)

西歐、中歐、東歐和非洲大部分地區的語言通常用拉丁字母書寫。越南語是一個明顯的例外,雖然它使用了拉丁文書寫系統的本地化形式,但它的重音符號可能比西歐語言中的要高得多。希臘語和西里爾語的書寫系統與拉丁文非常相似。

  1. 高字體(tall)

語言腳本,需要額外的行高來容納較大的象形文字,包括南亞、東南亞和中東語言,如阿拉伯語、印地語、泰語和越南語。

  1. 密集字體(dense)

需要額外的行高才能容納更大的象形文字的語言腳本,包括中文、日語和韓文。

那麼針對這三種類型的語言,默認的文字大小也會有調整。比如下圖中englishLikedense的對比(點擊查看源碼):

在這裏插入圖片描述
可以看到默認情況下,中文、日文、韓文會比英文類的文字大一個字號。比如常用的Text文字配置body1在切換爲中文環境後,文字的默認大小變爲了15.0,然而在英文環境下是14.0

這個問題也是我是在做完多語言配置後發現的一個問題,因爲文字變大導致個別頁面造成了文字溢出組件。所以儘量指定文字大小以避免不必要的這類問題。

3.預先緩存圖片

在Flutter中,加載本地圖片會存在一個加載過程。比如點擊圖標做圖標的切換時,那麼首次會發生閃動的情況。尤其是做類似引導頁這類需求是,通過左右滑動切換圖片時會發生比較明顯的白屏一閃而過。

在這裏插入圖片描述

解決方法很簡單,就是使用 precacheImage,它將圖像預存到圖像緩存中。如果圖像稍後被ImageBoxDecationFadeInImage使用,它會被加載得更快。

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 關鍵字修飾的widget,並不會重複創建。看來養成隨手添加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地址,順手也可以支持一波!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章