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

Logos發表於2019-12-31

注意:為了讓分析更加簡單,和邏輯清晰,我們去掉了部分原始碼和註釋,只留下了主要的程式碼和邏輯。如果沒有看過上一篇文章,請點選下面的連結。 Flutter必須理解Widget、Element、RenderObject的關係(一)

Element概述

updateChild方法

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

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樹的概念,這個函式就是從樹中移除相關的資料。

  • 註釋1

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

分析到這裡我們先告一個小段落,現在終結一下Element。

RenderObject概述

RenderObject定義

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

An object in the render tree.

這個概念很簡單,大概的意思是渲染樹上的一個物件。從概念上得出每個RenderObject都掛在一個渲染樹上,我們看一下RenderObject的原始碼,下面是RenderObject的原始碼。

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的主要職責是繪製和佈局,也就是我們說的,這是一個真正的渲染物件,我們下面分析一下這個物件是怎麼佈局和渲染的。

如果我們想在螢幕上想畫一個紅色的正方形,兩個重要的問題需要解決,第一是,畫在哪裡?第二是,是怎麼畫?畫在哪裡就是佈局,怎麼畫就是渲染,下面我們對著兩個問題進行分析,先從佈局開始。

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 {
  
}
複製程式碼

Flutter必須理解Widget、Element、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從下到上。

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

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()這個函式,這個函式已經計算出紅色方塊的大小了。

其它推薦

參考

相關文章