從源碼了解Flutter繪制

Flutter是怎么構(gòu)建一個視圖頁面的碰凶,Widget是如何繪制到屏幕上的欲低,這涉及到三棵樹:

  • Widget Tree
  • Element Tree
  • RenderObject Tree

Flutter入口函數(shù)為main()函數(shù)

void main()=> run(new MyApp());//MyApp是一個Widget

runApp 函數(shù)接收一個Widget類型的對象作為參數(shù)砾莱,也就可以理解為萬物皆為Widget,其他的業(yè)務(wù)邏輯等都只是在為Widget的數(shù)據(jù)腊瑟,狀態(tài)改變而服務(wù),下面我們看看runApp里面都做了些什么:

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..attachRootWidget(app)//把傳進(jìn)來的Widget掛載到跟Widget
    ..scheduleWarmUpFrame();//主動構(gòu)建視圖
}


class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

  //單例
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}


在runApp中會實(shí)例化一個WidgetsFlutterBinding單例闰非,然后將傳進(jìn)來的Widget掛載到跟Widget上峭范,WidgetsFlutterBinding通過mixin來使用框架中實(shí)現(xiàn)的其他 binding的 Service纱控,比如 手勢、基礎(chǔ)服務(wù)舶掖、隊(duì)列唾那、繪圖等

接下來我們看看attachRootWidget方法做了什么:

// Element
Element _renderViewElement;

void attachRootWidget(Widget rootWidget) {
  _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
    container: renderView,
    debugShortDescription: '[root]',
    child: rootWidget,
  ).attachToRenderTree(buildOwner, renderViewElement);
}

attachRootWidget把 widget交給了 RenderObjectToWidgetAdapter這個適配器闹获,通過attachRootWidget避诽,Element被創(chuàng)建沙庐,并且同時(shí)能持有 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是空贡耽,則調(diào)用createElement方法去創(chuàng)建蒲赂,然后通過mount方法將其掛載到視圖樹上刁憋。但是走到這我們都不知道Widget是怎么被畫出來的,只是大概了解到當(dāng)當(dāng)一個Widget首次被創(chuàng)建的時(shí)候至耻,那么這個Widget會過Widget.createElement inflate成一個element,掛在 element tree 上〕就牵現(xiàn)在我們看一個簡單的控件Opacity(設(shè)置控件的不透明度,取值[0,1])

  • Opacity 繼承關(guān)系
Opacity extends SingleChildRenderObjectWidget extends RenderObjectWidget extends Widget
  • StatelessWidget 繼承關(guān)系
StatelessWidget extends Widget
  • StatefulWidget 繼承關(guān)系
StatefulWidget extends Widget

Opacity 比StatelessWidget,StatefulWidget多了 SingleChildRenderObjectWidget泥耀,RenderObjectWidget兩層繼承關(guān)系

  • RenderObjectWidget 源碼
/// RenderObjectWidgets provide the configuration for [RenderObjectElement]s,
/// which wrap [RenderObject]s, which provide the actual rendering of the
/// application.
//半吊子注釋: RenderObjectWidgets 為 [RenderObjectElement]s提供配置,而真正為應(yīng)用渲染視圖的的是包裹Widget的 [RenderObject]s痰催,所以RenderObject 才是實(shí)際繪制視圖的對象
abstract class RenderObjectWidget extends Widget {

  //構(gòu)造
  const RenderObjectWidget({ Key key }) : super(key: key);

  /// RenderObjectWidgets always inflate to a [RenderObjectElement] subclass.
  //RenderObjectWidgets 一直填充于一個 RenderObjectElement 的子類兜辞,創(chuàng)建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實(shí)例逸吵,去描述(表現(xiàn))RenderObjectWidget的配置信息缝裁。此方法不應(yīng)對render對象的子代執(zhí)行任何操作。
而是由可覆蓋的RenderObjectElement.mount方法調(diào)用處理,例如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調(diào)用mount()方法进苍,mount 方法實(shí)例化一個RenderObject觉啊,由RenderObject 對象繪制視圖。還是接著看SingleChildRenderObjectWidget類:

abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {

  const SingleChildRenderObjectWidget({ Key key, this.child }) : super(key: key);

  final Widget child;

  // 重寫 createElement杠人,返回 SingleChildRenderObjectElement 實(shí)例對象
  @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;
@override
void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  //創(chuàng)建_renderObject實(shí)例對象
  _renderObject = widget.createRenderObject(this);
  assert(() { _debugUpdateRenderObjectOwner(); return true; }());
  assert(_slot == newSlot);
  attachRenderObject(newSlot);
  _dirty = false;
}

由此知道蚀浆,當(dāng)調(diào)用mount掛載的時(shí)候,會調(diào)用createRenderObject生成_renderObject實(shí)例搜吧。而createRenderObject 方法我們可以在Opacity這個組件類里看到市俊,它返回了一個RenderOpacity的實(shí)例:

@override
RenderOpacity createRenderObject(BuildContext context) {
  return RenderOpacity(
    opacity: opacity,
    alwaysIncludeSemantics: alwaysIncludeSemantics,
  );
}

在RenderOpacity中,重寫paint() 方法滤奈,調(diào)用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);
  }
}

小結(jié)

  • 調(diào)用runApp(rootWidget)摆昧,將rootWidget傳給rootElement,做為rootElement的子節(jié)點(diǎn),生成Element樹,由Element樹生成Render樹

  • Widget:存放渲染內(nèi)容蜒程、視圖布局信息,widget的屬性最好都是immutable(一成不變的)

  • Element:存放上下文绅你,通過Element遍歷視圖樹,Element同時(shí)持有Widget和RenderObject
    RenderObject:根據(jù)Widget的布局屬性進(jìn)行l(wèi)ayout昭躺,paint Widget傳入的內(nèi)容

在第一次創(chuàng)建 Widget 的時(shí)候忌锯,會對應(yīng)創(chuàng)建一個 Element, 然后將該元素插入樹中领炫。如果之后 Widget 發(fā)生了變化偶垮,則將其與舊的 Widget 進(jìn)行比較,并且相應(yīng)地更新 Element帝洪。重要的是似舵,Element 被不會重建,只是更新而已葱峡。這個目前的我還沒看相關(guān)的源碼砚哗。。砰奕。

代碼

class ElementApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: OpacityHome(),theme: ThemeData.light(),);
  }
}


class OpacityHome extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("flutter繪制機(jī)制"),),
      body: Center(
        child: Opacity(opacity: 0.5,child: Container(
          width: 100,
          color: Colors.black87,
          alignment: Alignment.center,
          height: 100,
          child: Text("flutter",style: TextStyle(fontSize: 20,color: Colors.white),),
        ),),
      ),

    );
  }
}

效果圖

看到這雖然對flutter繪制有一定了解蛛芥,但是好多問題出現(xiàn)了:

  • 視圖的更新機(jī)制提鸟,更新的依據(jù)是什么樣?
  • BuildContext 又有啥作用仅淑?
  • Widget中的Key又有啥作用称勋?

如果覺得有什么錯誤,歡迎拍磚漓糙。。烘嘱。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昆禽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蝇庭,更是在濱河造成了極大的恐慌醉鳖,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哮内,死亡現(xiàn)場離奇詭異盗棵,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)北发,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門纹因,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人琳拨,你說我怎么就攤上這事瞭恰。” “怎么了狱庇?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵惊畏,是天一觀的道長。 經(jīng)常有香客問我密任,道長颜启,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任浪讳,我火速辦了婚禮缰盏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘淹遵。我一直安慰自己乳规,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布合呐。 她就那樣靜靜地躺著暮的,像睡著了一般。 火紅的嫁衣襯著肌膚如雪淌实。 梳的紋絲不亂的頭發(fā)上冻辩,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天猖腕,我揣著相機(jī)與錄音,去河邊找鬼恨闪。 笑死倘感,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的咙咽。 我是一名探鬼主播老玛,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼钧敞!你這毒婦竟也來了蜡豹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤溉苛,失蹤者是張志新(化名)和其女友劉穎镜廉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愚战,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡娇唯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寂玲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片塔插。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拓哟,靈堂內(nèi)的尸體忽然破棺而出佑淀,到底是詐尸還是另有隱情,我是刑警寧澤彰檬,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布伸刃,位于F島的核電站,受9級特大地震影響逢倍,放射性物質(zhì)發(fā)生泄漏捧颅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一较雕、第九天 我趴在偏房一處隱蔽的房頂上張望碉哑。 院中可真熱鬧,春花似錦亮蒋、人聲如沸扣典。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贮尖。三九已至,卻和暖如春趁怔,著一層夾襖步出監(jiān)牢的瞬間湿硝,已是汗流浹背薪前。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留关斜,地道東北人示括。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像痢畜,于是被迫代替她去往敵國和親垛膝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 背景 最近小組在嘗試使用一套自定義的DSL丁稀,通過動態(tài)模板下發(fā)吼拥,實(shí)現(xiàn)Flutter端的動態(tài)化模板渲染;本來以為只是D...
    沒想好像閱讀 534評論 0 7
  • 前言 前面兩篇Flutter框架分析的文章介紹了渲染流水線秉沼,window和框架的初始化桶雀。這篇文章繼續(xù)來理一下對Fl...
    HowHardCanItBe閱讀 6,509評論 1 30
  • 二月十日,起床時(shí)光線暗淡唬复,還是雨天吧矗积。到了十點(diǎn)多,叔叔發(fā)了條微信給我讓我過去——今天要去另一個親戚家拜年敞咧。在奶奶...
    舒游閱讀 165評論 0 0
  • 朋友圈進(jìn)入了婚戀季休建,好多我們以為會是結(jié)婚最晚的女神們反而成了圈里最早定下來的乍恐,還有很多是從初中高中堅(jiān)持到結(jié)婚的,私...
    原女閱讀 383評論 0 2
  • 楊柳輕吐綠芽眉测砂,新梅含羞醉春風(fēng)茵烈。在“三八”婦女節(jié)到來之際,棗莊市市中區(qū)永安鎮(zhèn)政府?dāng)y手區(qū)婦保院在轄區(qū)內(nèi)的龍子心中學(xué)開...
    王琴wq70閱讀 360評論 0 5