开始

在Android中我们要实现一个布局需要继承ViewGroup, 重写其中的onLayoutonMeasure方法. 其中onLayout负责给子控件设置布局区域, onMeaseure度量子控件大小和自身大小. 今天我们就研究下Flutter是如何实现布局的.

Flutter布局

首先我们挑选一个Flutter控件去看源码, 我们就选Stack, 因为它足够简单. 从表象上讲它只要重叠摆放一组子控件即可. 先看下Stack的源码:

class Stack extends MultiChildRenderObjectWidget {Stack({Key key,this.alignment: AlignmentDirectional.topStart,this.textDirection,this.fit: StackFit.loose,this.overflow: Overflow.clip,List<Widget> children: const <Widget>[],}) : super(key: key, children: children);final AlignmentGeometry alignment;final StackFit fit;final Overflow overflow;@overrideRenderStack createRenderObject(BuildContext context) {return new RenderStack(alignment: alignment,textDirection: textDirection ?? Directionality.of(context),fit: fit,overflow: overflow,);}@overridevoid updateRenderObject(BuildContext context, RenderStack renderObject) {renderObject..alignment = alignment..textDirection = textDirection ?? Directionality.of(context)..fit = fit..overflow = overflow;}@overridevoid debugFillProperties(DiagnosticPropertiesBuilder properties) {super.debugFillProperties(properties);properties.add(new DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));properties.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));properties.add(new EnumProperty<StackFit>('fit', fit));properties.add(new EnumProperty<Overflow>('overflow', overflow));}
}

Stack继承自MultiChildRenderObjectWidget, 重写了createRenderObject其返回了一个RenderStack对象, 实际的工作者. 而updateRenderObject则只是修改RenderStack对象的属性. debugFillProperties方法则是填充该类属性的参数值到DiagnosticPropertiesBuilder中.

我们看看Flex, 也是如此, 重写了createRenderObject其返回了一个RenderFlex对象, 实际的工作者. 而updateRenderObject则只是修改RenderFlex对象的属性.

所以我们接下来看看RenderStack, 精简代码如下:

class RenderStack extends RenderBoxwith ContainerRenderObjectMixin<RenderBox, StackParentData>,RenderBoxContainerDefaultsMixin<RenderBox, StackParentData> {RenderStack({List<RenderBox> children,AlignmentGeometry alignment: AlignmentDirectional.topStart,TextDirection textDirection,StackFit fit: StackFit.loose,Overflow overflow: Overflow.clip,}) : assert(alignment != null),assert(fit != null),assert(overflow != null),_alignment = alignment,_textDirection = textDirection,_fit = fit,_overflow = overflow {addAll(children);}bool _hasVisualOverflow = false;@overridevoid performLayout() {_resolve();assert(_resolvedAlignment != null);_hasVisualOverflow = false;bool hasNonPositionedChildren = false;if (childCount == 0) {size = constraints.biggest;assert(size.isFinite);return;}double width = constraints.minWidth;double height = constraints.minHeight;BoxConstraints nonPositionedConstraints;assert(fit != null);switch (fit) {case StackFit.loose:nonPositionedConstraints = constraints.loosen();break;case StackFit.expand:nonPositionedConstraints = new BoxConstraints.tight(constraints.biggest);break;case StackFit.passthrough:nonPositionedConstraints = constraints;break;}assert(nonPositionedConstraints != null);RenderBox child = firstChild;while (child != null) {final StackParentData childParentData = child.parentData;if (!childParentData.isPositioned) {hasNonPositionedChildren = true;child.layout(nonPositionedConstraints, parentUsesSize: true);final Size childSize = child.size;width = math.max(width, childSize.width);height = math.max(height, childSize.height);}child = childParentData.nextSibling;}if (hasNonPositionedChildren) {size = new Size(width, height);assert(size.width == constraints.constrainWidth(width));assert(size.height == constraints.constrainHeight(height));} else {size = constraints.biggest;}assert(size.isFinite);child = firstChild;while (child != null) {final StackParentData childParentData = child.parentData;if (!childParentData.isPositioned) {childParentData.offset = _resolvedAlignment.alongOffset(size - child.size);} else {BoxConstraints childConstraints = const BoxConstraints();if (childParentData.left != null && childParentData.right != null)childConstraints = childConstraints.tighten(width: size.width - childParentData.right - childParentData.left);else if (childParentData.width != null)childConstraints = childConstraints.tighten(width: childParentData.width);if (childParentData.top != null && childParentData.bottom != null)childConstraints = childConstraints.tighten(height: size.height - childParentData.bottom - childParentData.top);else if (childParentData.height != null)childConstraints = childConstraints.tighten(height: childParentData.height);child.layout(childConstraints, parentUsesSize: true);double x;if (childParentData.left != null) {x = childParentData.left;} else if (childParentData.right != null) {x = size.width - childParentData.right - child.size.width;} else {x = _resolvedAlignment.alongOffset(size - child.size).dx;}if (x < 0.0 || x + child.size.width > size.width)_hasVisualOverflow = true;double y;if (childParentData.top != null) {y = childParentData.top;} else if (childParentData.bottom != null) {y = size.height - childParentData.bottom - child.size.height;} else {y = _resolvedAlignment.alongOffset(size - child.size).dy;}if (y < 0.0 || y + child.size.height > size.height)_hasVisualOverflow = true;childParentData.offset = new Offset(x, y);}assert(child.parentData == childParentData);child = childParentData.nextSibling;}}@protectedvoid paintStack(PaintingContext context, Offset offset) {defaultPaint(context, offset);}@overridevoid paint(PaintingContext context, Offset offset) {if (_overflow == Overflow.clip && _hasVisualOverflow) {context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintStack);} else {paintStack(context, offset);}}
}

可以看出RenderStack接收了所有传递给Stack的参数, 毕竟RenderStack才是实际干活的^^. performLayout负责了所有布局相关的工作. performLayout首先分析StackFit参数, 该参数有3个值:

  • StackFit.loose 按最小的来.
  • StackFit.expand 按最大的来.
  • StackFit.passthrough Stack上层为->Expanded->Row, 横向尽量大, 纵向尽量小.

得出BoxConstraints. 然后遍历所有子控件, 如果不是Positioned类型子控件, 则将BoxConstraints传给子控件让它根据父控件大小自己内部布局. 并且记录下所有子控件结合RenderStack自生大小得出的最大高度和宽度. 将其设置为当前控件大小.

接着再继续从头遍历子控件, 如果不是Positioned类型子控件, 根据alignment参数, 设置子控件在父控件中的偏移量, 比如Stack设置了居中, 上面计算出宽100, 高200, 而子控件宽30, 高30, 那么子控件需要偏移x=35, y=85. 如果是Positioned类型的子控件, 先将RenderStacksize大小, 减去Positioned属性里的大小. 再来计算便宜量.

这个里面有_hasVisualOverflow变量, 如果内容超出RenderStack大小, 其值为true. 也就是我们写布局时, 内容超过范围了, 报出来一个色块提示, 就是如此得出的.
_overflow属性则指定了子控件的绘制区域是否能超过父控件, 跟Android中的clipChildren属性很像.

另外我们再分析下IndexedStack, 该控件一次只能显示一个子控件. 其实际差异在RenderIndexedStack

class RenderIndexedStack extends RenderStack {...@overridebool hitTestChildren(HitTestResult result, { @required Offset position }) {if (firstChild == null || index == null)return false;assert(position != null);final RenderBox child = _childAtIndex();final StackParentData childParentData = child.parentData;return child.hitTest(result, position: position - childParentData.offset);}@overridevoid paintStack(PaintingContext context, Offset offset) {if (firstChild == null || index == null)return;final RenderBox child = _childAtIndex();final StackParentData childParentData = child.parentData;context.paintChild(child, childParentData.offset + offset);}...
}

重写了RenderStackpaintStackhitTestChildren方法, 只绘制选中的子控件, 和接收事件.

总结

实现一个自定义布局, 我们需要先继承MultiChildRenderObjectWidget, 然后重写createRenderObjectupdateRenderObject方法, 前者返回我们自定义的RenderBox的对象. 后者更新想要传递的属性. 然后需要我们继承RenderBox, 来扩展我们想要的功能特性.

转载于:https://www.cnblogs.com/zhujiabin/p/10248009.html

Flutter自定义布局套路相关推荐

  1. flutter自定义单元格_使用自定义大小的单元格制作复杂的UICollectionView布局(第1部分)

    flutter自定义单元格 Recently I built a screen with a pretty complex layout containing self sizing cells. I ...

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

    一.本文主要是学习巩固一下自定义RenderObject这一块内容,用所了解到的知识实现一个圆环布局效果 本篇文章主要参考了恋猫de小郭Flutter 完整开发实战详解(十六.详解自定义布局实战)文章 ...

  3. element布局容器大小_Flutter完整开发实战详解(十六、详解自定义布局实战)

    本篇将解析 Flutter 中自定义布局的原理,并带你深入实战自定义布局的流程,利用两种自定义布局的实现方式,完成如下图所示的界面效果,看完这一篇你将可以更轻松的对 Flutter 为所欲为. 文章汇 ...

  4. Flutter进阶—布局方法演示

    Flutter的布局机制的核心是控件.在Flutter中,几乎所有的东西都是一个控件,甚至布局模型都是控件.您在Flutter应用程序中看到的图像.图标和文本都是控件.但是您看不到的东西也是控件,例如 ...

  5. Flutter 自定义导航

    上篇文章我们讲到了自定义底部分栏控制器的使用,在开发过程中我们100%会使用到导航,当然Flutter也为我们提供了导航控件,但是自带的往往没有没有那么灵活,所以这个时候就需要我们自定义一个导航,下面 ...

  6. flutter自定义分页器

    使用flutter自定义一个分页器 最近在写flutter项目,项目刚好需要一个分页器,对数据进行分页处理,一开始想要在网上找有没有已经写好的插件,搜寻一会后还是想着自己写一个. 先看看效果图 思路 ...

  7. flutter 自定义绘制_自定义可绘制

    flutter 自定义绘制 I love our new designs! Recently I've been working on user interactions. One of them i ...

  8. ViewGroup1——自定义布局

    平时开发时,系统提供的几个布局基本就能满足我们的需求了.如果系统提供的布局无法满足需求,我们可以扩展ViewGroup类来实现自定义布局控件.先看下ViewGroup的继承图 由上图可知,ViewGr ...

  9. UICollectionView自定义布局(二)

    这是UICollectionView自定义布局的第二篇,实现类似UltravisualApp的视差效果,同样这篇文章的教程来自Ray家的Swift Expanding Cells in iOS Col ...

  10. CSS 7:网页布局(传统布局,flex布局,布局套路)

    传统布局 一栏.两栏.三栏布局 一栏布局 特点:页面内容居中,宽度固定 实现方式: 定宽 + 水平居中 width: 1000px; //或 max-width: 1000px; margin-lef ...

最新文章

  1. 卷积神经网络(CNN)实现CIFAR100类别分类
  2. 解决 Virtual PC 在 XP PAE模式下无法运行的问题
  3. linux到不了启动界面,Linux 界面不能启动的解决
  4. 《数据库SQL实战》获取所有非manager的员工emp_no
  5. java字符串包ascii 方法amp;#39_用 Java 生成 ASCII 字符画
  6. 机器学习中倒三角符号_机器学习的三角误差
  7. 旧金山运输系统攻击者威胁将公布消费者和职工个人数据
  8. js 拉勾网效果_借助JShaman,建立自己的JS代码混淆平台
  9. Bottlerocket:一套专用型容器操作系统
  10. 13. 为什么我们会需要 Pod?
  11. rocketmq 顺序消费_RocketMQ核心概念扫盲
  12. 02 Python元组 字典 数据类型 if while for 迭代
  13. 研究城市空间结构的入门级文献及书籍推荐(待更新)
  14. 脑机接口的商业化道路,还要走多远多长?
  15. Oracle Certified
  16. centos修改镜像源
  17. ad18常用快捷键可以修改吗_AD18的常用操作及快捷键
  18. 《正见——佛陀的证悟》读后感
  19. 七款网工在线画拓扑工具
  20. 【入门】Pytorch实现简单的图片分类器

热门文章

  1. js实现的笛卡尔乘积-商品发布
  2. WCF中NetTCp配置
  3. Merge k Sorted Lists
  4. 【Linux】查询 OS、CPU、内存、硬盘信息
  5. Soft-Masked BERT:文本纠错与BERT的最新结合
  6. 一道错误答案传遍全网的逻辑面试题
  7. pandas—pandas.read_parquet
  8. 深度学习2.0-25.Train-Val-Test划分检测过拟合(交叉验证)
  9. 荐书丨如何快速成为150万+小程序开发者中的一员
  10. WannaCry只是个开始?信息时代你急需的安全书单