首頁>技術>

Flutter是怎麼構建一個檢視頁面的,Widget是如何繪製到螢幕上的,這涉及到三棵樹:

Widget TreeElement TreeRenderObject Tree

Flutter入口函式為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又有啥作用?

如果覺得有什麼錯誤,歡迎拍磚。。。

67
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Laragon快速搭建本地開發環境