Flutter 完整开发实战详解自定义布局,移动开发框架2019
Posted m0_64604311
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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
,并混入 ContainerRenderObjectMixin
和 RenderBoxContainerDefaultsMixin
实现 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);
如下代码所示,接下来主要看 RenderCloudWidget
中override 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.parentData
的 offset
,来控制其位置。
最后通过 CloudWidget
加载我们的 RenderCloudWidget
即可, 当然完整代码还需要结合 FittedBox
与 RotatedBox
简化完成,具体可见 :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;
最后我们总结,实现自定义布局的流程就是,实现自定义 RenderBox
中 performLayout
child 的 offset
。
CustomMultiChildLayout
是 Flutter 为我们封装的简化自定义布局实现,它的内部同样是通过 MultiChildRenderObjectWidget
实现,但是它为我们封装了 RenderCustomMultiChildLayoutBox
和 MultiChildLayoutParentData
,并通过 MultiChildLayoutDelegate
暴露出需要自定义的地方。
以上是关于Flutter 完整开发实战详解自定义布局,移动开发框架2019的主要内容,如果未能解决你的问题,请参考以下文章
Flutter自定义MultiChildRenderObjectWidget实现圆环布局效果
Flutter自定义MultiChildRenderObjectWidget实现圆环布局效果
Android Flutter完整开发实战详解,一文搞懂Flutter框架
移动应用开发之路 03 Android Studio 6种布局介绍实战详解