iOSer 的 Flutter 快速入坑之道(二)
前言
本文是繼上一篇的有關(guān) flutter 的第二篇文章摆尝,由于合起來篇幅太長良姆,所以考慮分開成多篇文章上傳啦蚁堤。
Flutter UI
a. Widget
在 iOS 中嫩实,大部分控件都是基于 UIView 的實例刽辙,所有的控件實例在添加到視圖中后,會被作為節(jié)點添加到 UI 渲染樹中舶赔。其實在 Flutter 中的 Widget 概念也是類似扫倡,都會由一棵渲染樹來維護所有的 Widget 子節(jié)點。
但是不同之處在于竟纳,第一撵溃,F(xiàn)lutter 中所有的 widget 在它的生命周期中都是不可變的。在 iOS 中锥累,假如我們要修改并重新渲染一個控件缘挑,可以調(diào)用 setNeedDisplay 方法,然后在這個控件的節(jié)點開始桶略,重新跑一邊所有子節(jié)點语淘,渲染生成新的視圖,但是在這個過程中际歼,控件只是產(chǎn)生的屬性的修改惶翻,實例卻還是那個實例。然而在 Flutter 中就不同了鹅心,由于 Flutter 中的 Widget 都是不可變的吕粗,所以在當(dāng)某個 Widget 需要修改的時候,F(xiàn)lutter 會創(chuàng)建一個新的 Widget 來做替換旭愧。
第二颅筋,其實在 Flutter 中,Widget 不同于 View 的另一個區(qū)別就是 Widget 并不負(fù)責(zé)任何渲染相關(guān)的事情输枯。那么是誰在負(fù)責(zé)渲染呢议泵?Flutter 是怎么樣通過 Widget 來渲染出界面的?在這里我們就要了解 Flutter 中的 Widget桃熄、Element先口、RenderObject 之間的聯(lián)系啦。
Widget 中包含屬性 Element,Element 中又包含了 RenderObject 屬性碉京。Widget 就是一個對 Element 的配置或者描述桩引,我們開發(fā)者也是只需要和 Widget 打交道,并不需要去維護更新渲染樹收夸。
Element 就是負(fù)責(zé)維護渲染樹的實例坑匠,主要作用在 Flutter 的渲染管線 build 階段,可以把 element 看作真正的渲染樹子節(jié)點卧惜。
RenderObject 是用于渲染的對象厘灼,主要作用在 Flutter 的渲染管線 布局以及繪制 階段,也就是它將我們的界面最終繪制在屏幕上咽瓷。
關(guān)于 Flutter 的底層渲染機制设凹,其實我認(rèn)為作為每個初學(xué) Flutter 的同學(xué)都需要了解,只有更深入的了解它的渲染機制茅姜,才能更好地去使用它闪朱。
b.如何使用 Widget 構(gòu)建界面?
在上面我們大概了解了 Widget 是什么東西钻洒,以及 flutter 的渲染機制奋姿,那么實際開發(fā)中如何使用 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 都是通過這種嵌套的方式來表示其相互之間的關(guān)系以及配置称诗,其實很容易懂,不懂的話多看幾種 Widget 的例子就知道啦头遭。
Flutter 中文網(wǎng)
c.StatelessWidget 和 StatefulWidget
StatelessWidget 是不存在中間狀態(tài)的控件寓免,也就是說當(dāng)它被 new 出來之后,就不會被改變了计维,也無法被改變袜香。如果需要更新展示的內(nèi)容,就只能銷毀掉重新 new 一個鲫惶。
StatefulWidget 是可以保存中間狀態(tài)的控件蜈首,控件的中間狀態(tài)保存在 State 類中,通過調(diào)用 setState(){} 方法來觸發(fā)更新機制剑按,將此節(jié)點將其以下的整個子樹重新更新繪制疾就。
d.setState 機制介紹
setState 的作用是告訴框架這個 Object 的 state 已經(jīng)被修改了澜术,那么這個修改它可能會對 UI 有影響艺蝴,所以需要重新 build 一遍該 Object。
首先我們來了解一下一個 [State]Object 的生命周期狀態(tài)鸟废。
enum _StateLifeCycle {
created,
initialized,
ready,
defunct,
}
當(dāng)這個[State]Object 剛被創(chuàng)建時猜敢,它的狀態(tài)就是 created 的狀態(tài)。緊接著 Object 會調(diào)用 didChangeDependencies 方法,這時候 Object 的狀態(tài)會轉(zhuǎn)變?yōu)?initialized缩擂,但是此時鼠冕,object 并沒有做好被 build 的準(zhǔn)備。然后接下來胯盯,object 會轉(zhuǎn)變?yōu)?ready 的狀態(tài)懈费,表示現(xiàn)在已經(jīng)做好 build 的準(zhǔn)備了。object 會保持這個狀態(tài)博脑,直到 dispose 方法被調(diào)用憎乙。一旦 object 調(diào)用了 dispose 方法,那么它的狀態(tài)就會轉(zhuǎn)變成 defunct 的狀態(tài)叉趣。
然后我們可以看到 setState 的源碼泞边。首先 flutter 會判斷 setState 是否在 Object 調(diào)用 dispose 銷毀后再調(diào)用,若 state 為 defunct 疗杉,說明該 Object 已經(jīng)被銷毀了阵谚,報錯。
if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
throw error;
}
接下來我們看到 flutter 還判斷了 object 的狀態(tài)是否為 created 以及 是否非 mounted烟具。那么 mounted 屬性是什么呢梢什?
mounted 屬性代表的意思是這個 object 是否有被 buildContext 所持有。我們知道 mounted 有安裝朝聋、加入的意思绳矩。在一個[state]Object 調(diào)用 initState 方法之前,flutter 會把該[state]Object 通過與 buildContext 關(guān)聯(lián)的方式嵌入玖翅,直至 Object 調(diào)用了 dispose翼馆,也就是不會再調(diào)用 build 了,這個 Object 才不被繼續(xù)持有金度。
所以這兩個條件說明該 [state]Object 還未被加入到 widget 樹中应媚,有可能是在構(gòu)造函數(shù)的時候調(diào)用了 setState,這個是沒有必要的猜极,因為 object 剛被創(chuàng)建的時候是被標(biāo)記為 dirty 的中姜,需要調(diào)用 build。
final dynamic result = fn() as dynamic;
接下來是判斷如果 state 中的更新函數(shù)為一個異步函數(shù)跟伏,返回值是 Future 類型的丢胚,那么也報錯。
最后受扳,才是調(diào)用了 _element.markNeedsBuild 方法携龟。那么 markNeedsBuild 函數(shù)做了什么事情呢。首先勘高,他會判斷 state 是否為 defunct,elementLifeStyle 是否為 active 以及 owner 是否為 nil峡蟋。之后坟桅,將這些節(jié)點都標(biāo)記為 dirty,然后加入到全局的 widget 隊列中蕊蝗,在下一幀的時候進行重建仅乓。
那么什么是 owner,owner 的作用是什么呢蓬戚?
我們可以看到 owner 其實是一個 BuildOwner 的實例夸楣,它主要負(fù)責(zé)跟蹤需要 rebuild 的 widgets。 然后在 scheduleBuildFor 方法中子漩,首先是判斷 element 的 _inDirtyList 是否為 true裕偿,如果是,就說明它已經(jīng)在 dirtyList 中了痛单,于是把它的 dirtyElementsNeedsResorting 屬性設(shè)置為yes嘿棘,返回。
如果一切正常旭绒,那么接下來會調(diào)用到 onBuildScheduled()方法鸟妙,這是一個方法回調(diào),是 VoidCallback 類型挥吵。
那么接著重父,onBuildScheduled 的回調(diào)是在哪里呢。通過搜索我們發(fā)現(xiàn)在 WigetsBinding 的 initInstances 方法中有賦值 onBuildScheduled 代碼塊忽匈。
WigetsBinding 又是什么房午?! 從文檔中我們可以了解到丹允,文檔說 WidgetsBinding 是 wigetsLayer 和 flutter 引擎的粘合劑郭厌。。
好的雕蔽,不管他折柠,先繼續(xù)看內(nèi)部實現(xiàn),我們繼續(xù)往下點追蹤到一個叫
ensureVisualUpdate 的函數(shù)批狐,在其中的一個 case 中扇售,我們看到了 scheduleFrame 的方法。然后再往這個方法里面看嚣艇,可以看到最終它調(diào)用了 window.scheduleFrame 方法承冰。 window 的 scheduleFrame 方法會生成一個新的幀,在這個方法調(diào)用之后食零,flutter 引擎會最終調(diào)用 handleBeginFrame 強制刷新一個新幀困乒,無論之前那一幀是否已經(jīng)渲染結(jié)束了。
所以到這里差不多就是 setState 調(diào)用到屏幕渲染刷新的大致過程了慌洪。整理一下整個過程中調(diào)用的函數(shù)
State.setState() ->
Element.markNeedsBuild() ->
BuildOwner.scheduleBuildFor() ->
WidgetBinding._handleBuildScheduled() ->
SchedulerBinding.ensureVisualUpdate() ->
SchedulerBinding.scheduleFrame() ->
Window.scheduleFrame() ->
WidgetBinding.drawFrame()
e.如何做控件的顯示和隱藏顶燕?
我們已經(jīng)了解了 flutter 中 setState 的機制以及 widget 的一些特性了,那么實際開發(fā)中碰到對 widget 進行顯示隱藏控制等需求的時候冈爹,應(yīng)該怎么處理呢涌攻?
大概的簡單思路就是通過一個變量 t 去表示 widget 的顯示和隱藏,然后再封裝一個函數(shù)根據(jù)這個變量來返回需要顯示的 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),
),
);
}
}
以上是通過一個按鈕點擊控制控件顯示與否的簡單代碼鸵膏,蠻看下~
最后
這邊主要講有關(guān) flutter UI 方面的東西,當(dāng)然 flutter UI 相關(guān)的東西也遠(yuǎn)遠(yuǎn)不止這些怎炊,所以如果后續(xù)有補充的地方谭企,我也會在本文中修改跟進~