書接上文
控件樹中的每個控件通過實現RenderObjectWidget.createRenderObject(BuildContext context)
→ RenderObject
方法來創建對應的不同類型的RenderObject
對象,組成渲染對象樹。因為Flutter極大地簡化了布局的邏輯,所以整個布局過程中只需要深度遍歷一次:
image
渲染對象樹中的每個對象都會在布局過程中接受父對象的Constraints參數,決定自己的大小,然后父對象就可以按照自己的邏輯決定各個子對象的位置,完成布局過程。
子對象不存儲自己在容器中的位置,所以在它的位置發生改變時并不需要重新布局或者繪制。子對象的位置信息存儲在它自己的parentData字段中,但是該字段由它的父對象負責維護,自身并不關心該字段的內容。同時也因為這種簡單的布局邏輯,Flutter可以在某些節點設置布局邊界(Relayout boundary),即當邊界內的任何對象發生重新布局時,不會影響邊界外的對象,反之亦然.
布局完成后,渲染對象樹中的每個節點都有了明確的尺寸和位置,Flutter會把所有對象繪制到不同的圖層上:
image
因為繪制節點時也是深度遍歷,可以看到第二個節點在繪制它的背景和前景不得不繪制在不同的圖層上,因為第四個節點切換了圖層(因為“4”節點是一個需要獨占一個圖層的內容,比如視頻),而第六個節點也一起繪制到了紅色圖層。這樣會導致第二個節點的前景(也就是“5”)部分需要重繪時,和它在邏輯上毫不相干但是處于同一圖層的第六個節點也必須重繪。為了避免這種情況,Flutter提供了另外一個“重繪邊界”的概念:在進入和走出重繪邊界時,Flutter會強制切換新的圖層,這樣就可以避免邊界內外的互相影響。典型的應用場景就是ScrollView,當滾動內容重繪時,一般情況下其他內容是不需要重繪的。雖然重繪邊界可以在任何節點手動設置,但是一般不需要我們來實現,Flutter提供的控件默認會在需要設置的地方自動設置。
控件庫(Widgets)
Flutter的控件庫提供了非常豐富的控件,包括最基本的文本、圖片、容器、輸入框和動畫等等。在Flutter中“一切皆是控件”,通過組合、嵌套不同類型的控件,就可以構建出任意功能、任意復雜度的界面。它包含的最主要的幾個類有:
// WidgetsFlutterBinding 是Flutter的控件框架和Flutter引擎的膠水層, 基于Flutter控件系統開發的程序都需要使用
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding,
PaintingBinding, RendererBinding, WidgetsBinding { ... }
// Widget就是所有控件的基類,它本身所有的屬性都是只讀的。
abstract class Widget extends DiagnosticableTree { ... }
// StatelessWidget和StatefulWidget并不會直接影響RenderObject的創建,它們只負責創建對應的RenderObjectWidget
// StatelessElement和StatefulElement也是類似的功能。
abstract class StatelessWidget extends Widget { ... }
abstract class StatefulWidget extends Widget { ... }
// RenderObjectWidget所有的實現類則負責提供配置信息并創建具體的RenderObjectElement。
abstract class RenderObjectWidget extends Widget { ... }
// Element是Flutter用來分離控件樹和真正的渲染對象的中間層,控件用來描述對應的element屬性,控件重建后可能會復用同一個element。
abstract class Element extends DiagnosticableTree implements BuildContext { ... }
class StatelessElement extends ComponentElement { ... }
class StatefulElement extends ComponentElement { ... }
// RenderObjectElement持有真正負責布局、繪制和碰撞測試(hit test)的RenderObject對象。
abstract class RenderObjectElement extends Element { ... }
它們之間的關系如下圖:
image
- 如果控件的屬性發生了變化(因為控件的屬性是只讀的,所以變化也就意味著重新創建了新的控件樹),但是其樹上每個節點的類型沒有變化時,element樹和render樹可以完全重用原來的對象(因為element和render object的屬性都是可變的)
- 如果控件樹中的某個節點的類型發生了變化,則 element樹和 render樹中對應的節點也需要重新創建