前面讲了很多理论知识,现在我们可以开始实战,去自定义一个自己的Widget。
自定义Widget布局肯定需要三个部分:Widget、Element和RenderObject,而核心在于自定义RenderObject。对于单child来说我们可以继承SingleChildRenderObjectWidget
实现,多child Widget一般通过继承MultiChildRenderObjectWidget
和RenderBox
实现,而MultiChildRenderObjectElement
默认不需要自定义,它会负责将MultiChildRenderObjectWidget
和RenderBox
进行关联。
单child Widget自定义
在flutter提供的组件中,常见的 SingleChildRenderObjectWidget 有 Clip、Opacity、Padding、Align等。
1 | class MySingleChildWidget extends SingleChildRenderObjectWidget { |
自定义单child Widget时,需要继承SingleChildRenderObjectWidget并实现createRenderObject方法,SingleChildRenderObjectWidget实现了默认的 createElement方法,所以一般情况下不需要我们自定义。
1 | class RenderMySingleChildWidget extends RenderBox with RenderObjectWithChildMixin<RenderBox> { |
上面是我们自定义的renderObject,主要是职责是计算出空间自身最大、最小宽高,通过 performLayout 结合得到child大小返回Size,最后通过paint绘制。RenderObjectWithChildMixin
是flutter提供的mixin,给只有一个child的RenderObject使用,提供child属性以及一些child的相关操作。
在上面代码中,如果有child,我们就直接paint
child(context.paintChild
),否则我们就自己画一个矩形。所有绘画的逻辑都在paint
中。performLayout
负责child的布局,通过size属性的设置大小,对parentData.offset
的设置来控制child的位置。由此可看出,parent的size会根据child的size来计算,而child的位置完全由parent决定,并且parent会将constriain传递给child,由此child决定自己的size。
多child Widget自定义
多child Widget自定义需要继承MultiChildRenderObjectWidget
,比如我们常用的Row
,它的RenderFlex
在布局时需要计算各个child的大小并排布,之后决定自身的宽高是充满parent还是根据child进行调整。
在使用MultiChildRenderObjectWidget
时,有几个关键的辅助类
- ContainerRenderObjectMixin
是一个mixin类,主要作用是维护和提供一个双链表的children,通过在RenderBox里混入ContainerRenderObjectMixin,就可以得到一个双链表额children,方便在布局时可以正向或反向获取和管理children。
- RenderBoxContainerDefaultsMixin
对上述ContainerRenderObjectMixin进行拓展,对ContainerRenderObjectMixin内的children提供常用的默认行为和管理
1 | // 返回第一个child的基线,与child位置顺序有关 |
- ContainerBoxParentData
BoxParentData的子类,主要关联了上述RenderBoxContainerDefaultsMixin和BoxParentData,一起提供RenderBox需要的BoxParentData。
实现一个自定义Widget
实现自定义布局的主要流程为:
1 自定义自己的ParentData继承ContainerBoxParentData
1 | class MyParentData extends ContainerBoxParentData<RenderBox> { |
2 继承RenderBox,同时混入ContainerRenderObjectMixin、RenderBoxContainerDefaultsMixin
1 | class RenderMyMultiChildWidget extends RenderBox with ContainerRenderObjectMixin<RenderBox, MyParentData>, RenderBoxContainerDefaultsMixin<RenderBox, MyParentData> { |
3 继承MultiChildRenderObjectWidget,实现createRenderObject和updateRenderObject方法,关联我们自定义的RenderBox和更新状态信息
1 | class MyMultiChildWidget extends MultiChildRenderObjectWidget { |
4 override RenderBox的performLayout和setupParentData等方法,实现自定义布局。
1 |
|
上面代码的关键就在于怎么设置child.parentData的offset来控制位置。
这就是自定义一个Widget的基本流程。