Flutter 笔记

阅读: 评论:0

Flutter 笔记

Flutter 笔记

布局过程

Layout(布局)过程主要是确定每一个组件的布局信息(大小和位置),Flutter 的布局过程如下:

  1. 父节点向子节点传递约束(constraints)信息,限制子节点的最大和最小宽高。
  2. 子节点根据约束信息确定自己的大小(size)。
  3. 父节点根据特定布局规则(不同布局组件会有不同的布局算法)确定每一个子节点在父节点布局空间中的位置,用偏移 offset 表示。
  4. 递归整个过程,确定出每一个节点的大小和位置。

可以看到,组件的大小是由自身决定的,而组件的位置是由父组件决定的。

下面是官网的一张图,它用三句话描述了 Flutter 布局过程的精髓:

Flutter 中的布局类组件很多,根据孩子数量可以分为单子组件和多子组件,下面我们先通过分别自定义一个单子组件和多子组件来直观理解一下Flutter的布局过程,之后会介绍一下布局更新过程和 Flutter 中的 Constraints(约束)。

单子组件布局示例

我们实现一个单子组件 CustomCenter,功能基本和 Center 组件对齐,通过这个实例我们演示一下布局的主要流程。

首先,我们定义组件,为了介绍布局原理,我们不采用组合的方式来实现组件,而是直接通过定制 RenderObject 的方式来实现。因为居中组件需要包含一个子节点,所以我们直接继承 SingleChildRenderObjectWidget

class CustomCenter extends SingleChildRenderObjectWidget {const CustomCenter2({Key? key, required Widget child}): super(key: key, child: child);@overrideRenderObject createRenderObject(BuildContext context) {return RenderCustomCenter();}
}

接着实现 RenderCustomCenter。这里直接继承 RenderObject 会更接近底层一点,但这需要我们自己手动实现一些和布局无关的东西,比如事件分发等逻辑。为了更聚焦布局本身,我们选择继承自RenderShiftedBox,它是RenderBox的子类(RenderBox继承自RenderObject),它会帮我们实现布局之外的一些功能,这样我们只需要重写performLayout,在该函数中实现子节点居中算法即可。

class RenderCustomCenter extends RenderShiftedBox {RenderCustomCenter({RenderBox? child}) : super(child);@overridevoid performLayout() {//1. 先对子组件进行layout,随后获取它的sizechild!.layout(constraints.loosen(), //将约束传递给子节点parentUsesSize: true, // 因为我们接下来要使用child的size,所以不能为false);//2.根据子组件的大小确定自身的大小size = strain(Size(constraints.maxWidth == double.infinity? child!.size.width: double.infinity,constraints.maxHeight == double.infinity? child!.size.height: double.infinity,));// 3. 根据父节点子节点的大小,算出子节点在父节点中居中之后的偏移,然后将这个偏移保存在// 子节点的parentData中,在后续的绘制阶段,会用到。BoxParentData parentData = child!.parentData as BoxParentData;parentData.offset = ((size - child!.size) as Offset) / 2;}
}

布局过程请参考注释,在此需要额外说明有3点:

  1. 在对子节点进行布局时, constraintsCustomCenter 的父组件传递给自己的约束信息,我们传递给子节点的约束信息是constraints.loosen(),下面看一下loosen的实现源码:
BoxConstraints loosen() {return BoxConstraints(minWidth: 0.0,maxWidth: maxWidth,minHeight: 0.0,maxHeight: maxHeight,);
}

很明显,CustomCenter 约束子节点最大宽高不超过自身的最大宽高。

  1. 子节点在父节点(CustomCenter)的约束下,确定自己的宽高;此时CustomCenter会根据子节点的宽高确定自己的宽高,上面代码的逻辑是,如果CustomCenter父节点传递给它最大宽高约束是无限大时,它的宽高会设置为它子节点的宽高。注意,如果这时将CustomCenter的宽高也设置为无限大就会有问题,因为在一个无限大的范围内自己的宽高也是无限大的话,那么实际上的宽高到底是多大,它的父节点会懵逼的!屏幕的大小是固定的,这显然不合理。如果CustomCenter父节点传递给它的最大宽高约束不是无限大,那么是可以指定自己的宽高为无限大的,因为在一个有限的空间内,子节点如果说自己无限大,那么最大也就是父节点的大小。所以,简而言之,CustomCenter 会尽可能让自己填满父元素的空间。

  2. CustomCenter 确定了自己的大小和子节点大小之后就可以确定子节点的位置了,根据居中算法,将子节点的原点坐标算出后保存在子节点的 parentData 中,在后续的绘制阶段会用到,具体怎么用,我们看一下RenderShiftedBox中默认的 paint 实现:

@override
void paint(PaintingContext context, Offset offset) {if (child != null) {final BoxParentData childParentData = child!.parentData! as BoxParentData;//从child.parentData中取出子节点相对当前节点的偏移,加上当前节点在屏幕中的偏移,//便是子节点在屏幕中的偏移。context.paintChild(child!, childParentData.offset + offset);}
}
performLayout 流程

可以看到,布局的逻辑是在 performLayout 方法中实现的。我们梳理一下 performLayout 中具体做的事:

  1. 如果有子组件,则对子组件进行递归布局。
  2. 确定当前组件的大小(size),通常会依赖子组件的大小。
  3. 确定子组件在当前组件中的起始偏移。

在Flutter组件库中,有一些常用的单子组件比如 Align、SizedBox、DecoratedBox 等,都可以打开源码去看看其实现。

下面我们看一个多子组件的例子。

多子组件布局示例

实际开发中我们会经常用到贴边左-右布局,现在我们就来实现一个 LeftRightBox 组件来实现左-右布局,因为LeftRightBox 有两个孩子,用一个 Widget 数组来保存子组件。

首先我们定义组件,与单子组件不同的是多子组件需要继承自 MultiChildRenderObjectWidget

lass LeftRightBox extends MultiChildRenderObjectWidget {LeftRightBox({Key? key,required List<Widget> children,})  : assert(children.length == 2, "只能传两个children"),super(key: key, children: children);@overrideRenderObject createRenderObject(BuildContext context) {return RenderLeftRight();}
}

接下来需要实现 RenderLeftRight,在其 performLayout 中我们实现实现左-右布局算法:

class LeftRightParentData extends ContainerBoxParentData<RenderBox> {}class RenderLeftRight extends RenderBoxwithContainerRenderObjectMixin<RenderBox, LeftRightParentData>,RenderBoxContainerDefaultsMixin<RenderBox, LeftRightParentData> {// 初始化每一个child的parentData        @overridevoid setupParentData(RenderBox child) {if (child.parentData is! LeftRightParentData)child.parentData = LeftRightParentData();}@overridevoid performLayout() {final BoxConstraints constraints = straints;RenderBox leftChild = firstChild!;LeftRightParentData childParentData =leftChild.parentData! as LeftRightParentData;RenderBox rightChild = Sibling!;//我们限制右孩子宽度不超过总宽度一半rightChild.pyWith(maxWidth: constraints.maxWidth / 2),parentUsesSize

本文发布于:2024-01-28 05:56:04,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/17063925725286.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:笔记   Flutter
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23