Flutter自定義View(二)—— MultiChildRenderObejctWidget

YouCii發表於2020-05-31

前言

上回在Flutter自定義View以及響應式UI框架原理中講了Flutter的響應式UI框架原理以及如何自定義葉子節點LeafRenderObjectWidget,這次我們再來嘗試一下如何自定義一個可以包含多個子控制元件的父佈局。

首先呢,我們先給自己定一個目標:

  1. 所有child按角度平均分佈到一個圓環上, 以圓環最高點為起點, 順時針排列;
  2. 保證所有的child不能重疊;
  3. 以子View中最大的寬高為標準計算ViewGroup寬高, 即較小child的寬高也按照子View中最大的寬高計算;

效果如下:

Flutter自定義View(二)—— MultiChildRenderObejctWidget

具體實現

根據我們上一篇文章說明,自定義View需要同時構建Widget、Element、RenderObject三個層次,下面就依次說明下,同時我會與自定義葉子節點的對應步驟做同步比較。

Widget

自定義Widget我們是直接繼承了RenderObjectWidget,雖然系統給我們提供了LeafRenderObjectWidget以及MultiChildRenderObjectWidget,不過這兩個類很簡單,直接繼承RenderObjectWidget有利於我們瞭解其原理。

我們可以在這裡看到這兩個類的比較:SixStarWidgetRingWidgte

它們兩個的主要區別在於:

  1. LeafRenderObjectWidget需要在updateRenderObject中處理RenderObject的更新,MultiChildRenderObjectWidget則不需要(原因在下面Element中介紹)
  2. MultiChildRenderObjectWidget中除了其他自定義引數,需要包含一個List<Widget> children,此引數用於提供給Element使用。

Element

與Widget類似的,我們也是直接繼承了RenderObjectElement,而不是LeafRenderObjectElement和MultiChildRenderObjectElement。

兩個類的比較:SixStarElementRingElement

它們的區別是:

  1. MultiChildRenderObjectElement需要提供insertChildRenderObjectmoveChildRenderObjectremoveChildRenderObjectvisitChildren等方法;
  2. mount以及update方法中,MultiChildRenderObjectElement需要遍歷_children分別執行,而LeafRenderObjectElement只需要處理自身即可。

關於以上方法的具體作用請見RingElement內的註釋。

RenderObject

兩個類的比較:SixStarRenderObjectRingRenderObject

在上一篇文章中,我按個人理解把SixStarRenderObject分為了四部分,它們在兩種RenderObject內的表現有所不同:

  1. paint以及其他屬性引數:類似;
  2. layoutpaint, performLayoutperformResize
    • SixStarRenderObject:paint繪製自身,performLayout中賦值自身size即可;
    • RingRenderObject:paint除了繪製自身之外,還需要呼叫RenderBoxContainerDefaultsMixin#defaultPaint方法處理children的繪製;performLayout中除了賦值自身size之外,還需要遍歷children分別執行layout,然後處理偏移量,把各個child按既定規則放在具體位置;
  3. isRepaintBoundary, sizedByParent:一致,即isRepaintBoundary為true,sizedByParent為false
  4. hitTest, hitTestSelf, hitTestChildren
    • SixStarRenderObject:需要支援點選時僅需要重寫hitTestSelf為true即可;
    • RingRenderObject:不僅需要重寫hitTestSelf為true,還需要在hitTestChildren中呼叫RenderBoxContainerDefaultsMixin#defaultHitTestChildren方法處理children的點選。

除此之外,在RingRenderObject中還需要重寫額外兩組方法:

  1. 計算固有大小相關方法compute(Max/Min)Intrinsic(Height/Width):它們的作用是在layout方法未執行時根據給定寬高返回固定的高寬; 其實LeafRenderObject也存在這一系列方法,只不過作為葉子節點可以認為未經過layout計算時寬高都為0,但是對於ViewGroup我們需要考慮其child,所以需要重寫;
  2. setupParentData(RenderBox child)方法:給child指定ParentData,此引數用於傳遞父佈局所提供的資料,包括偏移量以及下一個節點指標等。

關於以上方法的具體作用請見RingRenderObject

總結

自定義可以新增多個子View的MultiChildRenderObejctWidget以及葉子節點LeafRenderObejctWidget的流程基本一致,而且比較清晰,而且原始碼中大多提供了預設實現,如果只為了實現功能的話需要重寫的方法會更少,比如Element可以直接使用系統提供類,RenderObejct類也可以省略大部分方法。

附Demo地址:github.com/YouCii/Flut…

相關文章