Flutter完整開發實戰詳解(八、 實用技巧與填坑)

戀貓de小郭發表於2019-03-29

作為系列文章的第八篇,本篇是主要講述 Flutter 開發過程中的實用技巧,讓你少走彎路少掉坑,全篇屬於很乾的乾貨總結,以實用為主,算是在深入原理過程中穿插的實用篇章。

前文:

1、Text 的 TextOverflow.ellipsis 不生效

有時候我們為 Text 設定 ellipsis ,卻發現並沒有生效,而是出現如下圖左邊提示 overflowed 的警告。

其實大部分時候,這是 Text 內部的 RenderParagraph 在判斷 final bool didOverflowWidth = size.width < textSize.width; 時, size.widthtextSize.width 是相等導致的。

所以你需要給 Text 設定一個 Container 之類的去約束它的大小,或者是 Row 中通過 Expanded + Container 去約束你的 Text

請無檢視片

2、獲取控制元件的大小和位置

看過第六篇的同學應該知道, 我們可以用 GlobalKey ,通過 key 去獲取到控制元件物件的 BuildContext,而前面我們也說過 BuildContext 的實現其實是 Element ,而 Element 持有 RenderObject 。So,我們知道的 RenderObject ,實際上獲取到的就是 RenderBox ,那麼通過 RenderBox 我們就只大小和位置了:

  showSizes() {
    RenderBox renderBoxRed = fileListKey.currentContext.findRenderObject();
    print(renderBoxRed.size);
  }

  showPositions() {
    RenderBox renderBoxRed = fileListKey.currentContext.findRenderObject();
    print(renderBoxRed.localToGlobal(Offset.zero));
  }
複製程式碼

3、獲取狀態列高度和安全佈局

如果你看過 MaterialApp 的原始碼,你應該會看到它的內部是一個 WidgetsApp ,而 WidgetsApp 內有一個 MediaQuery,熟悉它的朋友知道我們可以通過 MediaQuery.of(context).size 去獲取螢幕大小。

其實 MediaQuery 是一個 InheritedWidget ,它有一個叫 MediaQueryData 的引數,這個引數是通過如下圖設定的,再通過原始碼我們知道,一般情況下 MediaQueryDatapaddingtop 就是狀態列的高度。

所以我們可以通過 MediaQueryData.fromWindow(WidgetsBinding.instance.window).padding.top 獲取到狀態列高度,當然有時候可能需要考慮 viewInsets 引數。

Flutter完整開發實戰詳解(八、 實用技巧與填坑)

至於 AppBar 的高度,預設是 Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)),kToolbarHeight 是一個固定資料,當然你可以通過實現 PreferredSizeWidget 去自定義 AppBar

同時你可能會發現,有時候在佈局時發現佈局位置不正常,居然是從狀態列開始計算,這時候你需要用 SafeArea 巢狀下,至於為什麼,看原始碼你就會發現 MediaQueryData 的存在。

4、設定狀態列顏色和圖示顏色

簡單的可以通過 AppBarbrightness 或者 ThemeData 去設定狀態列顏色。

但是如果你不想用 AppBar ,那麼你可以巢狀 AnnotatedRegion<SystemUiOverlayStyle> 去設定狀態列樣式,通過 SystemUiOverlayStyle 就可以快速設定狀態列和底部導航欄的樣式。

同時你還可以通過 SystemChrome.setSystemUIOverlayStyle 去設定,前提是你沒有使用 AppBar需要注意的是,所有狀態列設定是全域性的, 如果你在 A 頁面設定後,B 頁面沒有手動設定或者使用 AppBar ,那麼這個設定將直接呈現在 B 頁面。

5、系統字型縮放

現在的手機一般都提供字型縮放,這給應用開發的適配上帶來一定工作量,所以大多數時候我們會選擇禁止應用跟隨系統字型縮放。

在 Flutter 中字型縮放也是和 MediaQueryDatatextScaleFactor 有關。所以我們可以在需要的頁面,通過最外層巢狀如下程式碼設定,將字型設定為預設不允許縮放。

    MediaQuery(
      data: MediaQueryData.fromWindow(WidgetsBinding.instance.window).copyWith(textScaleFactor: 1),
      child: new Container(),
    );
複製程式碼

6、Margin 和 Padding

在使用 Container 的時候我們經常會使用到 marginpadding 引數,其實在上一篇我們已經說過, Container 其實只是對各種佈局的封裝,內部的 marginpadding 其實是通過 Padding 實現的,而 Padding 不支援負數,所以如果你需要用到負數的情況下,推薦使用 Transform

  Transform(
      transform: Matrix4.translationValues(10, -10, 0),
      child: new Container(),
    );
複製程式碼

7、控制元件圓角裁剪

日常開發中我們大致上會使用兩種圓角方案:

  • 一種是通過 Decoration 的實現類 BoxDecoration 去實現。
  • 一種是通過 ClipRRect 去實現。

其中 BoxDecoration 一般應用在 DecoratedBoxContainer 等控制元件,這種實現一般都是直接 Canvas 繪製時,針對當前控制元件的進行背景圓角化,並不會影響其 child 。這意味著如果你的 child 是圖片或者也有背景色,那麼很可能圓角效果就消失了。

ClipRRect 的效果就是會影響 child 的,具體看看其如下的 RenderObject 原始碼可知。

Flutter完整開發實戰詳解(八、 實用技巧與填坑)

8、PageView

如果你在使用 TarBarView ,並且使用了 KeepAlive 的話,那麼我推薦你直接使用 PageView 。因為目前到 1.2 的版本,在 KeepAlive 的 狀態下,跨兩個頁面以上的 Tab 直接切換, TarBarView 會導致頁面的 dispose 再重新 initState。儘管 TarBarView 內也是封裝了 PageView + TabBar

你可以直接使用 PageView + TabBar 去實現,然後 tab 切換時使用 _pageController.jumpTo(MediaQuery.of(context).size.width * index); 可以避免一些問題。當然,這時候損失的就是動畫效果了。事實上 TarBarView 也只是針對 PageView + TabBar 做了一層封裝。

除了這個,其實還有第二種做法,使用如下方 PageStorageKey 保持頁面數狀態,但是因為它是 save and restore values ,所以的頁面的 dispose 再重新 initState 方法,每次都會被呼叫。

    return new Scaffold(
      key: new PageStorageKey<your value type>(your value)
    )
複製程式碼

9、懶載入

Flutter 中通過 FutureBuilder 或者 StreamBuilder 可以和簡單的實現懶載入,通過 future 或者 stream “非同步” 獲取資料,之後通過 AsyncSnapshot 的 data 再去載入資料,至於流和非同步的概念,以後再展開吧。

10、Android 返回鍵回到桌面

Flutter 官方已經為你提供了 android_intent 外掛了,這種情況下,實現回到桌面可以如下簡單實現:

  Future<bool> _dialogExitApp(BuildContext context) async {
    if (Platform.isAndroid) {
      AndroidIntent intent = AndroidIntent(
        action: 'android.intent.action.MAIN',
        category: "android.intent.category.HOME",
      );
      await intent.launch();
    }

    return Future.value(false);
  }
·····
 return WillPopScope(
      onWillPop: () {
        return _dialogExitApp(context);
      },
      child:xxx);
複製程式碼

自此,第八篇終於結束了!(///▽///)

資源推薦

完整開源專案推薦:
文章

《Flutter完整開發實戰詳解(一、Dart語言和Flutter基礎)》

《Flutter完整開發實戰詳解(二、 快速開發實戰篇)》

《Flutter完整開發實戰詳解(三、 打包與填坑篇)》

《Flutter完整開發實戰詳解(四、Redux、主題、國際化)》

《Flutter完整開發實戰詳解(五、 深入探索)》

《Flutter完整開發實戰詳解(六、 深入Widget原理)》

《Flutter完整開發實戰詳解(七、 深入佈局原理)》

《Flutter完整開發實戰詳解(八、 實用技巧與填坑)》

《Flutter完整開發實戰詳解(九、 深入繪製原理)》

《Flutter完整開發實戰詳解(十、 深入圖片載入流程)》

《Flutter完整開發實戰詳解(十一、全面深入理解Stream)》

《跨平臺專案開源專案推薦》

《移動端跨平臺開發的深度解析》

《React Native 的未來與React Hooks》

我們還會再見嗎?

相關文章