https://flutterchina.club/widgets/layout/

尺寸限制类容器用于限制容器大小,Flutter中提供了多种这样的容器,如ConstrainedBoxSizedBoxUnconstrainedBoxAspectRatio等,本节将介绍一些常用的。

ConstrainedBox

https://api.flutter.dev/flutter/widgets/ConstrainedBox-class.html
ConstrainedBox用于对子组件添加额外的约束。例如,如果你想让子组件的最小高度是80像素,你可以使用const BoxConstraints(minHeight: 80.0)作为子组件的约束。

定义
  1. ConstrainedBox({
  2. Key key,
  3. @required this.constraints,
  4. Widget child,
  5. })

BoxConstraints

BoxConstraints用于设置限制条件,它的定义如下:

  1. const BoxConstraints({
  2. this.minWidth = 0.0, //最小宽度
  3. this.maxWidth = double.infinity, //最大宽度(宽度尽可能大)
  4. this.minHeight = 0.0, //最小高度
  5. this.maxHeight = double.infinity //最大高度
  6. })
  1. ConstrainedBox(
  2. constraints: BoxConstraints(
  3. // maxWidth: double.infinity, //宽度尽可能大
  4. maxWidth: 300.00,
  5. minWidth: 100.0,
  6. minHeight: 100.0,
  7. maxHeight: 300.0,
  8. ),
  9. child: Container(
  10. color: Colors.green,
  11. child: Text('hello flutter'),
  12. ),
  13. );

BoxConstraints还定义了一些便捷的构造函数,用于快速生成特定限制规则的BoxConstraints。

  • BoxConstraints.expand() 可以生成一个尽可能大的用以填充另一个容器的BoxConstraints。

    1. //定义
    2. BoxConstraints.expand({
    3. double? width,
    4. double? height,
    5. }) : minWidth = width ?? double.infinity,
    6. maxWidth = width ?? double.infinity,
    7. minHeight = height ?? double.infinity,
    8. maxHeight = height ?? double.infinity;
  • BoxConstraints.loose()

    1. //定义
    2. BoxConstraints.loose(Size size)
    3. : minWidth = 0.0,
    4. maxWidth = size.width,
    5. minHeight = 0.0,
    6. maxHeight = size.height;
  • BoxConstraints.tight(Size size) 生成给定大小的限制 ```dart //定义 BoxConstraints.tight(Size size) : minWidth = size.width, maxWidth = size.width, minHeight = size.height, maxHeight = size.height;

BoxConstraints.tight(Size(300.0, 100.0));

  1. - **`BoxConstraints.tightFor({width, height})`** 生成给定大小的限制,
  2. 如果没有传参,等同于: `BoxConstraints()` .
  3. ```dart
  4. //定义
  5. BoxConstraints.tightFor({
  6. double? width,
  7. double? height,
  8. }) : minWidth = width ?? 0.0,
  9. maxWidth = width ?? double.infinity,
  10. minHeight = height ?? 0.0,
  11. maxHeight = height ?? double.infinity;
  12. BoxConstraints.tightFor(width: 300.0, height: 100.0);
  • BoxConstraints.tightForFinite()
    1. //定义
    2. tightForFinite({
    3. double width = double.infinity,
    4. double height = double.infinity,
    5. }) : minWidth = width != double.infinity ? width : 0.0,
    6. maxWidth = width != double.infinity ? width : double.infinity,
    7. minHeight = height != double.infinity ? height : 0.0,
    8. maxHeight = height != double.infinity ? height : double.infinity;

多重限制

如果某一个组件有多个父级ConstrainedBox限制,那么最终会是哪个生效?我们看一个例子:

  1. ConstrainedBox(
  2. constraints: BoxConstraints(minWidth: 60.0, minHeight: 60.0), //父
  3. child: ConstrainedBox(
  4. constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子
  5. child: redBox,
  6. )
  7. )

上面我们有父子两个ConstrainedBox,他们的限制条件不同,运行后效果如图5-4所示:
尺寸类其它容器 - 图1
最终显示效果是宽90,高60,也就是说是子ConstrainedBoxminWidth生效,而minHeight是父ConstrainedBox生效。单凭这个例子,我们还总结不出什么规律,我们将上例中父子限制条件换一下:

  1. ConstrainedBox(
  2. constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),
  3. child: ConstrainedBox(
  4. constraints: BoxConstraints(minWidth: 60.0, minHeight: 60.0),
  5. child: redBox,
  6. )
  7. )

运行效果如图5-5所示:
尺寸类其它容器 - 图2
最终的显示效果仍然是90,高60,效果相同,但意义不同,因为此时minWidth生效的是父ConstrainedBox,而minHeight是子ConstrainedBox生效。

通过上面示例,我们发现有多重限制时,对于minWidthminHeight来说,是取父子中相应数值较大的。实际上,只有这样才能保证父限制与子限制不冲突。

思考题:对于maxWidthmaxHeight,多重限制的策略是什么样的呢? 答:父子中相应数值较小的。

UnconstrainedBox

UnconstrainedBox不会对子组件产生任何限制,它允许其子组件按照其本身大小绘制。一般情况下,我们会很少直接使用此组件,但在”去除”多重限制的时候也许会有帮助,我们看下下面的代码:

  1. ConstrainedBox(
  2. constraints: BoxConstraints(minWidth: 60.0, minHeight: 100.0), //父
  3. child: UnconstrainedBox( //“去除”父级限制
  4. child: ConstrainedBox(
  5. constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子
  6. child: redBox,
  7. ),
  8. )
  9. )

上面代码中,如果没有中间的UnconstrainedBox,那么根据上面所述的多重限制规则,那么最终将显示一个90×100的红色框。但是由于UnconstrainedBox “去除”了父ConstrainedBox的限制,则最终会按照子ConstrainedBox的限制来绘制redBox,即90×20:
尺寸类其它容器 - 图3

但是,读者请注意,UnconstrainedBox对父组件限制的“去除”并非是真正的去除:上面例子中虽然红色区域大小是90×20,但上方仍然有80的空白空间。也就是说父限制的minHeight(100.0)仍然是生效的,只不过它不影响最终子元素redBox的大小,但仍然还是占有相应的空间,可以认为此时的父ConstrainedBox是作用于子UnconstrainedBox上,而redBox只受子ConstrainedBox限制,这一点请读者务必注意。

那么有什么方法可以彻底去除父ConstrainedBox的限制吗?答案是否定的!所以在此提示读者,在定义一个通用的组件时,如果要对子组件指定限制,那么一定要注意,因为一旦指定限制条件,子组件如果要进行相关自定义大小时将可能非常困难,因为子组件在不更改父组件的代码的情况下无法彻底去除其限制条件。

在实际开发中,当我们发现已经使用SizedBoxConstrainedBox给子元素指定了宽高,但是仍然没有效果时,几乎可以断定:已经有父元素已经设置了限制!举个例子,如Material组件库中的AppBar(导航栏)的右侧菜单中,我们使用SizedBox指定了loading按钮的大小,代码如下:

  1. AppBar(
  2. title: Text(title),
  3. actions: <Widget>[
  4. SizedBox(
  5. width: 20,
  6. height: 20,
  7. child: CircularProgressIndicator(
  8. strokeWidth: 3,
  9. valueColor: AlwaysStoppedAnimation(Colors.white70),
  10. ),
  11. )
  12. ],
  13. )

上面代码运行后,效果如图5-7所示:
尺寸类其它容器 - 图4
我们会发现右侧loading按钮大小并没有发生变化!这正是因为AppBar中已经指定了actions按钮的限制条件,所以我们要自定义loading按钮大小,就必须通过UnconstrainedBox来“去除”父元素的限制,代码如下:

  1. AppBar(
  2. title: Text(title),
  3. actions: <Widget>[
  4. UnconstrainedBox(
  5. child: SizedBox(
  6. width: 20,
  7. height: 20,
  8. child: CircularProgressIndicator(
  9. strokeWidth: 3,
  10. valueColor: AlwaysStoppedAnimation(Colors.white70),
  11. ),
  12. ),
  13. )
  14. ],
  15. )

运行后效果如图5-8所示:
尺寸类其它容器 - 图5
生效了!

SizedBox 固定宽高

https://docs.flutter.io/flutter/widgets/SizedBox-class.html
SizedBox 一个特定大小的盒子。这个widget强制它的孩子有一个特定的宽度和高度。如果宽度或高度为NULL,则此widget将调整自身大小以匹配该维度中的孩子的大小。

定义:

  1. SizedBox({
  2. Key key,
  3. this.width,
  4. this.height,
  5. Widget child
  6. }) : super(key: key, child: child);
  7. /// Creates a box that will become as large as its parent allows.
  8. const SizedBox.expand({Key key, Widget child})
  9. : width = double.infinity,
  10. height = double.infinity,
  11. super(key: key, child: child);
  12. /// Creates a box that will become as small as its parent allows.
  13. const SizedBox.shrink({Key key, Widget child})
  14. : width = 0.0,
  15. height = 0.0,
  16. super(key: key, child: child);

SizedBox 可以没有子组件,但仍然会占用空间,所以 SizedBox 非常适合控制2个组件之间的空隙。

  1. Column(
  2. children: <Widget>[
  3. Container(height: 30,color: Colors.blue,),
  4. SizedBox(height: 10,),
  5. Container(height: 30,color: Colors.red,),
  6. ],
  7. )

实际上SizedBox只是ConstrainedBox的一个定制。

  1. SizedBox(
  2. width: 100.0,
  3. height: 100.0,
  4. child: Container(
  5. color: Colors.green,
  6. child: Text('hello flutter'),
  7. ),
  8. );
  9. //等同于:
  10. ConstrainedBox(
  11. constraints: BoxConstraints.tightFor(width: 80.0,height: 80.0),
  12. child: A,
  13. );
  14. //等同于:
  15. ConstrainedBox(
  16. BoxConstraints(
  17. minHeight: 80.0,
  18. maxHeight: 80.0,
  19. minWidth: 80.0,
  20. maxWidth: 80.0
  21. ),
  22. child: A,
  23. );

而实际上ConstrainedBoxSizedBox都是通过RenderConstrainedBox来渲染的,我们可以看到ConstrainedBoxSizedBoxcreateRenderObject()方法都返回的是一个RenderConstrainedBox对象:

  1. @override
  2. RenderConstrainedBox createRenderObject(BuildContext context) {
  3. return new RenderConstrainedBox(
  4. additionalConstraints: ...,
  5. );
  6. }

AspectRatio 固定宽高比

https://api.flutter.dev/flutter/widgets/AspectRatio-class.html
一个widget,试图将子widget的大小指定为某个特定的长宽比。定义:

  1. AspectRatio({
  2. Key key,
  3. @required this.aspectRatio,
  4. Widget child,
  5. })

AspectRatio 首先会在布局限制条件允许的范围内尽可能的扩展,Widget的高度是由宽度和比率决定的,类似于 BoxFit 中的 contain,按照固定碧比率去尽量占满区域。

如果在满足所有限制条件过后无法找到一个可行的尺寸,AspectRatio 最终将会优先适应布局限制条件,而忽略所设置的比率。

示例:

  1. Column(
  2. crossAxisAlignment: CrossAxisAlignment.start,
  3. children: [
  4. Container(
  5. color: Colors.green,
  6. child: AspectRatio(
  7. aspectRatio: 3.0,
  8. child: Text('你'),
  9. ),
  10. ),
  11. SizedBox(height: 10.0),
  12. Container(
  13. width: 100.0,
  14. color: Colors.green,
  15. child: AspectRatio(
  16. aspectRatio: 3.0,
  17. child: Text('你'),
  18. ),
  19. ),
  20. SizedBox(height: 10.0),
  21. Container(
  22. height: 60.0,
  23. color: Colors.green,
  24. child: AspectRatio(
  25. aspectRatio: 3.0,
  26. child: Text('你'),
  27. ),
  28. ),
  29. ],
  30. );

image.png

请记住让 AspectRatio 控件确定其子级的大小。
如果把 AspectRatio 设置像 Expanded 这种命令,它将被父级强制扩展。

  1. Column(
  2. crossAxisAlignment: CrossAxisAlignment.start,
  3. children: [
  4. Container(
  5. width: 60.0,
  6. height: 60.0,
  7. color: Colors.green,
  8. child: AspectRatio(
  9. aspectRatio: 3.0, //如果父级宽高都限制了,它就没有效果了
  10. child: Text('你'),
  11. ),
  12. ),
  13. SizedBox(height: 10.0),
  14. Container(
  15. width: 300.0,
  16. height: 60.0,
  17. color: Colors.green,
  18. alignment: Alignment.bottomCenter,
  19. child: AspectRatio(
  20. aspectRatio: 3.0, //如果父级宽高都限制了,它就没有效果了
  21. child: Text('你'),
  22. ),
  23. ),
  24. ],
  25. );

image.png

FractionallySizedBox 固定百分比

https://api.flutter.dev/flutter/widgets/FractionallySizedBox-class.html
一个widget,它把它的子项放在可用空间的一小部分,可以根据父容器宽高的百分比来设置子组件宽高。定义:

  1. FractionallySizedBox({
  2. Key key,
  3. this.alignment = Alignment.center,
  4. this.widthFactor,
  5. this.heightFactor,
  6. Widget child,
  7. })

示例:

  1. Container(
  2. width: 200.0,
  3. height: 200.0,
  4. color: Colors.green,
  5. child: FractionallySizedBox(
  6. alignment: Alignment.topLeft,
  7. widthFactor: 0.5,
  8. heightFactor: 0.2,
  9. child: Container(color: Colors.grey),
  10. ),
  11. ),

image.png

FittedBox 类似图片的fit属性

https://api.flutter.dev/flutter/widgets/FittedBox-class.html
按自己的大小调整其子widget的大小和位置。定义:

  1. FittedBox({
  2. Key key,
  3. this.fit = BoxFit.contain,
  4. this.alignment = Alignment.center,
  5. this.clipBehavior = Clip.hardEdge,
  6. Widget child,
  7. })

示例:

  1. Column(
  2. crossAxisAlignment: CrossAxisAlignment.start,
  3. children: [
  4. Container(
  5. width: 100.0,
  6. height: 100.0,
  7. color: Colors.green,
  8. child: FittedBox(
  9. fit: BoxFit.contain, //fill contain cover fitWidth fitHeight none scaleDown
  10. child: Image.network('https://js.pic88.com/upload/20201117/16055958796074449641'),
  11. ),
  12. ),
  13. SizedBox(height: 10.0),
  14. Container(
  15. width: 100.0,
  16. height: 100.0,
  17. color: Colors.green,
  18. child: FittedBox(
  19. fit: BoxFit.contain,
  20. alignment: Alignment.topLeft,
  21. child: Image.network('https://js.pic88.com/upload/20201117/16055958796074449641'),
  22. ),
  23. ),
  24. ],
  25. );

image.png

Baseline

https://api.flutter.dev/flutter/widgets/Baseline-class.html
根据子项的基线对它们的位置进行定位的widget。定义:

  1. Baseline({
  2. Key key,
  3. @required this.baseline,
  4. @required this.baselineType,
  5. Widget child,
  6. })

示例:

  1. Container(
  2. width: 200.0,
  3. height: 200.0,
  4. color: Colors.green,
  5. child: Baseline(
  6. baseline: 100.0, //文字基线 距父组件顶部 100.0距离
  7. baselineType: TextBaseline.alphabetic, //alphabetic ideographic
  8. child: Text('hello flutter'),
  9. ),
  10. ),

image.png

IntrinsicHeight

https://api.flutter.dev/flutter/widgets/IntrinsicHeight-class.html
一个widget,它将它的子widget的宽度调整其本身实际的宽度。

LimitedBox 指定最大宽高

https://api.flutter.dev/flutter/widgets/LimitedBox-class.html
一个当其自身不受约束时才限制其大小的盒子。定义如下:

  1. LimitedBox({
  2. Key key,
  3. this.maxWidth = double.infinity,
  4. this.maxHeight = double.infinity,
  5. Widget child,
  6. })

一些控件根据父级定义的约束,来设置他们的大小,但是 ListView、Row、Column等控件,很难使其子项的大小保持平衡。当一个控件,没有父级来限制它的大小时,如何给它一个默认的大小呢?

使用 LimitedBox 控件。
仅当 LimitedBox 的父级未绑定时,LimitedBox才会为其子级提供默认大小。
当窗口控件的父级约束大小时,LimitedBox无效;

  1. Column(
  2. crossAxisAlignment: CrossAxisAlignment.start,
  3. children: [
  4. Container(
  5. width: 200.0,
  6. height: 200.0,
  7. color: Colors.green,
  8. child: LimitedBox(
  9. maxWidth: 100, //父级限制了,此处就无效了
  10. maxHeight: 100,
  11. child: Container(color: Colors.grey),
  12. ),
  13. ),
  14. SizedBox(height: 5),
  15. Container(
  16. child: LimitedBox(
  17. maxWidth: 100,
  18. maxHeight: 100,
  19. child: Container(color: Colors.grey),
  20. ),
  21. ),
  22. ],
  23. );

image.png

与滚动方向无限制的 ListView 一起使用时,LimitedBox 的潜力将变得无线。

  1. ListView.builder(
  2. itemCount: 10,
  3. itemBuilder: (context, index) {
  4. return LimitedBox(
  5. maxHeight: 100.0,
  6. child: Container(
  7. child: Container(color: Color(Base.getRandomColor())),
  8. ),
  9. );
  10. },
  11. );

image.png

Offstage 显隐

https://api.flutter.dev/flutter/widgets/Offstage-class.html
一个布局widget,可以控制其子widget的显示和隐藏。定义:

  1. Offstage({ Key key, this.offstage = true, Widget child })

示例:

  1. Column(
  2. crossAxisAlignment: CrossAxisAlignment.start,
  3. children: [
  4. Container(
  5. height: 60,
  6. color: Colors.green,
  7. child: Offstage(
  8. offstage: true, //默认为true,虽然看不见了,但是还占位
  9. child: Text('hello'),
  10. ),
  11. ),
  12. SizedBox(height: 5),
  13. Container(
  14. height: 60,
  15. color: Colors.green,
  16. child: Offstage(
  17. offstage: false,
  18. child: Text('hello'),
  19. ),
  20. ),
  21. ],
  22. );

image.png

OverflowBox 溢出

https://api.flutter.dev/flutter/widgets/OverflowBox-class.html
对其子项施加不同约束的widget,它可能允许子项溢出父级。定义:

  1. OverflowBox({
  2. Key key,
  3. this.alignment = Alignment.center,
  4. this.minWidth,
  5. this.maxWidth,
  6. this.minHeight,
  7. this.maxHeight,
  8. Widget child,
  9. }) : super(key: key, child: child);

示例:

  1. Column(
  2. crossAxisAlignment: CrossAxisAlignment.start,
  3. children: [
  4. Container(
  5. height: 40,
  6. color: Colors.green,
  7. child: OverflowBox(
  8. maxHeight: 40,
  9. child: Text('hello\nhello\nhello\nhello\nhello\nhello'),
  10. ),
  11. ),
  12. SizedBox(height: 50),
  13. Container(
  14. height: 40,
  15. color: Colors.green,
  16. child: OverflowBox(
  17. alignment: Alignment.topCenter,
  18. // maxHeight: double.infinity,
  19. maxHeight: 55,
  20. child: Text('hello\nhello\nhello\nhello\nhello\nhello'),
  21. ),
  22. ),
  23. ],
  24. );

image.png

SizedOverflowBox

一个特定大小的widget,但是会将它的原始约束传递给它的孩子,它可能会溢出。定义:

  1. SizedOverflowBox({
  2. Key key,
  3. @required this.size,
  4. this.alignment = Alignment.center,
  5. Widget child,
  6. })

示例:

  1. Column(
  2. crossAxisAlignment: CrossAxisAlignment.start,
  3. children: [
  4. Container(
  5. height: 40,
  6. color: Colors.green,
  7. child: SizedOverflowBox(
  8. alignment: Alignment.topCenter,
  9. size: Size(100, 40),
  10. child: Text('hello hello hello hello hello hello hello hello'),
  11. ),
  12. ),
  13. SizedBox(height: 50),
  14. Container(
  15. height: 40,
  16. color: Colors.green,
  17. child: SizedOverflowBox(
  18. alignment: Alignment.topCenter,
  19. size: Size(100, 40),
  20. child: Text('hello\nhello\nhello\nhello\nhello\nhello'),
  21. ),
  22. ),
  23. ],
  24. );

image.png

CustomSingleChildLayout

https://api.flutter.dev/flutter/widgets/CustomSingleChildLayout-class.html
一个自定义的拥有单个子widget的布局widget。