【Flutter学习】页面布局之其它布局处理
一,概述
Flutter中拥有30多种预定义的布局widget,常用的有Container、Padding、Center、Flex、Row、Colum、ListView、GridView。按照《Flutter技术入门与实战》上面来说的话,大概分为四类
- 基础布局组件:Container(容器布局),Center(居中布局),Padding(填充布局),Align(对齐布局),Colum(垂直布局),Row(水平布局),Expanded(配合Colum,Row使用),FittedBox(缩放布局),Stack(堆叠布局),overflowBox(溢出父视图容器)。
- 宽高尺寸处理:SizedBox(设置具体尺寸),ConstrainedBox(限定最大最小宽高布局),LimitedBox(限定最大宽高布局),AspectRatio(调整宽高比),FractionallySizedBox(百分比布局)
- 列表和表格处理:ListView(列表),GridView(网格),Table(表格)
- 其它布局处理:Transform(矩阵转换),Baseline(基准线布局),Offstage(控制是否显示组件),Wrap(按宽高自动换行布局)
二,其它布局处理
- Transform(矩阵转换)
- 介绍
Transform在介绍Container的时候有提到过,就是做矩阵变换的。Container中矩阵变换就是使用的Transform。
- 布局行为
有过其他平台经验的,对Transform应该不会陌生。可以对child做平移、旋转、缩放等操作。
- 继承关系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > Transform
- 构造函数
const Transform({
Key key,
@required this.transform,
this.origin,
this.alignment,
this.transformHitTests = true,
Widget child,
})上面是其默认的构造函数,Transform也提供下面三种构造函数:
Transform.rotate
Transform.translate
Transform.scale - 参数含义
- transform:一个4x4的矩阵。不难发现,其他平台的变换矩阵也都是四阶的。一些复合操作,仅靠三维是不够的,必须采用额外的一维来补充,感兴趣的同学可以自行搜索了解。
origin:旋转点,相对于左上角顶点的偏移。默认旋转点事左上角顶点。
alignment:对齐方式。
transformHitTests:点击区域是否也做相应的改变。
- 介绍
- Baseline(基准线布局)
- 介绍
Baseline这个控件,做过移动端开发的都会了解过,一般文字排版的时候,可能会用到它。它的作用很简单,根据child的baseline,来调整child的位置。例如两个字号不一样的文字,希望底部在一条水平线上,就可以使用这个控件,是一个非常基础的控件。
关于字符的Baseline,可以看下下面这张图,这具体就涉及到了字体排版,感兴趣的同学可以自行了解。

- 布局行为
Baseline控件布局行为分为两种情况:
- 如果child有baseline,则根据child的baseline属性,调整child的位置;
- 如果child没有baseline,则根据child的bottom,来调整child的位置。
- 继承关系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > Baseline
- 构造函数
const Baseline({
Key key,
@required this.baseline,
@required this.baselineType,
Widget child
}) - 参数含义
baseline:baseline数值,必须要有,从顶部算。
baselineType:bseline类型,也是必须要有的,目前有两种类型:
- alphabetic:对齐字符底部的水平线;
- ideographic:对齐表意字符的水平线。
- 介绍
- Offstage(控制是否显示组件)
- 介绍
Offstage的作用很简单,通过一个参数,来控制child是否显示,日常使用中也算是比较常用的控件。 - 布局行为
Offstage的布局行为完全取决于其offstage参数
- 当offstage为true,当前控件不会被绘制在屏幕上,不会响应点击事件,也不会占用空间;
- 当offstage为false,当前控件则跟平常用的控件一样渲染绘制;
另外,当Offstage不可见的时候,如果child有动画,应该手动停掉,Offstage并不会停掉动画。
- 继承关系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > Offstage
- 构造函数
const Offstage(
{
Key key,
this.offstage = true,
Widget child
}
) - 参数含义
offstage:默认为true,也就是不显示,当为flase的时候,会显示该控件。
- 介绍
- Wrap(按宽高自动换行布局)
- 介绍
其实Wrap实现的效果,Flow可以很轻松,而且可以更加灵活的实现出来。 - 布局行为
Flow可以很轻易的实现Wrap的效果,但是Wrap更多的是在使用了Flex中的一些概念,某种意义上说是跟Row、Column更加相似的。
单行的Wrap跟Row表现几乎一致,单列的Wrap则跟Row表现几乎一致。但Row与Column都是单行单列的,Wrap则突破了这个限制,mainAxis上空间不足时,则向crossAxis上去扩展显示。
从效率上讲,Flow肯定会比Wrap高,但是Wrap使用起来会方便一些。
- 继承关系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > MultiChildRenderObjectWidget > Wrap
- 构造函数
Wrap({
Key key,
this.direction = Axis.horizontal,
this.alignment = WrapAlignment.start,
this.spacing = 0.0,
this.runAlignment = WrapAlignment.start,
this.runSpacing = 0.0,
this.crossAxisAlignment = WrapCrossAlignment.start,
this.textDirection,
this.verticalDirection = VerticalDirection.down,
List<Widget> children = const <Widget>[],
}) - 参数含义
- direction:主轴(mainAxis)的方向,默认为水平。
alignment:主轴方向上的对齐方式,默认为start。
spacing:主轴方向上的间距。
runAlignment:run的对齐方式。run可以理解为新的行或者列,如果是水平方向布局的话,run可以理解为新的一行。
runSpacing:run的间距。
crossAxisAlignment:交叉轴(crossAxis)方向上的对齐方式。
textDirection:文本方向。
verticalDirection:定义了children摆放顺序,默认是down,见Flex相关属性介绍。
三,使用实例
- Transform(矩阵转换)
Center(
child: Transform(
transform: Matrix4.rotationZ(0.3),
child: Container(
color: Colors.blue,
width: 100.0,
height: 100.0,
),
),
)将Container绕z轴旋转了
效果图:

源码解析:
我们来看看它的绘制代码:
if (child != null) {
final Matrix4 transform = _effectiveTransform;
final Offset childOffset = MatrixUtils.getAsTranslation(transform);
if (childOffset == null)
context.pushTransform(needsCompositing, offset, transform, super.paint);
else
super.paint(context, offset + childOffset);
}整个绘制代码不复杂,如果child有偏移的话,则将两个偏移相加,进行绘制。如果child没有偏移的话,则按照设置的offset、transform进行绘制。
使用场景:这个控件算是较常见的控件,很多平移、旋转、缩放都可以使用的到。如果只是单纯的进行变换的话,用Transform比用Container效率会更高。
- Baseline(基准线布局)
new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Baseline(
baseline: 50.0,
baselineType: TextBaseline.alphabetic,
child: new Text(
'TjTjTj',
style: new TextStyle(
fontSize: 20.0,
textBaseline: TextBaseline.alphabetic,
),
),
),
new Baseline(
baseline: 50.0,
baselineType: TextBaseline.alphabetic,
child: new Container(
width: 30.0,
height: 30.0,
color: Colors.red,
),
),
new Baseline(
baseline: 50.0,
baselineType: TextBaseline.alphabetic,
child: new Text(
'RyRyRy',
style: new TextStyle(
fontSize: 35.0,
textBaseline: TextBaseline.alphabetic,
),
),
),
],
)效果图:

源码解析:
我们来看看源码中具体计算尺寸的这段代码
child.layout(constraints.loosen(), parentUsesSize: true);
final double childBaseline = child.getDistanceToBaseline(baselineType);
final double actualBaseline = baseline;
final double top = actualBaseline - childBaseline;
final BoxParentData childParentData = child.parentData;
childParentData.offset = new Offset(0.0, top);
final Size childSize = child.size;
size = constraints.constrain(new Size(childSize.width, top + childSize.height));getDistanceToBaseline这个函数是获取baseline数值的,存在的话,就取这个值,不存在的话,则取其高度。
整体的计算过程:
(1)获取child的 baseline 值;
(2)计算出top值,其为 baseline - childBaseline,这个值有可能为负数;
(3)计算出Baseline控件尺寸,宽度为child的,高度则为 top + childSize.height。 - Offstage(控制是否显示组件)
Column(
children: <Widget>[
new Offstage(
offstage: offstage,
child: Container(color: Colors.blue, height: 100.0),
),
new CupertinoButton(
child: Text("点击切换显示"),
onPressed: () {
setState(() {
offstage = !offstage;
});
},
),
],
)源码解析:
我们先来看下Offstage的computeIntrinsicSize相关的方法:
@override
double computeMinIntrinsicWidth(double height) {
if (offstage)
return 0.0;
return super.computeMinIntrinsicWidth(height);
}可以看到,当offstage为true的时候,自身的最小以及最大宽高都会被置为0.0。
接下来我们来看下其hitTest方法:
@override
bool hitTest(HitTestResult result, { Offset position }) {
return !offstage && super.hitTest(result, position: position);
}当offstage为true的时候,也不会去执行。
最后我们来看下其paint方法:
@override
void paint(PaintingContext context, Offset offset) {
if (offstage)
return;
super.paint(context, offset);
}当offstage为true的时候直接返回,不绘制了。
到此,跟上面所说的布局行为对应上了。我们一定要清楚一件事情,Offstage并不是通过插入或者删除自己在widget tree中的节点,来达到显示以及隐藏的效果,而是通过设置自身尺寸、不响应hitTest以及不绘制,来达到展示与隐藏的效果。
- Wrap(按宽高自动换行布局)
Wrap(
spacing: 8.0, // gap between adjacent chips
runSpacing: 4.0, // gap between lines
children: <Widget>[
Chip(
avatar: CircleAvatar(
backgroundColor: Colors.blue.shade900, child: new Text('AH', style: TextStyle(fontSize: 10.0),)),
label: Text('Hamilton'),
),
Chip(
avatar: CircleAvatar(
backgroundColor: Colors.blue.shade900, child: new Text('ML', style: TextStyle(fontSize: 10.0),)),
label: Text('Lafayette'),
),
Chip(
avatar: CircleAvatar(
backgroundColor: Colors.blue.shade900, child: new Text('HM', style: TextStyle(fontSize: 10.0),)),
label: Text('Mulligan'),
),
Chip(
avatar: CircleAvatar(
backgroundColor: Colors.blue.shade900, child: new Text('JL', style: TextStyle(fontSize: 10.0),)),
label: Text('Laurens'),
),
],
)效果图:

源码解析:
我们来看下其布局代码。
第一步,如果第一个child为null,则将其设置为最小尺寸。
RenderBox child = firstChild;
if (child == null) {
size = constraints.smallest;
return;
}第二步,根据direction、textDirection以及verticalDirection属性,计算出相关的mainAxis、crossAxis是否需要调整方向,以及主轴方向上的限制。
double mainAxisLimit = 0.0;
bool flipMainAxis = false;
bool flipCrossAxis = false;
switch (direction) {
case Axis.horizontal:
childConstraints = new BoxConstraints(maxWidth: constraints.maxWidth);
mainAxisLimit = constraints.maxWidth;
if (textDirection == TextDirection.rtl)
flipMainAxis = true;
if (verticalDirection == VerticalDirection.up)
flipCrossAxis = true;
break;
case Axis.vertical:
childConstraints = new BoxConstraints(maxHeight: constraints.maxHeight);
mainAxisLimit = constraints.maxHeight;
if (verticalDirection == VerticalDirection.up)
flipMainAxis = true;
if (textDirection == TextDirection.rtl)
flipCrossAxis = true;
break;
}第三步,计算出主轴以及交叉轴的区域大小。
while (child != null) {
child.layout(childConstraints, parentUsesSize: true);
final double childMainAxisExtent = _getMainAxisExtent(child);
final double childCrossAxisExtent = _getCrossAxisExtent(child);
if (childCount > && runMainAxisExtent + spacing + childMainAxisExtent > mainAxisLimit) {
mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
crossAxisExtent += runCrossAxisExtent;
if (runMetrics.isNotEmpty)
crossAxisExtent += runSpacing;
runMetrics.add(new _RunMetrics(runMainAxisExtent, runCrossAxisExtent, childCount));
runMainAxisExtent = 0.0;
runCrossAxisExtent = 0.0;
childCount = ;
}
runMainAxisExtent += childMainAxisExtent;
if (childCount > )
runMainAxisExtent += spacing;
runCrossAxisExtent = math.max(runCrossAxisExtent, childCrossAxisExtent);
childCount += ;
final WrapParentData childParentData = child.parentData;
childParentData._runIndex = runMetrics.length;
child = childParentData.nextSibling;
}第四步,根据direction设置Wrap的尺寸。
switch (direction) {
case Axis.horizontal:
size = constraints.constrain(new Size(mainAxisExtent, crossAxisExtent));
containerMainAxisExtent = size.width;
containerCrossAxisExtent = size.height;
break;
case Axis.vertical:
size = constraints.constrain(new Size(crossAxisExtent, mainAxisExtent));
containerMainAxisExtent = size.height;
containerCrossAxisExtent = size.width;
break;
}第五步,根据runAlignment计算出每一个run之间的距离,几种属性的差异,之前文章介绍过,在此就不做详细阐述。
final double crossAxisFreeSpace = math.max(0.0, containerCrossAxisExtent - crossAxisExtent);
double runLeadingSpace = 0.0;
double runBetweenSpace = 0.0;
switch (runAlignment) {
case WrapAlignment.start:
break;
case WrapAlignment.end:
runLeadingSpace = crossAxisFreeSpace;
break;
case WrapAlignment.center:
runLeadingSpace = crossAxisFreeSpace / 2.0;
break;
case WrapAlignment.spaceBetween:
runBetweenSpace = runCount > ? crossAxisFreeSpace / (runCount - ) : 0.0;
break;
case WrapAlignment.spaceAround:
runBetweenSpace = crossAxisFreeSpace / runCount;
runLeadingSpace = runBetweenSpace / 2.0;
break;
case WrapAlignment.spaceEvenly:
runBetweenSpace = crossAxisFreeSpace / (runCount + );
runLeadingSpace = runBetweenSpace;
break;
}第六步,根据alignment计算出每一个run中child的主轴方向上的间距。
switch (alignment) {
case WrapAlignment.start:
break;
case WrapAlignment.end:
childLeadingSpace = mainAxisFreeSpace;
break;
case WrapAlignment.center:
childLeadingSpace = mainAxisFreeSpace / 2.0;
break;
case WrapAlignment.spaceBetween:
childBetweenSpace = childCount > ? mainAxisFreeSpace / (childCount - ) : 0.0;
break;
case WrapAlignment.spaceAround:
childBetweenSpace = mainAxisFreeSpace / childCount;
childLeadingSpace = childBetweenSpace / 2.0;
break;
case WrapAlignment.spaceEvenly:
childBetweenSpace = mainAxisFreeSpace / (childCount + );
childLeadingSpace = childBetweenSpace;
break;
}最后一步,调整child的位置。
while (child != null) {
final WrapParentData childParentData = child.parentData;
if (childParentData._runIndex != i)
break;
final double childMainAxisExtent = _getMainAxisExtent(child);
final double childCrossAxisExtent = _getCrossAxisExtent(child);
final double childCrossAxisOffset = _getChildCrossAxisOffset(flipCrossAxis, runCrossAxisExtent, childCrossAxisExtent);
if (flipMainAxis)
childMainPosition -= childMainAxisExtent;
childParentData.offset = _getOffset(childMainPosition, crossAxisOffset + childCrossAxisOffset);
if (flipMainAxis)
childMainPosition -= childBetweenSpace;
else
childMainPosition += childMainAxisExtent + childBetweenSpace;
child = childParentData.nextSibling;
} if (flipCrossAxis)
crossAxisOffset -= runBetweenSpace;
else
crossAxisOffset += runCrossAxisExtent + runBetweenSpace;我们大致梳理一下布局的流程。
如果第一个child为null,则将Wrap设置为最小尺寸,布局结束;
根据direction、textDirection以及verticalDirection属性,计算出mainAxis、crossAxis是否需要调整方向;
计算出主轴以及交叉轴的区域大小;
根据direction设置Wrap的尺寸;
根据runAlignment计算出每一个run之间的距离;
根据alignment计算出每一个run中child的主轴方向上的间距
调整每一个child的位置。
四,参考
【Flutter学习】页面布局之其它布局处理的更多相关文章
- 【Flutter学习】页面布局之列表和表格处理
一,概述 Flutter中拥有30多种预定义的布局widget,常用的有Container.Padding.Center.Flex.Row.Colum.ListView.GridView.按照< ...
- 【Flutter学习】页面布局之宽高尺寸处理
一,概述 Flutter中拥有30多种预定义的布局widget,常用的有Container.Padding.Center.Flex.Row.Colum.ListView.GridView.按照< ...
- 【Flutter学习】页面布局之基础布局组件
一,概述 Flutter中拥有30多种预定义的布局widget,常用的有Container.Padding.Center.Flex.Row.Colum.ListView.GridView.按照< ...
- Flutter学习指南:UI布局和控件
Flutter学习指南:UI布局和控件 - IT程序猿 https://www.itcodemonkey.com/article/11041.html
- Flutter学习笔记(23)--多个子元素的布局Widget(Rwo、Column、Stack、IndexedStack、Table、Wrap)
如需转载,请注明出处:Flutter学习笔记(23)--多个子元素的布局Widget(Rwo.Column.Stack.IndexedStack.Table.Wrap) 上一篇梳理了拥有单个子元素布局 ...
- Flutter学习笔记(22)--单个子元素的布局Widget(Container、Padding、Center、Align、FittedBox、Offstage、LimitedBox、OverflowBox、SizedBox)
如需转载,请注明出处:Flutter学习笔记(22)--单个子元素的布局Widget(Container.Padding.Center.Align.FittedBox.Offstage.Limited ...
- web页面之响应式布局
一.什么是响应式布局? 响应式布局是Ethan Marcotte在2010年5月份提出的一个概念,简而言之,就是一个网站能够兼容多个终端——而不是为每个终端做一个特定的版本.这个概念是为解决移动互联网 ...
- Flex布局【弹性布局】学习
先让我们看看在原来的学习中遇到的问题 之前在软件工程的大作业中,自己从零开始学习如何开发一个网站,从页面,到后台,当然数据库是大二的必修课 在学习如何编写一个静态页面的时候,完全是自学,自己摸索,所以 ...
- CSS3 Flex布局(伸缩布局盒模型)学习
CSS3 Flex布局(伸缩布局盒模型)学习 转自:http://www.xifengxx.com/web-front-end/1408.html CSS2定义了四种布局:块布局.行内布局.表格布局盒 ...
随机推荐
- 直击KubeCon 2018 |云原生正在改变你的衣食住行
云计算从不被看好到成长为势不可挡的技术潮流,仅仅用了十年的时间.如今“云原生”又被企业以及开发者奉为圭臬,并被认为是云计算的未来. 阿里云容器技术负责人易立认为云计算有三个阶段:云搬迁.云就绪和云原生 ...
- DOS基础使用专题(强烈推荐)
DOS基础使用专题(强烈推荐) 美丽的DOS时代 DOS是世界上使用人数最多的操作系统,包括上面的Win3.x/9x等GUI操作平台的用户.尽管许多人由于种种原因而使用了其它非DOS的操作系统或操作环 ...
- 【LeetCode 60】第k个排列
题目链接 [题解] 逆康托展开. 考虑康托展开的过程. K = ∑v[i]*(n-i)! 其中v[i]表示在a[i+1..n]中比a[i]小的数字的个数 (也即未出现的数字中它排名第几(从0开始)) ...
- [CF1161F]Zigzag Game
通过这道模板题学了一种新的模型,记录一下. 稳定婚姻匹配 至于这道题,显然是一个二分图博弈的模型.考虑选择Bob,我们要找一组匹配使得任何情况下Bob都有匹配边能走.不失一般性假设Alice选择了in ...
- paper 166:梯度下降法及其Python实现
参考来源:https://blog.csdn.net/yhao2014/article/details/51554910 梯度下降法(gradient descent),又名最速下降法(steepes ...
- leetcode-解题记录 884. 两句话中的不常见单词
题目 给定两个句子 A 和 B . (句子是一串由空格分隔的单词.每个单词仅由小写字母组成.) 如果一个单词在其中一个句子中只出现一次,在另一个句子中却没有出现,那么这个单词就是不常见的. 返回所有不 ...
- ORM模型类介绍,
所有的软件开发过程中,都会涉及到对象和关系型数据库,在用户层面和业务逻辑层面,程序员编写代码都是面向对象的,当我们对象的信息发生变化的时候,都需要将对应的信息,传到关系型数据库中.而在此之前,需要我们 ...
- python 装饰器 第五步(1):带有参数的装饰器
#第五步:带有参数的装饰器 #用于扩展基本函数的函数 def kuozhan(func): #内部函数(扩展之后的eat函数) #5由于调用的时候传了两个参数,未来的eat函数没有参数接收 #5报错的 ...
- java反射(一)--认识反射机制
一.认识java反射机制 在java语言中,之所以会有如此众多的开源技术支撑,很大的一部分来源于java最大特征--反射机制.能够灵活的去使用反射机制进行项目的开发与设计,才能够真正接触到java的精 ...
- 整理eclipse,升级jdk环境小记录
这2天在整理项目: 需要把eclipse 32位,jdk1.6 32位的更改为eclipse 64位,jdk1.8 64位版本的,于是我就在一台window7的电脑上直接操作,遇到了一下几点问题,记录 ...