iOSer 的 Flutter 快速入坑之道(二)

iOSer 的 Flutter 快速入坑之道(二)

前言

本文是繼上一篇的有關 flutter 的第二篇文章,由于合起來篇幅太長,所以考慮分開成多篇文章上傳啦。

Flutter UI

a. Widget

在 iOS 中,大部分控件都是基于 UIView 的實例,所有的控件實例在添加到視圖中后,會被作為節點添加到 UI 渲染樹中。其實在 Flutter 中的 Widget 概念也是類似,都會由一棵渲染樹來維護所有的 Widget 子節點。

但是不同之處在于,第一,Flutter 中所有的 widget 在它的生命周期中都是不可變的。在 iOS 中,假如我們要修改并重新渲染一個控件,可以調用 setNeedDisplay 方法,然后在這個控件的節點開始,重新跑一邊所有子節點,渲染生成新的視圖,但是在這個過程中,控件只是產生的屬性的修改,實例卻還是那個實例。然而在 Flutter 中就不同了,由于 Flutter 中的 Widget 都是不可變的,所以在當某個 Widget 需要修改的時候,Flutter 會創建一個新的 Widget 來做替換。

第二,其實在 Flutter 中,Widget 不同于 View 的另一個區別就是 Widget 并不負責任何渲染相關的事情。那么是誰在負責渲染呢?Flutter 是怎么樣通過 Widget 來渲染出界面的?在這里我們就要了解 Flutter 中的 Widget、Element、RenderObject 之間的聯系啦。

Widget 中包含屬性 Element,Element 中又包含了 RenderObject 屬性。Widget 就是一個對 Element 的配置或者描述,我們開發者也是只需要和 Widget 打交道,并不需要去維護更新渲染樹。

Element 就是負責維護渲染樹的實例,主要作用在 Flutter 的渲染管線 build 階段,可以把 element 看作真正的渲染樹子節點。

RenderObject 是用于渲染的對象,主要作用在 Flutter 的渲染管線 布局以及繪制 階段,也就是它將我們的界面最終繪制在屏幕上。

關于 Flutter 的底層渲染機制,其實我認為作為每個初學 Flutter 的同學都需要了解,只有更深入的了解它的渲染機制,才能更好地去使用它。

深入了解Flutter界面開發

b.如何使用 Widget 構建界面?

在上面我們大概了解了 Widget 是什么東西,以及 flutter 的渲染機制,那么實際開發中如何使用 Widget呢?

還是從 hello world 開始吧!

import 'package:flutter/material.dart';
 void main() => runApp(new MyApp());

 class MyApp extends StatelessWidget {
 @override
  Widget build(BuildContext context) {
      return new MaterialApp(
         title: 'Welcome to Flutter',
             home: new Scaffold(
                 appBar: new AppBar(
                  title: new Text('Welcome to Flutter'),
                  ),
          body: new Center(
               child: new Text('Hello World'),
           ),
          ),
       );
      }
 }

所有的 Widget 都是通過這種嵌套的方式來表示其相互之間的關系以及配置,其實很容易懂,不懂的話多看幾種 Widget 的例子就知道啦。
Flutter 中文網

c.StatelessWidget 和 StatefulWidget

StatelessWidget 是不存在中間狀態的控件,也就是說當它被 new 出來之后,就不會被改變了,也無法被改變。如果需要更新展示的內容,就只能銷毀掉重新 new 一個。

StatefulWidget 是可以保存中間狀態的控件,控件的中間狀態保存在 State 類中,通過調用 setState(){} 方法來觸發更新機制,將此節點將其以下的整個子樹重新更新繪制。

d.setState 機制介紹

setState 的作用是告訴框架這個 Object 的 state 已經被修改了,那么這個修改它可能會對 UI 有影響,所以需要重新 build 一遍該 Object。

首先我們來了解一下一個 [State]Object 的生命周期狀態。

 enum _StateLifeCycle {
 created,
 initialized,
 ready,
 defunct,
 }

當這個[State]Object 剛被創建時,它的狀態就是 created 的狀態。緊接著 Object 會調用 didChangeDependencies 方法,這時候 Object 的狀態會轉變為 initialized,但是此時,object 并沒有做好被 build 的準備。然后接下來,object 會轉變為 ready 的狀態,表示現在已經做好 build 的準備了。object 會保持這個狀態,直到 dispose 方法被調用。一旦 object 調用了 dispose 方法,那么它的狀態就會轉變成 defunct 的狀態。

然后我們可以看到 setState 的源碼。首先 flutter 會判斷 setState 是否在 Object 調用 dispose 銷毀后再調用,若 state 為 defunct ,說明該 Object 已經被銷毀了,報錯。

  if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
      throw error;
      }

接下來我們看到 flutter 還判斷了 object 的狀態是否為 created 以及 是否非 mounted。那么 mounted 屬性是什么呢?

mounted 屬性代表的意思是這個 object 是否有被 buildContext 所持有。我們知道 mounted 有安裝、加入的意思。在一個[state]Object 調用 initState 方法之前,flutter 會把該[state]Object 通過與 buildContext 關聯的方式嵌入,直至 Object 調用了 dispose,也就是不會再調用 build 了,這個 Object 才不被繼續持有。

所以這兩個條件說明該 [state]Object 還未被加入到 widget 樹中,有可能是在構造函數的時候調用了 setState,這個是沒有必要的,因為 object 剛被創建的時候是被標記為 dirty 的,需要調用 build。

    final dynamic result = fn() as dynamic;

接下來是判斷如果 state 中的更新函數為一個異步函數,返回值是 Future 類型的,那么也報錯。

最后,才是調用了 _element.markNeedsBuild 方法。那么 markNeedsBuild 函數做了什么事情呢。首先,他會判斷 state 是否為 defunct,elementLifeStyle 是否為 active 以及 owner 是否為 nil。之后,將這些節點都標記為 dirty,然后加入到全局的 widget 隊列中,在下一幀的時候進行重建。

那么什么是 owner,owner 的作用是什么呢?

我們可以看到 owner 其實是一個 BuildOwner 的實例,它主要負責跟蹤需要 rebuild 的 widgets。 然后在 scheduleBuildFor 方法中,首先是判斷 element 的 _inDirtyList 是否為 true,如果是,就說明它已經在 dirtyList 中了,于是把它的 dirtyElementsNeedsResorting 屬性設置為yes,返回。
如果一切正常,那么接下來會調用到 onBuildScheduled()方法,這是一個方法回調,是 VoidCallback 類型。

那么接著,onBuildScheduled 的回調是在哪里呢。通過搜索我們發現在 WigetsBinding 的 initInstances 方法中有賦值 onBuildScheduled 代碼塊。

WigetsBinding 又是什么?! 從文檔中我們可以了解到,文檔說 WidgetsBinding 是 wigetsLayer 和 flutter 引擎的粘合劑。。

好的,不管他,先繼續看內部實現,我們繼續往下點追蹤到一個叫
ensureVisualUpdate 的函數,在其中的一個 case 中,我們看到了 scheduleFrame 的方法。然后再往這個方法里面看,可以看到最終它調用了 window.scheduleFrame 方法。 window 的 scheduleFrame 方法會生成一個新的幀,在這個方法調用之后,flutter 引擎會最終調用 handleBeginFrame 強制刷新一個新幀,無論之前那一幀是否已經渲染結束了。

所以到這里差不多就是 setState 調用到屏幕渲染刷新的大致過程了。整理一下整個過程中調用的函數

State.setState() -> 
Element.markNeedsBuild() -> 
BuildOwner.scheduleBuildFor() -> 
WidgetBinding._handleBuildScheduled() -> 
SchedulerBinding.ensureVisualUpdate() -> 
SchedulerBinding.scheduleFrame() -> 
Window.scheduleFrame() -> 
WidgetBinding.drawFrame()    

e.如何做控件的顯示和隱藏?

我們已經了解了 flutter 中 setState 的機制以及 widget 的一些特性了,那么實際開發中碰到對 widget 進行顯示隱藏控制等需求的時候,應該怎么處理呢?

大概的簡單思路就是通過一個變量 t 去表示 widget 的顯示和隱藏,然后再封裝一個函數根據這個變量來返回需要顯示的 widget 或 Container()。在 flutter 中,如果需要表示返回空的一個 widget 的時候,不要返回 null,而是返回 Container()。最后在需要修改變量 t 的時候加上 setState 代碼塊,表示需要重新繪制。

class _TestState extends State<Test> {
  bool toggle = true;
  void _toggle() {
    setState(() {
      toggle = !toggle;
    });
  }

  _getToggleChild() {
    if (toggle) {
      return Text('Text');
    } else {
      return Container();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Test App"),
      ),
      body: Center(
        child: _getToggleChild(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _toggle,
        child: Icon(Icons.update),
      ),
    );
  }
}   

以上是通過一個按鈕點擊控制控件顯示與否的簡單代碼,蠻看下~

最后

這邊主要講有關 flutter UI 方面的東西,當然 flutter UI 相關的東西也遠遠不止這些,所以如果后續有補充的地方,我也會在本文中修改跟進~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容

  • 原文在此,此處只為學習 Widget與ElementWidget主要接口Stateless WidgetState...
    lltree閱讀 4,524評論 0 1
  • 作為家長您是怎么對待孩子玩手機的問題的,強制沒收對孩子來說已經不起作用了,反而易造成孩子的逆反心理,今天這篇文章給...
    子曰語果閱讀 491評論 0 0
  • (十二) 許白曼在晉國府上住了些許日子,她能感覺到唐予正對她的感情,只是大仇未報,自己戴罪之身也未沉冤昭雪,哪還顧...
    老死不死閱讀 892評論 0 54
  • 走不動的老人,搬不走的墓碑 就讓他們留守在時光里 守候流水般空寂的鄉村 日暮降至 興致而歸的羊群,攆著薄薄的黃昏 ...
    莫名小情許閱讀 137評論 0 0
  • 感賞自己每天堅持學習讀書,每天進步那么一點點都值得高興。 大女兒看手機時間太長,昨晚她爸爸要準備說她被我制止了,感...
    延愛閱讀 155評論 0 3