前言:
这是我参与8月更文挑战的第 22 天,活动详情查看:8月更文挑战。为应掘金的八月更文挑战,我准备在本月挑选 31 个以前没有介绍过的组件,进行全面分析和属性介绍。这些文章将来会作为 Flutter 组件集录 的重要素材。希望可以坚持下去,你的支持将是我最大的动力~
一、 认识 ConstrainedBox 组件
源码中对 ConstrainedBox 的介绍为:为子组件施加额外的约束。那什么是约束?为什么说是 额外的 ?约束的作用是什么? 通过本文一起来看一下。
1.ConstrainedBox 基本信息
下面是 ConstrainedBox 组件类的定义和 构造方法,可以看出它继承自 SingleChildRenderObjectWidget。可接受一个子组件,在构造时必须传入 constraints 参数,其类型为 BoxConstraints 。
/// 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 涂上蓝色。
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 之间。
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 函数。
double constrainWidth([ double width = double.infinity ]) {
assert(debugAssertIsValid());
return width.clamp(minWidth, maxWidth);
}
这个函数可能大家很少用,下面通过一个方法测试一下。可以看出 clamp 的参数是上下界:如果小于下界,则返回下界值,如果大于上界,则返回上界值;如果在上下界直接,则返回该值。也就是说,一个返回值不会超过 clamp 参数的上下界。这个方法非常契合约束的理念。
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 也是类似的。
double constrainHeight([ double height = double.infinity ]) {
assert(debugAssertIsValid());
return height.clamp(minHeight, maxHeight);
}
enforce 方法算是一个非常重要的方法,它可以整合两个约束,生成一个新约束。可以看出,会以入参constraints 为范围区间,本约束的四个值通过 clamp 进行计算,得到新值。这样就保证,以 本约束 为优先,以 入参约束 为兜底。 可以结合上面的案例思考一下这种约束叠加的方式。
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 对原尺寸进行运算而已。也就是说新尺寸是在入参尺寸的基础上,宽高尽量符合入参尺寸 。
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 渲染对象的尺寸。
这也就是,父渲染对象将约束自上而下传递给子节点,子渲染对象将尺寸自下而上 赋予父节点的原理。到这里大家应该对布局约束有了更深的认识,看那本文到这里就结束了,谢谢观看,明天见~