【Flutter 组件集录】ConstrainedBox | 8 月更文挑战

1,236 阅读6分钟
前言:

这是我参与8月更文挑战的第 22 天,活动详情查看:8月更文挑战。为应掘金的八月更文挑战,我准备在本月挑选 31 个以前没有介绍过的组件,进行全面分析和属性介绍。这些文章将来会作为 Flutter 组件集录 的重要素材。希望可以坚持下去,你的支持将是我最大的动力~

本系列组件文章列表
1.NotificationListener2.Dismissible3.Switch
4.Scrollbar5.ClipPath6.CupertinoActivityIndicator
7.Opacity8.FadeTransition9. AnimatedOpacity
10. FadeInImage11. Offstage12. TickerMode
13. Visibility14. Padding15. AnimatedContainer
16.CircleAvatar17.PhysicalShape18.Divider
19.Flexible、Expanded 和 Spacer 20.Card21.SizedBox
22.ConstrainedBox [本文]

一、 认识 ConstrainedBox 组件

源码中对 ConstrainedBox 的介绍为:为子组件施加额外的约束。那什么是约束?为什么说是 额外的 ?约束的作用是什么? 通过本文一起来看一下。


1.ConstrainedBox 基本信息

下面是 ConstrainedBox 组件类的定义构造方法,可以看出它继承自 SingleChildRenderObjectWidget。可接受一个子组件,在构造时必须传入 constraints 参数,其类型为 BoxConstraints

/// The additional constraints to impose on the child.
final BoxConstraints constraints;

2.认识约束 BoxConstraints

BoxConstraints 类是对区域范围的抽象,维护着四个值: 这四个值组成了一个尺寸的取值区域,来限定子组件的尺寸大小。这里再强调一下,组件本身是没有尺寸概念的,这里说的组件尺寸,是指其维护的渲染对象尺寸。

成员对象对象类型默认值介绍
minWidthdouble0尺寸宽度最小值
maxWidthdoubledouble.infinity尺寸宽度最大值
minHeightdouble0尺寸高度最小值
maxHeightdoubledouble.infinity尺寸高度最大值


BoxConstraints 就是 RenderObject 的一个属性,负责自身区域约束,并结合额外约束向下层传递。也就是说,每一个 RenderObject 都具有约束属性。比如下面是 100*50SizedBox ,使用 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的宽度也必须在 1040 之间。

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 对象。也就是说,通过该 约束和一个尺寸返回新 尺寸。从下面的代码中也看出,就是新尺寸就是通过 constrainWidthconstrainHeight 对原尺寸进行运算而已。也就是说新尺寸是在入参尺寸的基础上,宽高尽量符合入参尺寸

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 约束对象传入。


RenderConstrainedBoxperformLayout 中进行布局,可以看出当 child 非空时,会先执行 child 渲染对象的 layout ,并将 _additionalConstraints 和自身的 constraints 使用 enforce 方法进行叠加,作为子组件的约束。子组件布局玩后,自身的尺寸等于 child 渲染对象的尺寸。

这也就是,父渲染对象将约束自上而下传递给子节点,子渲染对象将尺寸自下而上 赋予父节点的原理。到这里大家应该对布局约束有了更深的认识,看那本文到这里就结束了,谢谢观看,明天见~