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 ,并混入 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 暴露出需要自定义的地方。

以上是关于Flutter 完整开发实战详解自定义布局,移动开发框架2019的主要内容,如果未能解决你的问题,请参考以下文章

Flutter自定义MultiChildRenderObjectWidget实现圆环布局效果

Flutter自定义MultiChildRenderObjectWidget实现圆环布局效果

Android Flutter完整开发实战详解,一文搞懂Flutter框架

移动应用开发之路 03 Android Studio 6种布局介绍实战详解

Flutter完整开发实战详解(一Dart语言和Flutter基础)

最新Flutter完整开发实战详解,安卓程序员快存下吧,很难找全的~