一、本文主要是学习巩固一下自定义RenderObject这一块内容,用所了解到的知识实现一个圆环布局效果

二、效果图

.
根据自组件的数量,平均将所有的子组件摆放在矩形的内切圆上。

三、自定义Layout步骤

  1. 创建CircleLayoutWidget类继承MultiChildRenderObjectWidget
  2. 创建CircleLayoutData类继承ContainerBoxParentData
  3. 创建CircleLayoutRenderBox类继承RenderBox,同时混入ContainerRenderObjectMixinRenderBoxContainerDefaultsMixin

关于上面两个混入,在开头引入的文章链接中已经详细说明了

如下代码
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这块操作

  1. 需要混入上面说到的两个mixin
  2. 重写setupParentData函数为每一个child设置数据
  3. 重写performLayout函数进行布局大小设置、逻辑编写;同时需要对每一个child进行layout

    child.layout(constraints, parentUsesSize: true);

  4. 重写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)运算,这是为了让组件的中心在圆弧上同时将坐标原点移动到画布中心

本文所有源码下载

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐