Flutter必須理解Widget、Element、RenderObject的關係(二)

注意:爲了讓分析更加簡單,和邏輯清晰,咱們去掉了部分源碼和註釋,只留下了主要的代碼和邏輯。若是沒有看過上一篇文章,請點擊下面的連接。 Flutter必須理解Widget、Element、RenderObject的關係(一)git

Element概述

updateChild方法

接着上篇接着來,上面提到過這個這個方法比較重要,咱們將單獨拿一個章節講解,下面是updateChild()源碼。github

abstract class Element extends DiagnosticableTree implements BuildContext {
  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
      if (newWidget == null) {
        if (child != null)
          deactivateChild(child);//註釋1
        return null;
      }
      if (child != null) {
        if (child.widget == newWidget) {
          if (child.slot != newSlot)
            updateSlotForChild(child, newSlot);//註釋2
          return child;
        }
        if (Widget.canUpdate(child.widget, newWidget)) {
          if (child.slot != newSlot)
            updateSlotForChild(child, newSlot);
          child.update(newWidget);//註釋3
          return child;
        }
        deactivateChild(child);
      }
      return inflateWidget(newWidget, newSlot);//註釋4
    }  
}
複製代碼

這個方法的意思就更新Element配置的一個函數,具體是怎麼更新的呢,其實仍是比較簡單的,在前面的的文章中咱們說過關於Element樹的概念,這個函數就是從樹中移除相關的數據。ide

  • 註釋1函數

    deactivateChild()是把Element從Element樹上刪除,佈局

分析到這裏咱們先告一個小段落,如今終結一下Element。post

RenderObject概述

RenderObject定義

仍是先從概念上入手,下面是ReaderObject的概念。ui

An object in the render tree.this

這個概念很簡單,大概的意思是渲染樹上的一個對象。從概念上得出每一個RenderObject都掛在一個渲染樹上,咱們看一下RenderObject的源碼,下面是RenderObject的源碼。spa

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
  ParentData parentData;
  Constraints _constraints;
  void layout(Constraints constraints, { bool parentUsesSize = false }) {
    
  }
  void paint(PaintingContext context, Offset offset) { }
  
  void performLayout();
  void markNeedsPaint() {
  }
}
複製代碼

咱們共提供這些方法可用看出來,RenderObject的主要職責是繪製和佈局,也就是咱們說的,這是一個真正的渲染對象,咱們下面分析一下這個對象是怎麼佈局和渲染的。3d

若是咱們想在屏幕上想畫一個紅色的正方形,兩個重要的問題須要解決,第一是,畫在哪裏?第二是,是怎麼畫?畫在哪裏就是佈局,怎麼畫就是渲染,下面咱們對着兩個問題進行分析,先從佈局開始。

RenderObject佈局

咱們先從layout入手,這個方法是計算渲染對象的大小和佈局,這方法一般不要被在類覆蓋,若是你想重寫佈局,要重寫performLayout()。

abstract class RenderObject {
    void layout(Constraints constraints, { bool parentUsesSize = false }) {
      
      RenderObject relayoutBoundary;//註釋1
      if (!parentUsesSize || sizedByParent || constraints.isTight 
          || parent is! RenderObject) {//註釋2
        relayoutBoundary = this;//註釋3
      } else {
        final RenderObject parent = this.parent;
        relayoutBoundary = parent._relayoutBoundary;//註釋4
      }
       _relayoutBoundary = relayoutBoundary;
      if (sizedByParent) {
          performResize();//註釋5
      }
      performLayout();//註釋6
      markNeedsPaint();
    }
	}
複製代碼

上面就是layout方法,這方法有兩個參數,第一個參數constraints,是經過父類傳入的,也就是你們常說的約束從上到下。

  • 註釋1

    relayoutBoundary這變量比較重要,咱們打算用用個小結講解。

RelayoutBoundary

在註釋1處聲明瞭一個屬性relayoutBoundary,屬性叫作佈局邊界,這個這屬性是提升渲染效率的(由於在佈局和渲染的都會用到這屬性),咱們知道RenderObject在渲染樹中,如今若是一個葉子RenderObject對象發生佈局變化,那麼必定會致使這個葉子節點的父佈局重新佈局,這一定致使低效,那麼Flutter就用這個屬性防止父節點重新佈局,可是這個須要知足幾個條。

  • parentUsesSize

    layout的第二個參數,父控件的佈局是依賴子控件的佈局,默認值是false,也就是默認父控件不依賴子控件佈局。

  • sizedByParent

    子控件的大小徹底在父控件的約束條件下,也就是子控件在父控件的min和max之間。

  • constraints.isTight

    就是min等於max,這比較容易理解。

  • parent is! RenderObject

    這個條件很容易理解了,就是parent不是RenderObject。

上面的4個條件,若是一個成立就執行註釋3的代碼,註釋3的意思佈局邊界是本身,由於這個節點的佈局變化不會引發父節點的重新佈局。不然執行註釋4,把父節點的佈局邊界賦值給本身。

  • 註釋5

performResize()方法子類實現的方法,這個方法主要的功能是更新渲染對象的大小,固然這個這個方法被調用的條件是sizedByParent是true,也就是說子控件的大小徹底在父控件的約束條件下,執行這個方法。

  • 註釋6

performLayout()方法一樣的是須要子類去實現的,計算RenderObject的佈局。

下面咱們分析一下RenderConstrainedBox的performLayout方法,仍是先看一下RenderConstrainedBox的繼承關係。

class RenderConstrainedBox extends RenderProxyBox {
  
}
class RenderProxyBox extends RenderBox {
  
}
abstract class RenderBox extends RenderObject {
  
}
複製代碼

從上面的代碼中很明顯能看出來RenderConstrainedBox最終繼承RenderObject,咱們下面就看一下performLayout()方法的具體實現。

class RenderConstrainedBox extends RenderProxyBox {
	  @override
    void performLayout() {
      if (child != null) {
        child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true)
        size = child.size;
      } else {
        size = _additionalConstraints.enforce(constraints).constrain(Size.zero);
      }
    }
}
複製代碼

這個方法能看出來,當調用RenderObject的layout()方法的時候,會調用RenderConstrainedBox的performLayout(),這個方法又會調用child.layout()方法,直到佈局完成。約束是從父類傳到子類的,可是size是從子類給到父類的,用一張圖表示就清楚多了,這個就是你們常說的,constraints從上到下,size從下到上。

RenderObject渲染

在RenderObject中主要渲染的的函數是paint(),在RenderObject中沒有具體的實現,須要子類本身實現,下面是paint方法。

void pait(PaintingContext context, Offset offset) { }
複製代碼

上面的paint有兩個參數,先解釋兩個參數的意思,讓後天然就能明白這個函數的意思。

  • context

    Context是PaintingContext,這類繼承自ClipContext,在ClipContext中有一個Canvas,這樣好像就理解了,原來context是用來畫東西的畫布。

  • offset

    offset是Offset,就是屏幕座標上的一個位置。

這樣咱們應該能猜到這個paint()就是一個,將一個context,畫到一個屏幕位置上,這裏你們可能有一個疑問,好比咱們想畫一個紅色的正方形,如今只有畫布和畫到那個位置,尚未告訴我紅色方方塊的大小呢,怎麼畫?在上一小節中(RenderObject佈局),咱們講了performResize()這個函數,這個函數已經計算出紅色方塊的大小了。

其它推薦

參考

相關文章
相關標籤/搜索