前言:
这是我参与8月更文挑战的第 22 天,活动详情查看:8月更文挑战。为应掘金的八月更文挑战
,我准备在本月挑选 31
个以前没有介绍过的组件,进行全面分析和属性介绍。这些文章将来会作为 Flutter 组件集录
的重要素材。希望可以坚持下去,你的支持将是我最大的动力~
| 本系列 | 组件文章 | 列表 | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | | 1.NotificationListener | 2.Dismissible | 3.Switch | | 4.Scrollbar | 5.ClipPath | 6.CupertinoActivityIndicator | | 7.Opacity | 8.FadeTransition | 9. AnimatedOpacity | | 10. FadeInImage | 11. Offstage | 12. TickerMode | | 13. Visibility | 14. Padding | 15. AnimatedContainer | | 16.CircleAvatar | 17.PhysicalShape | 18.Divider | | 19.Flexible、Expanded 和 Spacer | 20.Card | 21.SizedBox | | 22.ConstrainedBox [本文]
| | |
一、 认识 ConstrainedBox 组件
源码中对 ConstrainedBox
的介绍为:为子组件施加额外的
约束。那什么是约束?为什么说是 额外的
?约束的作用是什么? 通过本文一起来看一下。
1.ConstrainedBox 基本信息
下面是 ConstrainedBox
组件类的定义
和 构造方法
,可以看出它继承自 SingleChildRenderObjectWidget
。可接受一个子组件,在构造时必须传入 constraints
参数,其类型为 BoxConstraints
。
dart /// The additional constraints to impose on the child. final BoxConstraints constraints;
2.认识约束 BoxConstraints
BoxConstraints
类是对区域范围的抽象,维护着四个值: 这四个值组成了一个尺寸的取值区域
,来限定子组件的尺寸大小。这里再强调一下,组件本身是没有尺寸概念的,这里说的组件尺寸,是指其维护的渲染对象
尺寸。
| 成员对象 | 对象类型 | 默认值 | 介绍 | | --------- | -------- | --------------- | -------------- | | minWidth | double | 0 | 尺寸宽度最小值 | | maxWidth | double | double.infinity | 尺寸宽度最大值 | | minHeight | double | 0 | 尺寸高度最小值 | | maxHeight | double | double.infinity | 尺寸高度最大值 |
而 BoxConstraints
就是 RenderObject
的一个属性,负责自身区域约束,并结合额外约束向下层传递。也就是说,每一个 RenderObject
都具有约束属性。比如下面是 100*50
的 SizedBox
,使用 ColoredBox
涂上蓝色。
dart SizedBox( width: 100, height: 50, child: ColoredBox( color: Colors.blue.withAlpha(88) ), ),
可以看出 SizedBox
组件维护着 RenderConstrainedBox
渲染对象,其自身的约束是上层施加的 [w(0,800) - h(0,600)]
,也就是说该渲染对象的尺寸需要在这个范围内。除此之外,它会给子节点施加额外的 [w(100,100) - h(50,50)]
约束。这样约束传递给 ColoredBox
对应的渲染对象 _RenderColoredBox
,其约束为 [w(100,100) - h(50,50)]
,就决定了 _RenderColoredBox
尺寸被限定为 (100,50)
。
这样应该对约束是什么有了一个简单的认识。
3.ConstrainedBox 的使用
下面先来通过一个简单的例子看一下 ConstrainedBox
的作用 。在上面案例的基础上,限定宽度在 [10~40]
之内。可以看出,即使子组件使用 SizedBox
明确表示自己想要 100*50
的尺寸,但由于这里 ConstrainedBox
施加的约束,SizedBox
的宽度也必须在 10
和 40
之间。
dart ConstrainedBox( constraints: BoxConstraints( minWidth: 10, maxWidth: 40, ), child: SizedBox( width: 100, height: 50, child: ColoredBox( color: Colors.blue.withAlpha(88) ), ), );
如下所示,SizedBox
对应的渲染对象,本身约束为 [w(10,40) - h(0,600)]
,向子节点额外的约束为 [w(100,100) - h(50,50)]
,这两个约束会结合起来,成为对下一个子节点的约束。
二、进一步认识 BoxConstraints
简单来说 BoxConstraints
就是维护着四个值,但通过这四个值衍生出来的对象还是值得梳理一下的。
1. BoxConstraints 的构造方法
BoxConstraints
一共有 6
种构造方法,普通构造传入四个值,之前看过了。
.tight
构造 ,需要传入一个 Size
对象,将约束区域为指定宽高
, 和 SizedBox
作用是一致的。
.tightFor
构造, 传入的宽高值。和 tight
不同的是这里宽/高
可谓空,如果宽为空则宽度取值范围在 0~无限
之间,高度也类似。在上一篇 SizedBox 一文中我们说过,SizedBox
的内部就是使用 .tightFor
根据宽高构造约束对象的。也就是说,你使用 tightFor
创建约束,用于 ConstrainedBox
中,本质上和 SizedBox
一样。
.expand
构造,传入宽和高。和 tightFor
类似,宽/高
可空。不同点在于:如果宽为空,则宽度取值范围在 无限~无限
之间,也就是说区域无限,会尽可能扩充。
.loose
构造,和.tight
类似 ,需要传入一个 Size
对象。可以看出下界是 0
,上界是 size
的宽高。所以这种的约束是松散的,并不像 .tight
会将宽高定死。
.tightForFinite
构造,默认宽高无限。拿宽举例,默认情况下 width
无限,取值区间为 0~无限
,如果传入的 width
非无限,那么宽度将被固定为 width
,高度也类似。
2.BoxConstraints 的成员方法
BoxConstraints
的成员方法很多,但也都是围绕这四个属性值进行操作的,这里挑几个重要的方法看看。首先来看 constrainWidth
。可以看出需要传入一个 width
参数,默认是无限,返回值使用了 num.clamp
函数。
dart double constrainWidth([ double width = double.infinity ]) { assert(debugAssertIsValid()); return width.clamp(minWidth, maxWidth); }
这个函数可能大家很少用,下面通过一个方法测试一下。可以看出 clamp
的参数是上下界:如果小于下界,则返回下界值
,如果大于上界,则返回上界值
;如果在上下界直接,则返回该值
。也就是说,一个返回值不会超过 clamp
参数的上下界。这个方法非常契合约束的理念。
dart main(){ int a = 10.clamp(3, 6); // 6 int b = 4.clamp(3, 6); // 4 int c = 1.clamp(3, 6); // 3 print('$a,$b,$c'); }
同样 constrainHeight
也是类似的。
dart double constrainHeight([ double height = double.infinity ]) { assert(debugAssertIsValid()); return height.clamp(minHeight, maxHeight); }
enforce
方法算是一个非常重要的方法,它可以整合两个约束,生成一个新约束。可以看出,会以入参constraints
为范围区间,本约束的四个值通过 clamp
进行计算,得到新值。这样就保证,以 本约束
为优先,以 入参约束
为兜底。 可以结合上面的案例思考一下这种约束叠加的方式。
dart BoxConstraints enforce(BoxConstraints constraints) { return BoxConstraints( minWidth: minWidth.clamp(constraints.minWidth, constraints.maxWidth), maxWidth: maxWidth.clamp(constraints.minWidth, constraints.maxWidth), minHeight: minHeight.clamp(constraints.minHeight, constraints.maxHeight), maxHeight: maxHeight.clamp(constraints.minHeight, constraints.maxHeight), ); }
constrain
方法,入参是一个 Size
对象,且返回一个 Size
对象。也就是说,通过该 约束
和一个尺寸
返回新 尺寸
。从下面的代码中也看出,就是新尺寸就是通过 constrainWidth
和 constrainHeight
对原尺寸进行运算而已。也就是说新尺寸是在入参尺寸的基础上,宽高尽量符合入参尺寸
。
dart Size constrain(Size size) { Size result = Size(constrainWidth(size.width), constrainHeight(size.height)); assert(() { result = _debugPropagateDebugSize(size, result); return true; }()); return result; }
三、ConstrainedBox 源码解读
ConstrainedBox
继承自 SingleChildRenderObjectWidget
,需要维护一个渲染对象。
实现约束功能的渲染对象是 RenderConstrainedBox
,会在构造时将 constraints
约束对象传入。
在 RenderConstrainedBox
的 performLayout
中进行布局,可以看出当 child
非空时,会先执行 child
渲染对象的 layout
,并将 _additionalConstraints
和自身的 constraints
使用 enforce
方法进行叠加,作为子组件的约束。子组件布局玩后,自身的尺寸等于 child
渲染对象的尺寸。
这也就是,父渲染对象将约束自上而下
传递给子节点,子渲染对象将尺寸自下而上
赋予父节点的原理。到这里大家应该对布局约束有了更深的认识,看那本文到这里就结束了,谢谢观看,明天见~