本文最后更新于 798 天前
一、初步使用-和SingleChildRenderObjectWidget类似
-
- class CusRenderBox extends SingleChildRenderObjectWidget/MultiChildRenderObjectWidget
-
- 不同点:
// 可以进行内部绘制
// class RenderCusRenderBox extends RenderBox with RenderObjectWithChildMixin{
class RenderCusRenderBox extends RenderProxyBox{}
// 可以进行内部绘制-需要额外设置ContainerBoxParentData以及多个孩子的布局
// CustomMultiChildLayout 需要将 绘制Widget Child传进来 内部不能绘制
class RenderCloudWidget extends RenderBox
with ContainerRenderObjectMixin<RenderBox, RenderCloudParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, RenderCloudParentData> {}
1.1 ContainerRenderObjectMixin
主要是维护提供了一个双链表的 children RenderObject 。
通过在 RenderBox 里混入 ContainerRenderObjectMixin , 我们就可以得到一个双链表的 children ,
方便在我们布局时,可以正向或者反向去获取和管理 RenderObject
1.2 RenderBoxContainerDefaultsMixin
RenderBoxContainerDefaultsMixin 主要是对 ContainerRenderObjectMixin 的拓展,是对 ContainerRenderObjectMixin 内的 children 提供常用的默认行为和管理
/// 计算返回第一个 child 的基线 ,常用于 child 的位置顺序有关
double defaultComputeDistanceToFirstActualBaseline(TextBaseline baseline)
/// 计算返回所有 child 中最小的基线,常用于 child 的位置顺序无关
double defaultComputeDistanceToHighestActualBaseline(TextBaseline baseline)
/// 触摸碰撞测试
bool defaultHitTestChildren(BoxHitTestResult result, { Offset position })
/// 默认绘制
void defaultPaint(PaintingContext context, Offset offset)
/// 以数组方式返回 child 链表
List<ChildType> getChildrenAsList()
1.3 ContainerBoxParentData
ContainerBoxParentData 是 BoxParentData 的子类,
主要是关联了 ContainerDefaultsMixin 和 BoxParentData ,BoxParentData 是 RenderBox 绘制时所需的位置类。
通过 ContainerBoxParentData ,我们可以将 RenderBox 需要的 BoxParentData 和上面的 ContainerParentDataMixin 组合起来,
事实上我们得到的 children 双链表就是以 ParentData 的形式呈现出来的
1.4 实现步骤
- 1、自定义 ParentData 继承 ContainerBoxParentData 。
- 2、继承 RenderBox ,同时混入 ContainerRenderObjectMixin 和 RenderBoxContainerDefaultsMixin 实现自定义RenderObject 。
- 3、继承 MultiChildRenderObjectWidget,实现 createRenderObject 和 updateRenderObject 方法,关联我们自定义的 RenderBox。
- 4、override RenderBox 的 performLayout 和 setupParentData 方法,实现自定义布局
二、简化-直接使用CustomMultiChildLayout
CustomMultiChildLayout 是 Flutter 为我们封装的简化自定义布局实现,它的内部同样是通过 MultiChildRenderObjectWidget 实现,但是它为我们封装了 RenderCustomMultiChildLayoutBox 和 MultiChildLayoutParentData ,并通过 MultiChildLayoutDelegate 暴露出需要自定义的地方
只需要继承 MultiChildLayoutDelegate ,并实现如下方法即可
void performLayout(Size size);
bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate);
参考
Container(
color: Colors.black54,
child: CustomMultiChildLayout(
delegate: CircleLayoutDelegate(
["a","b","c","d","e"],
center: Offset(200,500)
),
children: [
LayoutId(
id: "a",
child: Text("a"),
),
LayoutId(
id: "b",
child: Text("b"),
),
LayoutId(
id: "c",
child: Text("a"),
),
LayoutId(
id: "d",
child: Text("d"),
),
LayoutId(
id: "e",
child: Text("e"),
),
],
)
)
///自定义实现圆形布局
class CircleLayoutDelegate extends MultiChildLayoutDelegate {
final List<String> customLayoutId;
final Offset center;
Size? childSize;
CircleLayoutDelegate(this.customLayoutId,
{this.center = Offset.zero, this.childSize});
@override
void performLayout(Size size) {
for (var item in customLayoutId) {
if (hasChild(item)) {
double r = 100;
int index = int.parse(item);
double step = 360 / customLayoutId.length;
double hd = (2 * math.pi / 360) * step * index;
var x = center.dx + math.sin(hd) * r;
var y = center.dy - math.cos(hd) * r;
childSize ??= Size(size.width / customLayoutId.length,
size.height / customLayoutId.length);
///设置 child 大小
layoutChild(item, BoxConstraints.loose(childSize!));
final double centerX = childSize!.width / 2.0;
final double centerY = childSize!.height / 2.0;
var result = new Offset(x - centerX, y - centerY);
///设置 child 位置
positionChild(item, result);
}
}
}
@override
bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => false;
}
参考
import 'dart:math' as math;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
///CloudWidget RenderBox
///默认都会 mixins ContainerRenderObjectMixin 和 RenderBoxContainerDefaultsMixin
class RenderCloudWidget extends RenderBox
with
ContainerRenderObjectMixin<RenderBox, RenderCloudParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, RenderCloudParentData> {
RenderCloudWidget({
List<RenderBox> children,
}) {
addAll(children);
}
///是否重复区域了
bool overlaps(RenderCloudParentData data) {
Rect rect = data.content;
RenderBox child = data.previousSibling;
if (child == null) {
return false;
}
do {
RenderCloudParentData childParentData = child.parentData as RenderCloudParentData;
if (rect.overlaps(childParentData.content)) {
return true;
}
child = childParentData.previousSibling;
} while (child != null);
return false;
}
///设置为我们的数据
@override
void setupParentData(RenderBox child) {
if (child.parentData is! RenderCloudParentData)
child.parentData = RenderCloudParentData();
}
@override
void performLayout() {
///没有 childCount 不玩
if (childCount == 0) {
size = constraints.smallest;
return;
}
///初始化区域
var recordRect = Rect.zero;
var previousChildRect = Rect.zero;
RenderBox child = firstChild;
while (child != null) {
var curIndex = -1;
///提出数据
final RenderCloudParentData childParentData = child.parentData as RenderCloudParentData;
child.layout(constraints, parentUsesSize: true);
var childSize = child.size;
///记录大小
childParentData.width = childSize.width;
childParentData.height = childSize.height;
do {
++curIndex;
///设置为遏制
childParentData.offset = Offset(0, curIndex * 8.toDouble());
///判处是否交叠
} while (overlaps(childParentData));
///记录区域
previousChildRect = childParentData.content;
recordRect = recordRect.expandToInclude(previousChildRect);
///下一个
child = childParentData.nextSibling;
}
///调整布局大小
size = constraints
.tighten(
height: recordRect.height,
width: recordRect.width,
)
.smallest;
}
///设置绘制默认
@override
void paint(PaintingContext context, Offset offset) {
defaultPaint(context, offset);
}
@override
double computeDistanceToActualBaseline(TextBaseline baseline) {
return defaultComputeDistanceToHighestActualBaseline(baseline);
}
@override
bool hitTestChildren(HitTestResult result, { Offset position}) {
return defaultHitTestChildren(result as BoxHitTestResult, position: position);
}
}
/// CloudParentData
class RenderCloudParentData extends ContainerBoxParentData<RenderBox> {
double width;
double height;
Rect get content => Rect.fromLTWH(
offset.dx,
offset.dy,
width,
height,
);
}
class CloudWidget extends MultiChildRenderObjectWidget {
CloudWidget({
Key key,
List<Widget> children = const <Widget>[],
}) : super(key: key, children: children);
@override
RenderObject createRenderObject(BuildContext context) {
return RenderCloudWidget();
}
}