前言
上回在Flutter自定義View以及響應式UI框架原理中講了Flutter的響應式UI框架原理以及如何自定義葉子節點LeafRenderObjectWidget,這次我們再來嘗試一下如何自定義一個可以包含多個子控制元件的父佈局。
首先呢,我們先給自己定一個目標:
- 所有child按角度平均分佈到一個圓環上, 以圓環最高點為起點, 順時針排列;
- 保證所有的child不能重疊;
- 以子View中最大的寬高為標準計算ViewGroup寬高, 即較小child的寬高也按照子View中最大的寬高計算;
效果如下:
具體實現
根據我們上一篇文章說明,自定義View需要同時構建Widget、Element、RenderObject三個層次,下面就依次說明下,同時我會與自定義葉子節點的對應步驟做同步比較。
Widget
自定義Widget我們是直接繼承了RenderObjectWidget,雖然系統給我們提供了LeafRenderObjectWidget以及MultiChildRenderObjectWidget,不過這兩個類很簡單,直接繼承RenderObjectWidget有利於我們瞭解其原理。
我們可以在這裡看到這兩個類的比較:SixStarWidget和RingWidgte。
它們兩個的主要區別在於:
- LeafRenderObjectWidget需要在
updateRenderObject
中處理RenderObject的更新,MultiChildRenderObjectWidget則不需要(原因在下面Element中介紹) - MultiChildRenderObjectWidget中除了其他自定義引數,需要包含一個
List<Widget> children
,此引數用於提供給Element使用。
Element
與Widget類似的,我們也是直接繼承了RenderObjectElement,而不是LeafRenderObjectElement和MultiChildRenderObjectElement。
兩個類的比較:SixStarElement和RingElement。
它們的區別是:
- MultiChildRenderObjectElement需要提供
insertChildRenderObject
、moveChildRenderObject
、removeChildRenderObject
、visitChildren
等方法; - 在
mount
以及update
方法中,MultiChildRenderObjectElement需要遍歷_children分別執行,而LeafRenderObjectElement只需要處理自身即可。
關於以上方法的具體作用請見RingElement內的註釋。
RenderObject
兩個類的比較:SixStarRenderObject和RingRenderObject。
在上一篇文章中,我按個人理解把SixStarRenderObject分為了四部分,它們在兩種RenderObject內的表現有所不同:
paint
以及其他屬性引數:類似;layout
和paint
,performLayout
和performResize
- SixStarRenderObject:
paint
繪製自身,performLayout
中賦值自身size即可; - RingRenderObject:
paint
除了繪製自身之外,還需要呼叫RenderBoxContainerDefaultsMixin#defaultPaint
方法處理children的繪製;performLayout
中除了賦值自身size之外,還需要遍歷children分別執行layout,然後處理偏移量,把各個child按既定規則放在具體位置;
- SixStarRenderObject:
isRepaintBoundary
,sizedByParent
:一致,即isRepaintBoundary
為true,sizedByParent
為falsehitTest
,hitTestSelf
,hitTestChildren
- SixStarRenderObject:需要支援點選時僅需要重寫
hitTestSelf
為true即可; - RingRenderObject:不僅需要重寫
hitTestSelf
為true,還需要在hitTestChildren
中呼叫RenderBoxContainerDefaultsMixin#defaultHitTestChildren
方法處理children的點選。
- SixStarRenderObject:需要支援點選時僅需要重寫
除此之外,在RingRenderObject中還需要重寫額外兩組方法:
- 計算固有大小相關方法
compute(Max/Min)Intrinsic(Height/Width)
:它們的作用是在layout方法未執行時根據給定寬高返回固定的高寬; 其實LeafRenderObject也存在這一系列方法,只不過作為葉子節點可以認為未經過layout計算時寬高都為0,但是對於ViewGroup我們需要考慮其child,所以需要重寫; setupParentData(RenderBox child)
方法:給child指定ParentData
,此引數用於傳遞父佈局所提供的資料,包括偏移量以及下一個節點指標等。
關於以上方法的具體作用請見RingRenderObject。
總結
自定義可以新增多個子View的MultiChildRenderObejctWidget以及葉子節點LeafRenderObejctWidget的流程基本一致,而且比較清晰,而且原始碼中大多提供了預設實現,如果只為了實現功能的話需要重寫的方法會更少,比如Element可以直接使用系統提供類,RenderObejct類也可以省略大部分方法。
附Demo地址:github.com/YouCii/Flut…