Flutter 完整开发实战详解自定义布局,移动开发框架2019

/// 计算返回第一个 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 getChildrenAsList()

3、ContainerBoxParentData

ContainerBoxParentData 是 BoxParentData 的子类,主要是关联了 ContainerDefaultsMixin 和 BoxParentData ,BoxParentData 是 RenderBox 绘制时所需的位置类。

通过 ContainerBoxParentData ,我们可以将 RenderBox 需要的 BoxParentData 和上面的 ContainerParentDataMixin 组合起来,事实上我们得到的 children 双链表就是以 ParentData 的形式呈现出来的。

abstract class ContainerBoxParentData extends BoxParentData with ContainerParentDataMixin { }

4、MultiChildRenderObjectWidget

MultiChildRenderObjectWidget 的实现很简单 ,它仅仅只是继承了 RenderObjectWidget,然后提供了 children 数组,并创建了 MultiChildRenderObjectElement。

上面的 RenderObjectWidget 顾名思义,它是提供 RenderObject 的 Widget ,那有不存在 RenderObject 的 Widget 吗?

有的,比如我们常见的 StatefulWidget 、 StatelessWidget 、 Container 等,它们的 Element 都是 ComponentElement , ComponentElement 仅仅起到容器的作用,而它的 get renderObject 需要来自它的 child 。

5、MultiChildRenderObjectElement

前面的篇章我们说过 Element 是 BuildContext 的实现, 内部一般持有 Widget 、RenderObject 并作为二者沟通的桥梁,那么 MultiChildRenderObjectElement 就是我们自定义布局时的桥梁了, 如下代码所示,MultiChildRenderObjectElement 主要实现了如下接口,其主要功能是对内部 children 的 RenderObject ,实现了插入、移除、访问、更新等逻辑:

/// 下面三个方法都是利用 ContainerRenderObjectMixin 的 in

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

sert/move/remove 去操作

/// ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin

void insertChildRenderObject(RenderObject child, Element slot)

void moveChildRenderObject(RenderObject child, dynamic slot)

void removeChildRenderObject(RenderObject child)

/// visitChildren 是通过 Element 中的 ElementVisitor 去迭代的

/// 一般在 RenderObject get renderObject 会调用

void visitChildren(ElementVisitor visitor)

/// 添加忽略child _forgottenChildren.add(child);

void forgetChild(Element child)

/// 通过 inflateWidget , 把 children 中 List 对应的 List

void mount(Element parent, dynamic newSlot)

/// 通过 updateChildren 方法去更新得到 List

void update(MultiChildRenderObjectWidget newWidget)

所以 MultiChildRenderObjectElement 利用 ContainerRenderObjectMixin 最终将我们自定义的 RenderBox 和 Widget 关联起来。

6、自定义流程

上述主要描述了 MultiChildRenderObjectWidget 、 MultiChildRenderObjectElement 和其他三个辅助类ContainerRenderObjectMixin 、 RenderBoxContainerDefaultsMixin 和 ContainerBoxParentData 之间的关系。

了解几个关键类之后,我们看一般情况下,实现自定义布局的简化流程是:

1、自定义 ParentData 继承 ContainerBoxParentData 。

2、继承 RenderBox ,同时混入 ContainerRenderObjectMixin 和 RenderBoxContainerDefaultsMixin 实现自定义RenderObject 。

3、继承 MultiChildRenderObjectWidget,实现 createRenderObject 和 updateRenderObject 方法,关联我们自定义的 RenderBox。

4、override RenderBox 的 performLayout 和 setupParentData 方法,实现自定义布局。

当然我们可以利用官方的 CustomMultiChildLayout 实现自定义布局,这个后面也会讲到,现在让我们先从基础开始, 而上述流程中混入的 ContainerRenderObjectMixin 和 RenderBoxContainerDefaultsMixin ,在 RenderFlex 、RenderWrap 、RenderStack 等官方实现的布局里,也都会混入它们。

三、自定义布局


自定义布局就是在 performLayout 中实现的 child.layout 大小和 child.ParentData.offset 位置的赋值。

首先我们要实现类似如图效果,我们需要自定义 RenderCloudParentData 继承 ContainerBoxParentData ,用于记录宽高和内容区域 :

class RenderCloudParentData extends ContainerBoxParentData {

double width;

double height;

Rect get content => Rect.fromLTWH(

offset.dx,

offset.dy,

width,

height,

);

}

然后自定义 RenderCloudWidget 继承 RenderBox ,并混入 ContainerRenderObjectMixinRenderBoxContainerDefaultsMixin 实现 RenderBox 自定义的简化。

class RenderCloudWidget extends RenderBox

with

ContainerRenderObjectMixin<RenderBox, RenderCloudParentData>,

RenderBoxContainerDefaultsMixin<RenderBox, RenderCloudParentData> {

RenderCloudWidget({

List children,

Overflow overflow = Overflow.visible,

double ratio,

}) : _ratio = ratio,

_overflow = overflow {

///添加所有 child

addAll(children);

}

如下代码所示,接下来主要看 RenderCloudWidgetoverride performLayout 中的实现,这里我们只放关键代码:

  • 1、我们首先拿到 ContainerRenderObjectMixin 链表中的 firstChild ,然后从头到位读取整个链表。

  • 2、对于每个 child 首先通过 child.layout 设置他们的大小,然后记录下大小之后。

  • 3、以容器控件的中心为起点,从内到外设置布局,这是设置的时候,需要通过记录的 Rect 判断是否会重复,每次布局都需要计算位置,直到当前 child 不在重复区域内。

  • 4、得到最终布局内大小,然后设置整体居中。

///设置为我们的数据

@override

void setupParentData(RenderBox child) {

if (child.parentData is! RenderCloudParentData)

child.parentData = RenderCloudParentData();

}

@override

void performLayout() {

///默认不需要裁剪

_needClip = false;

///没有 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;

child.layout(constraints, parentUsesSize: true);

var childSize = child.size;

///记录大小

childParentData.width = childSize.width;

childParentData.height = childSize.height;

do {

///设置 xy 轴的比例

var rX = ratio >= 1 ? ratio : 1.0;

var rY = ratio <= 1 ? ratio : 1.0;

///调整位置

var step = 0.02 * _mathPi;

var rotation = 0.0;

var angle = curIndex * step;

var angleRadius = 5 + 5 * angle;

var x = rX * angleRadius * math.cos(angle + rotation);

var y = rY * angleRadius * math.sin(angle + rotation);

var position = Offset(x, y);

///计算得到绝对偏移

var childOffset = position - Alignment.center.alongSize(childSize);

++curIndex;

///设置为遏制

childParentData.offset = childOffset;

///判处是否交叠

} while (overlaps(childParentData));

///记录区域

previousChildRect = childParentData.content;

recordRect = recordRect.expandToInclude(previousChildRect);

///下一个

child = childParentData.nextSibling;

}

///调整布局大小

size = constraints

.tighten(

height: recordRect.height,

width: recordRect.width,

)

.smallest;

///居中

var contentCenter = size.center(Offset.zero);

var recordRectCenter = recordRect.center;

var transCenter = contentCenter - recordRectCenter;

child = firstChild;

while (child != null) {

final RenderCloudParentData childParentData = child.parentData;

childParentData.offset += transCenter;

child = childParentData.nextSibling;

}

///超过了嘛?

_needClip =

size.width < recordRect.width || size.height < recordRect.height;

}

其实看完代码可以发现,关键就在于你怎么设置 child.parentDataoffset ,来控制其位置。

最后通过 CloudWidget 加载我们的 RenderCloudWidget 即可, 当然完整代码还需要结合 FittedBoxRotatedBox 简化完成,具体可见 :GSYFlutterDemo

class CloudWidget extends MultiChildRenderObjectWidget {

final Overflow overflow;

final double ratio;

CloudWidget({

Key key,

this.ratio = 1,

this.overflow = Overflow.clip,

List children = const [],

}) : super(key: key, children: children);

@override

RenderObject createRenderObject(BuildContext context) {

return RenderCloudWidget(

ratio: ratio,

overflow: overflow,

);

}

@override

void updateRenderObject(

BuildContext context, RenderCloudWidget renderObject) {

renderObject

…ratio = ratio

…overflow = overflow;

}

}

最后我们总结,实现自定义布局的流程就是,实现自定义 RenderBoxperformLayout child 的 offset

四、CustomMultiChildLayout


CustomMultiChildLayout 是 Flutter 为我们封装的简化自定义布局实现,它的内部同样是通过 MultiChildRenderObjectWidget 实现,但是它为我们封装了 RenderCustomMultiChildLayoutBoxMultiChildLayoutParentData ,并通过 MultiChildLayoutDelegate 暴露出需要自定义的地方。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>