Flutter是怎麼構建一個檢視頁面的,Widget是如何繪製到螢幕上的,這涉及到三棵樹:
Widget TreeElement TreeRenderObject TreeFlutter入口函式為main()函式
void main()=> run(new MyApp());//MyApp是一個Widget
runApp 函式接收一個Widget型別的物件作為引數,也就可以理解為萬物皆為Widget,其他的業務邏輯等都只是在為Widget的資料,狀態改變而服務,下面我們看看runApp裡面都做了些什麼:
void runApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() ..attachRootWidget(app)//把傳進來的Widget掛載到跟Widget ..scheduleWarmUpFrame();//主動構建檢視}class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding { //單例 static WidgetsBinding ensureInitialized() { if (WidgetsBinding.instance == null) WidgetsFlutterBinding(); return WidgetsBinding.instance; }}
在runApp中會例項化一個WidgetsFlutterBinding單例,然後將傳進來的Widget掛載到跟Widget上,WidgetsFlutterBinding通過mixin來使用框架中實現的其他 binding的 Service,比如 手勢、基礎服務、佇列、繪圖等
接下來我們看看attachRootWidget方法做了什麼:
// ElementElement _renderViewElement;void attachRootWidget(Widget rootWidget) { _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>( container: renderView, debugShortDescription: '[root]', child: rootWidget, ).attachToRenderTree(buildOwner, renderViewElement);}
attachRootWidget把 widget交給了 RenderObjectToWidgetAdapter這個介面卡,通過attachRootWidget,Element被建立,並且同時能持有 Widget和 RenderObject的引用。然後我們看看attachToRenderTree做了什麼:
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) { if (element == null) { owner.lockState(() { element = createElement(); assert(element != null); element.assignOwner(owner); }); owner.buildScope(element, () { element.mount(null, null); }); } else { element._newWidget = this; element.markNeedsBuild(); } return element;}
從原始碼中我們能看到如果element是空,則呼叫createElement方法去建立,然後通過mount方法將其掛載到檢視樹上。但是走到這我們都不知道Widget是怎麼被畫出來的,只是大概了解到噹噹一個Widget首次被建立的時候,那麼這個Widget會過Widget.createElement inflate成一個element,掛在 element tree 上。現在我們看一個簡單的控制元件Opacity(設定控制元件的不透明度,取值[0,1])
Opacity 繼承關係Opacity extends SingleChildRenderObjectWidget extends RenderObjectWidget extends Widget
StatelessWidget 繼承關係
StatelessWidget extends Widget
StatefulWidget 繼承關係
StatefulWidget extends Widget
Opacity 比StatelessWidget,StatefulWidget多了 SingleChildRenderObjectWidget,RenderObjectWidget兩層繼承關係
RenderObjectWidget 原始碼/// RenderObjectWidgets provide the configuration for [RenderObjectElement]s,/// which wrap [RenderObject]s, which provide the actual rendering of the/// application.//半吊子註釋: RenderObjectWidgets 為 [RenderObjectElement]s提供配置,而真正為應用渲染檢視的的是包裹Widget的 [RenderObject]s,所以RenderObject 才是實際繪製檢視的物件abstract class RenderObjectWidget extends Widget { //構造 const RenderObjectWidget({ Key key }) : super(key: key); /// RenderObjectWidgets always inflate to a [RenderObjectElement] subclass. //RenderObjectWidgets 一直填充於一個 RenderObjectElement 的子類,建立element物件 @override RenderObjectElement createElement(); /// Creates an instance of the [RenderObject] class that this /// [RenderObjectWidget] represents, using the configuration described by this /// [RenderObjectWidget]. /// /// This method should not do anything with the children of the render object. /// That should instead be handled by the method that overrides /// [RenderObjectElement.mount] in the object rendered by this object's /// [createElement] method. See, for example, /// [SingleChildRenderObjectElement.mount]. @protected RenderObject createRenderObject(BuildContext context); ....}
通過此類可以知道Widget為Element提供配置,RenderObject真正繪製檢視。還有一個方法就是createRenderObject(BuildContext context),看其註釋,此方法返回一個RenderObject例項,去描述(表現)RenderObjectWidget的配置資訊。此方法不應對render物件的子代執行任何操作。而是由可覆蓋的RenderObjectElement.mount方法呼叫處理,例如SingleChildRenderObjectElement中的mount方法。
看到這裡讓我想起之前 attachRootWidget 這個方法的原始碼,再貼一次:
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) { if (element == null) { owner.lockState(() { element = createElement(); assert(element != null); element.assignOwner(owner); }); owner.buildScope(element, () { element.mount(null, null); }); } else { element._newWidget = this; element.markNeedsBuild(); } return element; }
可以看到attachToRenderTree中element呼叫mount()方法,mount 方法例項化一個RenderObject,由RenderObject 物件繪製檢視。還是接著看SingleChildRenderObjectWidget類:
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget { const SingleChildRenderObjectWidget({ Key key, this.child }) : super(key: key); final Widget child; // 重寫 createElement,返回 SingleChildRenderObjectElement 例項物件 @override SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);}
接著看 SingleChildRenderObjectElement類:
class SingleChildRenderObjectElement extends RenderObjectElement { Element _child; //重寫 的mount方法 @override void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); _child = updateChild(_child, widget.child, null); }}
super.mount(parent, newSlot)RenderObject _renderObject;@overridevoid mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); //建立_renderObject例項物件 _renderObject = widget.createRenderObject(this); assert(() { _debugUpdateRenderObjectOwner(); return true; }()); assert(_slot == newSlot); attachRenderObject(newSlot); _dirty = false;}
由此知道,當呼叫mount掛載的時候,會呼叫createRenderObject生成_renderObject例項。而createRenderObject 方法我們可以在Opacity這個元件類裡看到,它返回了一個RenderOpacity的例項:
@overrideRenderOpacity createRenderObject(BuildContext context) { return RenderOpacity( opacity: opacity, alwaysIncludeSemantics: alwaysIncludeSemantics, );}
在RenderOpacity中,重寫paint() 方法,呼叫context.pushOpacity繪製檢視,控制透明度:
void paint(PaintingContext context, Offset offset) { if (child != null) { if (_alpha == 0) { return; } if (_alpha == 255) { context.paintChild(child, offset); return; } assert(needsCompositing); context.pushOpacity(offset, _alpha, super.paint); }}
小結呼叫runApp(rootWidget),將rootWidget傳給rootElement,做為rootElement的子節點,生成Element樹,由Element樹生成Render樹Widget:存放渲染內容、檢視佈局資訊,widget的屬性最好都是immutable(一成不變的)Element:存放上下文,通過Element遍歷檢視樹,Element同時持有Widget和RenderObject RenderObject:根據Widget的佈局屬性進行layout,paint Widget傳入的內容
在第一次建立 Widget 的時候,會對應建立一個 Element, 然後將該元素插入樹中。如果之後 Widget 發生了變化,則將其與舊的 Widget 進行比較,並且相應地更新 Element。重要的是,Element 被不會重建,只是更新而已。這個目前的我還沒看相關的原始碼。。。
看到這雖然對flutter繪製有一定了解,但是好多問題出現了:
檢視的更新機制,更新的依據是什麼樣?BuildContext 又有啥作用?Widget中的Key又有啥作用?如果覺得有什麼錯誤,歡迎拍磚。。。