【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(按宽高自动换行布局)
二,宽高尺寸处理
- SizedBox(设置具体尺寸)
- 介绍
比较常用的一个控件,设置具体尺寸。SizeBox组件是一个特定大小的盒子,这个组件强制它的chird有特定的宽度和高度,如果宽度和高度为null,则此组件将调整自身大小匹配该纬度中child的大小。 - 布局行为
SizedBox布局行为相对较简单:
- child不为null时,如果设置了宽高,则会强制把child尺寸调到此宽高;如果没有设置宽高,则会根据child尺寸进行调整;
- child为null时,如果设置了宽高,则自身尺寸调整到此宽高值,如果没设置,则尺寸为0;
- 继承关系
Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > SizedBox
- 构造方法
const SizedBox({
Key key,
this.width, //宽
this.height, //高
Widget child //子组件
}) - 参数解析
width:宽度值,如果具体设置了,则强制child宽度为此值,如果没设置,则根据child宽度调整自身宽度。
height:同上。
- 介绍
- ConstrainedBox(限定最大最小宽高布局)
- 介绍
这个控件的作用是添加额外的限制条件(constraints)到child上,本身挺简单的,可以被一些控件替换使用。Flutter的布局控件体系,梳理着发现确实有点乱,感觉总体思想是缺啥就造啥
- 布局行为
ConstrainedBox的布局行为非常简单,取决于设置的限制条件,而关于父子节点的限制因素生效优先级,可以查看之前的文章,在这里就不做具体叙述了。
- 继承关系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > ConstrainedBox
- 构造函数
包含了一个constraints属性,且不能为null。
ConstrainedBox({ Key key, @required this.constraints, Widget child })
- 参数解析
constraints:
添加到child上的额外限制条件,其类型为BoxConstraints。BoxConstraints的作用是干啥的呢?其实很简单,就是限制各种最大最小宽高。说到这里插一句,double.infinity在widget布局的时候是合法的,也就说,例如. 想最大的扩展宽度,可以将宽度值设为double.infinity。
- 介绍
LimitedBox
- (限定最大宽高布局)
- 介绍LimitedBox,通过字面意思,也可以猜测出这个控件的作用,是限制类型的控件。这种类型的控件前面也介绍了不少了,这个是对最大宽高进行限制的控件。
- 布局行为从布局的角度讲,LimitedBox是将child限制在其设定的最大宽高中的,但这个限定是有条件的。当LimitedBox最大宽度不受限制时,child的宽度就会受到这个最大宽度的限制,高度同理。
- 继承关系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > LimitedBox
- 构造函数
const LimitedBox({ Key key, this.maxWidth = double.infinity, this.maxHeight = double.infinity, Widget child, })
- 参数解析
maxWidth:限定的最大宽度,默认值是double.infinity,不能为负数。
maxHeight:同上。
- AspectRatio(调整宽高比)
- 介绍
AspectRatio的作用是调整child到设置的宽高比,这种控件在其他移动端平台上一般都不会提供,Flutter之所以提供,我想最大的原因,可能就是自定义起来特别麻烦吧。 - 布局行为
AspectRatio的布局行为分为两种情况:
- AspectRatio首先会在布局限制条件允许的范围内尽可能的扩展,widget的高度是由宽度和比率决定的,类似于BoxFit中的contain,按照固定比率去尽量占满区域。
- 如果在满足所有限制条件过后无法找到一个可行的尺寸,AspectRatio最终将会去优先适应布局限制条件,而忽略所设置的比率。
- 继承关系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > AspectRatio
从继承关系看,AspectRatio是基础的布局控件。
- 构造函数
const AspectRatio({ Key key, @required this.aspectRatio, Widget child })
构造函数只包含了一个aspectRatio属性,其中aspectRatio不能为null。
- 参数解析
aspectRatio:aspectRatio是宽高比,最终可能不会根据这个值去布局,具体则要看综合因素,外层是否允许按照这种比率进行布局,只是一个参考值。
- 介绍
- FractionallySizedBox(百分比布局)
- 介绍
FractionallySizedBox控件会根据现有空间,来调整child的尺寸,所以说child就算设置了具体的尺寸数值,也不起作用。 - 布局行为
FractionallySizedBox的布局行为主要跟它的宽高因子两个参数有关,当参数为null或者有具体数值的时候,布局表现不一样。当然,还有一个辅助参数alignment,作为对齐方式进行布局。
- 当设置了具体的宽高因子,具体的宽高则根据现有空间宽高 * 因子,有可能会超出父控件的范围,当宽高因子大于1的时候;
- 当没有设置宽高因子,则填满可用区域;
- 继承关系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > FractionallySizedBox
- 构造函数
const FractionallySizedBox({ Key key, this.alignment = Alignment.center, this.widthFactor, this.heightFactor, Widget child, })
- 参数解析
alignment:对齐方式,不能为null。
widthFactor:宽度因子,跟之前介绍的控件类似,宽度乘以这个值,就是最后的宽度。
heightFactor:高度因子,用作计算最后实际高度的。
其中widthFactor和heightFactor都有一个规则
- 如果不为null,那么实际的最大宽高度则为child的宽高乘以这个因子;
- 如果为null,那么child的宽高则会尽量充满整个区域。
- 介绍
三,常用方法
- SizeBox(设置具体尺寸)
/** * MySizeBox */ class MySizeBox extends StatelessWidget { @override Widget build(BuildContext context) { // TODO: implement build return new Container( color: Colors.green, padding: const EdgeInsets.all(5.0), child: SizedBox( width: 200.0, height: 200.0, child: new Container( color: Colors.red, width: 100.0, height: 300.0, ), ), ); } }
效果图:
源码解析:
SizedBox内部是通过RenderConstrainedBox来实现的。具体的源码就不解析了,总体思路是,根据宽高值算好一个constraints,然后强制应用到child上。 - ConstrainedBox(限定最大最小宽高布局)
/** * ConstrainedBox * 在一个宽高200.0的Container上添加一个约束最大最小宽高的ConstrainedBox,实际的显示中,则是一个宽高为150.0的区域。 */ class MyConstrainedBox extends StatelessWidget { @override Widget build(BuildContext context) { // TODO: implement build return new ConstrainedBox( constraints: const BoxConstraints( minWidth: 100.0, minHeight: 100.0, maxWidth: 150.0, maxHeight: 150.0, ), child: new Container( width: 200.0, height: 200.0, color: Colors.red, ), ); } }
效果图:
源码解析:
@override RenderConstrainedBox createRenderObject(BuildContext context) { return new RenderConstrainedBox(additionalConstraints: constraints); }
RenderConstrainedBox实现其绘制。其具体的布局表现分两种情况:
如果child不为null,则将限制条件加在child上;
如果child为null,则会尽可能的缩小尺寸。
具体代码如下:@override void performLayout() { if (child != null) { child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true); size = child.size; } else { size = _additionalConstraints.enforce(constraints).constrain(Size.zero); } }
使用场景:
需要添加额外的约束条件可以使用此控件,例如设置最小宽高,尽可能的占用区域等等。笔者在实际开发中使用的倒不是很多,倒不是说这个控件不好使用,而是好多约束因素是综合的,例如需要额外的设置margin、padding属性能,因此单独再套个这个就显得很繁琐了。
- LimitedBox(限定最大宽高布局)
class MyLimitedBox extends StatelessWidget { @override Widget build(BuildContext context) { // TODO: implement build return new Row( children: <Widget>[ new Container( color: Colors.red, width: 100.0, ), LimitedBox( maxWidth: 150.0, child: new Container( color: Colors.blue, width: 250.0, ), ) ], ); } }
效果图:
源码解析:先不说其源码,单纯从其作用,前面介绍的SizedBox、ConstrainedBox都类似,都是通过强加到child的constraint,来达到相应的效果。
我们直接看其计算constraint的代码
minWidth: constraints.minWidth, maxWidth: constraints.hasBoundedWidth ? constraints.maxWidth : constraints.constrainWidth(maxWidth), minHeight: constraints.minHeight, maxHeight: constraints.hasBoundedHeight ? constraints.maxHeight : constraints.constrainHeight(maxHeight)
LimitedBox只是改变最大宽高的限定。具体的布局代码如下:
if (child != null) { child.layout(_limitConstraints(constraints), parentUsesSize: true); size = constraints.constrain(child.size); } else { size = _limitConstraints(constraints).constrain(Size.zero); }
根据最大尺寸,限制child的布局,然后将自身调节到child的尺寸。
使用场景:
是不可能清楚了,光是找例子,就花了不少时间。Flutter的一些冷门控件,真的是除了官方的文档,啥材料都木有。
谷歌说这个很有用,还是一脸懵逼。这种控件,也有其他的替代解决方案,LimitedBox可以达到的效果,ConstrainedBox都可以实现。 - AspectRatio(调整宽高比)
/** * AspectRatio * 定义了一个高度为200的区域,内部AspectRatio比率设置为1.5,最终AspectRatio的是宽300高200的一个区域。 */ class MyAspectRatio extends StatelessWidget { @override Widget build(BuildContext context) { // TODO: implement build return new Container( height: 200.0, child: new AspectRatio( aspectRatio: 1.5, child: new Container( color: Colors.red, ), ), ); } }
效果图:
源码解析:
@override RenderAspectRatio createRenderObject(BuildContext context) => new RenderAspectRatio(aspectRatio: aspectRatio);
经过前面一些控件的解析,我想大家对这种构造应该不会再陌生了,绘制都是交由RenderObject去完成的,这里则是由RenderAspectRatio去完成具体的绘制工作。
RenderAspectRatio的构造函数中会对aspectRatio做一些检测(assert)
aspectRatio不能为null; aspectRatio必须大于0; aspectRatio必须是有限的。
接下来我们来看一下RenderAspectRatio的具体尺寸计算函数:
(1)如果限制条件为isTight,则返回最小的尺寸(constraints.smallest);
if (constraints.isTight) return constraints.smallest;
(2)如果宽度为有限的值,则将高度设置为width / _aspectRatio。 如果宽度为无限,则将高度设为最大高度,宽度设为height * _aspectRatio;
if (width.isFinite) { height = width / _aspectRatio; } else { height = constraints.maxHeight; width = height * _aspectRatio; }
(3)接下来则是在限制范围内调整宽高,总体思想则是宽度优先,大于最大值则设为最大值,小于最小值,则设为最小值。
如果宽度大于最大宽度,则将其设为最大宽度,高度设为width / _aspectRatio;if (width > constraints.maxWidth) { width = constraints.maxWidth; height = width / _aspectRatio; }
如果高度大于最大高度,则将其设为最大高度,宽度设为height * _aspectRatio;
if (height > constraints.maxHeight) { height = constraints.maxHeight; width = height * _aspectRatio; }
如果宽度小于最小宽度,则将其设为最小宽度,高度设为width / _aspectRatio;
if (width < constraints.minWidth) { width = constraints.minWidth; height = width / _aspectRatio; }
如果高度小于最小高度,则将其设为最小高度,宽度设为height * _aspectRatio。
if (height < constraints.minHeight) { height = constraints.minHeight; width = height * _aspectRatio; }
- FractionallySizedBox(百分比布局)
/** * MyFractionallySizeBox */ class MyFractionallySizeBox extends StatelessWidget { @override Widget build(BuildContext context) { // TODO: implement build return new Container( color: Colors.blue, height: 150.0, width: 150.0, padding: const EdgeInsets.all(10.0), child: new FractionallySizedBox( alignment: Alignment.topLeft, widthFactor: 1.5, heightFactor: 0.5, child: new Container( color: Colors.red, ), ), ); } }
效果图:
源码解析:
FractionallySizedBox内部具体渲染是由RenderFractionallySizedOverflowBox来实现的,通过命名就可以看出,这个控件可能会Overflow。我们直接看实际计算尺寸的代码
double minWidth = constraints.minWidth; double maxWidth = constraints.maxWidth;
if (_widthFactor != null) { final double width = maxWidth * _widthFactor; minWidth = width; maxWidth = width; }
double minHeight = constraints.minHeight; double maxHeight = constraints.maxHeight;
if (_heightFactor != null) { final double height = maxHeight * _heightFactor; minHeight = height; maxHeight = height; }
源代码中,根据宽高因子是否存在,来进行相对应的尺寸计算。
使用场景:
当需要在一个区域里面取百分比尺寸的时候,可以使用这个,比方说,高度40%宽度70%的区域。当然,AspectRatio也可以达到近似的效果。