尺度束缚类容器用于束缚容器巨细,Flutter中提供了多种这样的容器,如ConstrainedBoxSizedBoxUnconstrainedBoxAspectRatio 等,本节将介绍一些常用的。 Flutter 中有两种布局模型:

  • 依据 RenderBox 的盒模型布局。
  • 依据 Sliver ( RenderSliver ) 按需加载列表布局。

两种布局方法在细节上略有差异,但大体流程相同,布局流程如下:

  1. 上层组件向基层组件传递束缚(constraints)条件。
  2. 基层组件确认自己的巨细,然后告知上层组件。留意基层组件的巨细必须符合父组件的束缚。
  3. 上层组件确认基层组件相对于本身的偏移和确认本身的巨细(大多数情况下会依据子组件的巨细来确认本身的巨细)。

比方,父组件传递给子组件的束缚是“最大宽高不能超过100,最小宽高为0”,假如咱们给子组件设置宽高都为200,则子组件最终的巨细是100*100,由于任何时候子组件都必须先恪守父组件的束缚,在此基础上再运用子组件束缚(相当于父组件的束缚和本身的巨细求一个交集)。

本节咱们主要看一下盒模型布局,盒模型布局组件有两个特色:

  1. 组件对应的烘托目标都承继自 RenderBox 类。类的实例。
  2. 在布局过程中父级传递给子级的束缚信息由 BoxConstraints 描述。

BoxConstraints

BoxConstraints 是盒模型布局过程中父烘托目标传递给子烘托目标的束缚信息,包括最大宽高信息,子组件巨细需求在束缚的规模内,BoxConstraints 默认的结构函数如下:

const BoxConstraints({
  this.minWidth = 0.0, //最小宽度
  this.maxWidth = double.infinity, //最大宽度
  this.minHeight = 0.0, //最小高度
  this.maxHeight = double.infinity //最大高度
})

它包括 4 个属性,BoxConstraints还界说了一些快捷的结构函数,用于快速生成特定束缚规矩的BoxConstraints,如BoxConstraints.tight(Size size),它能够生成固定宽高的束缚;BoxConstraints.expand()能够生成一个尽可能大的用以填充另一个容器的BoxConstraints。除此之外还有一些其它的快捷函数,读者能够查看类界说。别的咱们会在后面深化介绍布局原理时还会评论 Constraints,在这里,读者只需知道父级组件是经过 BoxConstraints 来描述对子组件可用的空间规模即可。

ConstrainedBox

ConstrainedBox用于对子组件添加额外的束缚。例如,假如你想让子组件的最小高度是80像素,你能够运用const BoxConstraints(minHeight: 80.0)作为子组件的束缚。

咱们完成一个最小高度为50,宽度尽可能大的赤色容器。

ConstrainedBox(
  constraints: BoxConstraints(
    minWidth: double.infinity, //宽度尽可能大
    minHeight: 50.0 //最小高度为50像素
  ),
  child: Container(
    height: 5.0, 
    child: redBox ,
  ),
)

运转作用:

37、Flutter之组件布局原理与约束(constraints)

能够看到,咱们尽管将Container的高度设置为5像素,可是最终却是50像素,这正是ConstrainedBox的最小高度束缚收效了。假如将Container的高度设置为80像素,那么最终赤色区域的高度也会是80像素,由于在此示例中,ConstrainedBox只束缚了最小高度,并未束缚最大高度。

SizedBox

SizedBox用于给子元素指定固定的宽高,如:

SizedBox(
  width: 80.0,
  height: 80.0,
  child: redBox
)

实践上SizedBox仅仅ConstrainedBox的一个定制,上面代码等价于:

ConstrainedBox(
  constraints: BoxConstraints.tightFor(width: 80.0,height: 80.0),
  child: redBox, 
)

BoxConstraints.tightFor(width: 80.0,height: 80.0)等价于:

BoxConstraints(minHeight: 80.0,maxHeight: 80.0,minWidth: 80.0,maxWidth: 80.0)

而实践上ConstrainedBoxSizedBox都是经过RenderConstrainedBox来烘托的,咱们能够看到ConstrainedBoxSizedBoxcreateRenderObject()方法都回来的是一个RenderConstrainedBox目标:

@override
RenderConstrainedBox createRenderObject(BuildContext context) {
  return RenderConstrainedBox(
    additionalConstraints: ...,
  );
}

多重束缚

假如某一个组件有多个父级ConstrainedBox束缚,那么最终会是哪个收效?咱们看一个比方:

ConstrainedBox(
  constraints: BoxConstraints(minWidth: 60.0, minHeight: 60.0), //父
  child: ConstrainedBox(
    constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子
    child: redBox,
  ),
)

上面咱们有父子两个ConstrainedBox,他们的束缚条件不同,运转后作用如图所示:

37、Flutter之组件布局原理与约束(constraints)

最终显现作用是宽90,高60,也就是说是子ConstrainedBoxminWidth收效,而minHeight是父ConstrainedBox收效。单凭这个比方,咱们还总结不出什么规则,咱们将上例中父子束缚条件换一下:

ConstrainedBox(
  constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),
  child: ConstrainedBox(
    constraints: BoxConstraints(minWidth: 60.0, minHeight: 60.0),
    child: redBox,
  )
)

运转作用如图:

37、Flutter之组件布局原理与约束(constraints)

最终的显现作用依然是90,高60,作用相同,但意义不同,由于此时minWidth收效的是父ConstrainedBox,而minHeight是子ConstrainedBox收效。

经过上面示例,咱们发现有多重束缚时,对于minWidthminHeight来说,是取父子中相应数值较大的。实践上,只要这样才能保证父束缚与子束缚不抵触。

UnconstrainedBox

尽管任何时候子组件都必须恪守其父组件的束缚,但前提条件是它们必须是父子关系,假如有一个组件 A,它的子组件是B,B 的子组件是 C,则 C 必须恪守 B 的束缚,一起 B 必须恪守 A 的束缚,可是 A 的束缚不会直接束缚到 C,除非B将A对它自己的束缚透传给了C。 运用这个原理,就能够完成一个这样的 B 组件:

  1. B 组件中在布局 C 时不束缚C(能够为无限大)。
  2. C 依据本身实在的空间占用来确认本身的巨细。
  3. B 在恪守 A 的束缚前提下结合子组件的巨细确认本身巨细。

而这个 B组件就是 UnconstrainedBox 组件,也就是说UnconstrainedBox 的子组件将不再遭到束缚,巨细完全取决于自己。一般情况下,咱们会很少直接运用此组件,但在”去除”多重束缚的时候或许会有协助,咱们看下下面的代码:

ConstrainedBox(
  constraints: BoxConstraints(minWidth: 60.0, minHeight: 100.0),  //父
  child: UnconstrainedBox( //“去除”父级束缚
    child: ConstrainedBox(
      constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子
      child: redBox,
    ),
  )
)

上面代码中,假如没有中间的UnconstrainedBox,那么依据上面所述的多重束缚规矩,那么最终将显现一个90100的赤色框。可是由于UnconstrainedBox “去除”了父ConstrainedBox的束缚,则最终会依照子ConstrainedBox的束缚来绘制redBox,即9020:

37、Flutter之组件布局原理与约束(constraints)

可是请留意,UnconstrainedBox对父组件束缚的“去除”并非是真实的去除:上面比方中尽管赤色区域巨细是9020,但上方依然有80的空白空间。也就是说父束缚的minHeight(100.0)依然是收效的,只不过它不影响最终子元素redBox的巨细,但依然仍是占有相应的空间,能够认为此时的父ConstrainedBox是作用于子UnconstrainedBox上,而redBox只受子ConstrainedBox束缚.

那么有什么方法能够彻底去除父ConstrainedBox的束缚吗?答案是否定的!请牢记,任何时候子组件都必须恪守其父组件的束缚,所以在此提示,在界说一个通用的组件时,假如要对子组件指定束缚,那么必定要留意,由于一旦指定束缚条件,子组件本身就不能违背束缚。

在实践开发中,当咱们发现已经运用 SizedBox 或 ConstrainedBox给子元素指定了固定宽高,可是依然没有作用时,几乎能够判定:已经有父组件指定了束缚!举个比方,如 Material 组件库中的AppBar(导航栏)的右侧菜单中,咱们运用SizedBox指定了 loading 按钮的巨细,代码如下:

 AppBar(
   title: Text(title),
   actions: <Widget>[
     SizedBox(
       width: 20, 
       height: 20,
       child: CircularProgressIndicator(
         strokeWidth: 3,
         valueColor: AlwaysStoppedAnimation(Colors.white70),
       ),
     )
   ],
)

运转作用:

37、Flutter之组件布局原理与约束(constraints)

咱们会发现右侧loading按钮巨细并没有发生变化!这正是由于AppBar中已经指定了actions按钮的束缚条件,所以咱们要自界说loading按钮巨细,就必须经过UnconstrainedBox来 “去除” 父元素的束缚,代码如下:

AppBar(
  title: Text(title),
  actions: <Widget>[
    UnconstrainedBox(
      child: SizedBox(
        width: 20,
        height: 20,
        child: CircularProgressIndicator(
          strokeWidth: 3,
          valueColor: AlwaysStoppedAnimation(Colors.white70),
        ),
      ),
    )
  ],
)

运转作用:

37、Flutter之组件布局原理与约束(constraints)

收效了!实践上将 UnconstrainedBox 换成 Center 或许 Align 也是能够的。

别的,需求留意,UnconstrainedBox 尽管在其子组件布局时能够取消束缚(子组件能够为无限大),可是 UnconstrainedBox 本身是受其父组件束缚的,所以当 UnconstrainedBox 随着其子组件变大后,假如UnconstrainedBox 的巨细超过它父组件束缚时,也会导致溢出报错,比方:

Column(
  children: <Widget>[
    UnconstrainedBox(
      alignment: Alignment.topLeft,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Row(children: [Text('xx' * 30)]),
      ),
    ),
 ]

运转作用如下:

37、Flutter之组件布局原理与约束(constraints)

其他束缚容器

除了上面介绍的这些常用的尺度束缚类容器外,还有一些其他的尺度束缚类容器,比方AspectRatio,它能够指定子组件的长宽比、LimitedBox 用于指定最大宽高、FractionallySizedBox 能够依据父容器宽高的百分比来设置子组件宽高等,由于这些容器运用起来都比较简单。

本文正在参与「金石计划 . 瓜分6万现金大奖」