Flutter是一個優秀的UI框架,藉助它開箱即用的Widgets咱們可以構建出漂亮和高性能的用戶界面。那這些Widgets究竟是如何工做的又是如何完成渲染的。html
在本文中呢,咱們就來探析Widgets背後的故事-Flutter渲染機制之三棵樹。緩存
在Flutter中和Widgets一塊兒協同工做的還有另外兩個夥伴:Elements和RenderObjects;因爲它們都是有着樹形結構,因此常常會稱它們爲三棵樹。markdown
初步認識了三棵樹以後,那Flutter是如何建立佈局的?以及三棵樹之間他們是如何協同的呢?接下來就讓咱們經過一個簡單的例子來剖析下它們內在的協同關係:架構
class ThreeTree extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
child: Container(color: Colors.blue)
);
}
}
複製代碼
上面這個例子很簡單,它由三個Widget組成:ThreeTree、Container、Text。那麼當Flutter的runApp()方法被調用時會發生什麼呢?框架
當runApp()被調用時,第一時間會在後臺發生如下事件:less
下圖是Flutter通過這三個步驟後的狀態:ide
從圖中能夠看出Flutter建立了三個不一樣的樹,一個對應着Widget,一個對應着Element,一個對應着RenderObject。每個Element中都有着相對應的Widget和RenderObject的引用。能夠說Element是存在於可變Widget樹和不可變RenderObject樹之間的橋樑。Element擅長比較兩個Object,在Flutter裏面就是Widget和RenderObject。它的做用是配置好Widget在樹中的位置,而且保持對於相對應的RenderObject和Widget的引用。工具
簡而言之是爲了性能,爲了複用Element從而減小頻繁建立和銷燬RenderObject。由於實例化一個RenderObject的成本是很高的,頻繁的實例化和銷燬RenderObject對性能的影響比較大,因此當Widget樹改變的時候,Flutter使用Element樹來比較新的Widget樹和原來的Widget樹:oop
//framework.dart
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
Element newChild;
if (child != null) {
assert(() {
final int oldElementClass = Element._debugConcreteSubtype(child);
final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
hasSameSuperclass = oldElementClass == newWidgetClass;
return true;
}());
if (hasSameSuperclass && child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
assert(child.widget == newWidget);
assert(() {
child.owner._debugElementWasRebuilt(child);
return true;
}());
newChild = child;
} else {
deactivateChild(child);
assert(child._parent == null);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
newChild = inflateWidget(newWidget, newSlot);
}
assert(() {
if (child != null)
_debugRemoveGlobalKeyReservation(child);
final Key key = newWidget?.key;
if (key is GlobalKey) {
key._debugReserveFor(this, newChild);
}
return true;
}());
return newChild;
}
...
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
複製代碼
看到這裏你是否會以爲整個Flutter APP就像是一個RecycleView呢?佈局
由於在框架中,Element是被抽離開來的,因此你不須要常常和它們打交道。每一個Widget的build(BuildContext context)方法中傳遞的context就是實現了BuildContext接口的Element。
由於Widget是不可變的,當某個Widget的配置改變的時候,整個Widget樹都須要被重建。例如當咱們改變一個Container的顏色爲橙色的時候,框架就會觸發一個重建整個Widget樹的動做。由於有了Element的存在,Flutter會比較新的Widget樹中的第一個Widget和以前的Widget。接下來比較Widget樹中第二個Widget和以前Widget,以此類推,直到Widget樹比較完成。
class ThreeTree extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.orange,
child: Container(color: Colors.blue,),
);
}
}
複製代碼
Flutter遵循一個最基本的原則:判斷新的Widget和老的Widget是不是同一個類型:
在咱們的例子中,ThreeTree Widget是和原來同樣的類型,它的配置也是和原來的ThreeTreeRender同樣的,因此什麼都不會發生。下一個節點在Widget樹中是Container Widget,它的類型和原來是同樣的,可是它的顏色變化了,因此RenderObject的配置也會發生對應的變化,而後它會從新渲染,其餘的對象都保持不變。
注意這三個樹,配置發生改變以後,Element和RenderObject實例沒有發生變化。
上面這個過程是很是快的,由於Widget的不變性和輕量級使得他能快速的建立,這個過程當中那些重量級的RenderObject則是保持不變的,直到與其相對應類型的Widget從Widget樹中被移除。
class ThreeTree extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.orange,
child: FlatButton(
onPressed: () {},
child: Text('三棵樹'),
),
);
}
}
複製代碼
和剛纔流程同樣,Flutter會重新Widget樹的頂端向下遍歷,與原有樹中的Widget類型進行對比。
由於FlatButton的類型與Element樹中相對應位置的Element的類型不一樣,Flutter將會從各自的樹上刪除這個Element和相對應的ContainerRender,而後Flutter將會重建與FlatButton相對應的Element和RenderObject。
當新的RenderObject樹被重建後將會計算佈局,而後繪製在屏幕上面。Flutter內部使用了不少優化方法和緩存策略來處理,因此你不須要手動來處理這些。
以上即是Flutter的總體渲染機制,能夠看出Flutter利用了三棵樹很巧妙的解決的性能的問題。