上次我們從RichText
出發,一步步摸索出了Widget
, Element
, RenderObject
各自的責任,和彼此之間的相互關係。我們也看到RichText
有這樣的繼承關係:
class RichText extends MultiChildRenderObjectWidget
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget
而MultiChildRenderObjectWidget
產生的MultiChildRenderObjectElement
則是這樣的關係:
class MultiChildRenderObjectElement extends RenderObjectElement
abstract class RenderObjectElement extends Element
最後RichText
產生的RenderParagraph
則是:
class RenderParagraph extends RenderBox
abstract class RenderBox extends RenderObject
光是一個簡單的RichText
就牽涉到這麼多的class,它們彼此之間到底是如何對應的?而我們平常在使用的眾多Widget
,它們對應的Element
和RenderObject
又是什麼?讓我們來看看:
這裡我們畫出了Widget
, Element
, RenderObject
各自的繼承關係圖,以及它們之間的(顏色)對應關係。我們可以看到,Widget
和Element
之間有著明顯的一對一的關係,StatelessWidget
產生StatelessElement
、SingleChildRenderObjectWidget
產生SIngleChildRenderObjectElement
...等等。
然而,RenderObject
卻沒有和Widget
,Element
對應的架構。RenderObjectWidget
底下的Padding
、RichText
、RawImage
,分別建立了RenderObject
這邊的RenderPadding
、RenderParagraph
、RenderImage
,而StatelessWidget
和StatefulWidget
這邊,我們常見的各種Widget
卻沒有對應的RenderObject
。
顯然,只有RenderObjectElement
才會呼叫RenderObjectWidget
去產生RenderObject
,而ComponentElement
(StatelessElement
&StatefulElement
並不會呼叫StatelessWidget
和StatefulWidget
去產生RenderObject
,畢竟這兩種Widget
本身是由其它Widget
複合而成的,不會建立自己的RenderObject
。
簡單來說,既然整個Widget Tree是一個由各種Widget複合出來的樹,我們要去渲染它時自然會遞迴地去呼叫StatelessWidget
和StatefulWidget
的build
函數,直到我們遇到某個沒有build
函數的RenderObjectWidget
,就由它產生其對應的RenderObject
。
最後我們以這個簡單的APP為例:
void main() {
runApp(Container(
alignment: Alignment.center,
child: Text(
'Hello, World!',
textDirection: TextDirection.ltr,
),
));
}
我們可以得到這樣的三顆渲染樹:
注意到雖然我們只複合了Container
和Text
兩個Widget
,但當我們在Container
上設置各種參數(如alignment)時,它的作用其實是幫我們在我們傳入的child(Text
)之上,再去複合各種Widget
。最終的四層Widget Tree,產生了對應的四層Element Tree,但只有在遇到RenderObjectWidget
時才會去產生RenderObject
。
雖然一直逃避作圖到現在,但老實說網路上每一篇關於Flutter渲染樹的文章幾乎都有精美的示意圖,只有我不畫好像有點說不過去。因為美術細胞趨近於零所以又花了不少時間,下次我們終於可以進入真正有趣的部份了:渲染樹的生命週期,也就是從APP啟動,渲染樹建立、更新到回收的整個流程細節,敬請期待!(不保證是下一篇)