一、本文主要是学习巩固一下自定义RenderObject这一块内容,用所了解到的知识实现一个圆环布局效果
- 本篇文章主要参考了
恋猫de小郭
Flutter 完整开发实战详解(十六、详解自定义布局实战)文章,大家可以先看完这篇文章再来阅读本篇这样能更好的理解文中代码。 - 源码在文末。
二、效果图
.
根据自组件的数量,平均将所有的子组件摆放在矩形的内切圆上。
三、自定义Layout步骤
- 创建
CircleLayoutWidget
类继承MultiChildRenderObjectWidget
- 创建
CircleLayoutData
类继承ContainerBoxParentData
- 创建
CircleLayoutRenderBox
类继承RenderBox
,同时混入ContainerRenderObjectMixin
、RenderBoxContainerDefaultsMixin
关于上面两个混入,在开头引入的文章链接中已经详细说明了
如下代码
class CircleLayoutWidget extends MultiChildRenderObjectWidget {
///圆环布局的大小
final double size;
CircleLayoutWidget({
required this.size,
required List<Widget> children,
Key? key,
}) : super(key: key, children: children);
RenderObject createRenderObject(BuildContext context) {
return CircleLayoutRenderBox(size);
}
}
///每个child的数据
class CircleLayoutData extends ContainerBoxParentData<RenderBox> {}
四、这里重点讲一下自定义RenderBox
这块操作
- 需要混入上面说到的两个
mixin
- 重写
setupParentData
函数为每一个child
设置数据 - 重写
performLayout
函数进行布局大小设置、逻辑编写;同时需要对每一个child
进行layout
child.layout(constraints, parentUsesSize: true);
- 重写
paint
函数进行child
的绘制
class CircleLayoutRenderBox extends RenderBox
with
ContainerRenderObjectMixin<RenderBox, CircleLayoutData>,
RenderBoxContainerDefaultsMixin<RenderBox, CircleLayoutData> {
final double layoutSize;
CircleLayoutRenderBox(this.layoutSize);
///step 1
void setupParentData(RenderObject child) {
if (child.parentData is! CircleLayoutData) {
child.parentData = CircleLayoutData();
}
}
void performLayout() {
size = Size(layoutSize, layoutSize);
///省略部分代码...
}
void paint(PaintingContext context, Offset offset) {
defaultPaint(context, offset);
}
}
五、这里说下关于每一个child
在圆环上的坐标计算
通过画图来理解各个点位的关系,可以得到以下信息
- 半径r是画布大小的一半
r = layoutSize / 2
- 角a根据子组件数量进行计算
∠a = 2 * pi / childCount
- 有了上面俩个条件就可以通过三角函数计算出F点的坐标
F(x,y) = (sin(∠a) * r, cos(∠a) * r)
void performLayout() {
final double r = layoutSize / 2;
///起始角度0
double angle = 0;
int count = 0;
RenderBox? child = firstChild;
while (child != null) {
child.layout(constraints, parentUsesSize: true);
final CircleLayoutData parentData = child.parentData! as CircleLayoutData;
///计算当前child所在的角度
angle = count * 2 * pi / childCount;
parentData.offset = Offset(
sin(angle) * r - child.size.width / 2 + r,
cos(angle) * r - child.size.height / 2 + r,
);
///下一个child
child = parentData.nextSibling;
count++;
}
}
注意:最后还需要对F点进行(x,y) = (x - child.size.width / 2 + r ,y - child.size.height / 2 +r)
运算,这是为了让组件的中心在圆弧上同时将坐标原点移动到画布中心