Flutter布局指南之Box套盒子

676 阅读5分钟

对于写过Flutter的开发者来说,我敢肯定,大部分的开发者都不能准确预测这次Hot Reload之后,布局是否是自己想要的结果。Flutter的布局与Native的布局方式非常不同,所以,了解Flutter这茫茫多的布局组件,是我们准确布局的基础。

在Flutter中,有一堆Box布局组件,它们可以用来更加精确的调整布局,下面我们就来看看这些Box都有哪些作用。

ConstrainedBox

ConstrainedBox用于限制Child Widget的尺寸约束,例如:

  • 让Text最宽100,从而实现多行
  • 固定Widget最大最小尺寸
body: Center(
  childColumn(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      ConstrainedBox(
        constraintsconst BoxConstraints(
          maxWidth100,
        ),
        childconst Text(
          'xuyisheng',
          textAlign: TextAlign.center,
          styleTextStyle(
            fontSize50,
          ),
        ),
      ),
      ConstrainedBox(
        constraintsconst BoxConstraints(
          minHeight100,
        ),
        childElevatedButton(
          childconst Text('Flutter'),
          onPressed: () {},
        ),
      ),
    ],
  ),
)` 

效果如图所示。

UnconstrainedBox

UnconstrainedBox的作用正好和ConstrainedBox相反。它可以破除组件本身的约束规则,从而更方便的进行布局。

例如下面这个例子:

Center(
  childContainer(
    color: Colors.blue,
    width300,
    height300,
    childContainer(
      width100,
      height100,
      color: Colors.red,
    ),
  ),
)

由于Container的布局规则,内部的Container被设置为父Widget尺寸,从而忽略了子Widget的尺寸设置,所以,这里使用UnconstrainedBox来解除这种约束:

Center(
  childContainer(
    color: Colors.blue,
    width300,
    height300,
    childUnconstrainedBox(
      childContainer(
        width100,
        height100,
        color: Colors.red,
      ),
    ),
  ),
)

从而可以让内部的Container按照自身尺寸进行布局。

更加灵活一点,我们还可以选择保留某一方向上的约束:constrainedAxis: Axis.horizontal。

SizedBox

SizedBox有下面几个使用场景:

  • 当你需要一个确切尺寸的Widget时,通过SizedBox来进行约束
  • 在父容器中撑满剩余空间
  • 在没有child的情况下,对空间做分割

场景1:

SizedBox(
  width200,
  height200,
  childText(
    'xuyisheng',
    textAlign: TextAlign.center,
    styleTextStyle(
      fontSize50,
    ),
  ),
)

场景2:某方向上的double.infinity,会在父级允许的尺寸下尽可能多的拓展。

Center(
  childContainer(
    color: Colors.red,
    width200,
    height200,
    childCenter(
      childContainer(
        color: Colors.blue,
        child: const SizedBox(
          width: double.infinity,
          height100,
          childText(
            'xuyisheng',
            textAlign: TextAlign.center,
            styleTextStyle(
              fontSize50,
            ),
          ),
        ),
      ),
    ),
  ),
)

展示效果如图所示。

如果width和height方向上都是撑满父Widget的剩余空间,那么可以使用SizedBox.expand来简写。

还有一个便捷方法——SizedBox.shrink,它的作用是让尺寸在父容器的约束下尽可能的小,如果父容器不设置minWidth或者minHeight,那么它的尺寸就是0,这个属性通常和BoxConstraints一起配合使用。

FractionallySizedBox

这是Flutter给你提供的一个百分百布局工具。通常用于在父容器中,按照百分比来进行布局。

Center(
  childContainer(
    color: Colors.red,
    width200,
    height200,
    child: const FractionallySizedBox(
      widthFactor0.5,
      heightFactor0.5,
      childText(
        'xuyisheng',
        textAlign: TextAlign.center,
        styleTextStyle(
          fontSize50,
        ),
      ),
    ),
  ),
)

展示效果如图所示。

和SizedBox一样,它也可以用于作为Widget直接的间隔,只不过它使用的是百分比作为单位,总量是父容器的尺寸。

如果使用Flexible组件包裹FractionallySizedBox,那么就可以适用于Row和Column。

要注意的是,widthFactor和heightFactor是可以大于1的,也就是说,子Widget可以超出父容器展示。

LimitedBox

当Widget没有父级来限制它们的尺寸时,如何在Widget上设置它的默认大小呢?这就需要使用到LimitedBox了。

LimitedBox只在父容器没有提供尺寸约束时,对子Widget的尺寸进行默认约束,在在Listview和Column、Row中是非常有用的。

如果外部容器对Child设置了尺寸约束,那么LimitedBox将不会生效

例如下面这个场景:

ListView(
  children: [
    for (var i = 0i < 100i++)
      Container(
        margin: const EdgeInsets.all(8),
        color: Colors.green,
      ),
  ],
)`

由于Listview中无尺寸约束,所以Container是不会展示出来的,这时候就需要使用LimitedBox。

ListView(
  children: [
    for (var i = 0; i < 100; i++)
      LimitedBox(
        maxHeight100,
        childContainer(
          margin: const EdgeInsets.all(8),
          color: Colors.green,
        ),
      ),
  ],
)` 

一句话总结LimitedBox的作用:在不受限制的环境中,为其子元素提供默认尺寸。

FittedBox

在Flutter中,Widget之间可以任意堆叠、嵌套,所以,当子Widget的尺寸与父Widget尺寸不一致时,就会产生一些奇怪的样式,FittedBox就是用来处理这种场景的。

Center(
  childContainer(
    width200,
    height200,
    color: Colors.red,
    childText(
      'xuyishengxuyisheng',
      styleTextStyle(
        fontSize50,
      ),
    ),
  ),
)` 

效果如图所示。

Text会因为父容器尺寸的限制而自动换行,下面我们给它加上FittedBox。

Center(
  childContainer(
    width200,
    height200,
    color: Colors.red,
    childFittedBox(
      childText(
        'xuyishengxuyisheng',
        styleTextStyle(
          fontSize50,
        ),
      ),
    ),
  ),
)` 

效果如图所示。

可以发现,FittedBox默认的fit是contain,所以内容被完整的一行显示了,与FontSize无关,这个就可以很方便的自适应修改文字大小。

当然,你还可以设置别的fit方式,详细的可以参考Flutter Dojo中的例子。fit属性是非常有用的一个属性,例如当我们设置FittedBox后,文字会在设备中自动显示为一行,但是在横竖屏切换时,Text会自动修改字体大小,来适配contain的效果,如果你想让它保存当前的文字Size,那么可以设置Fit为scaleDown,这样的话,它就会以最小尺寸来进行适配,当空间足够的时候,就不会自动放大字体大小了。

FittedBox中还可以设置alignment,从而控制剩余空间中子Widget的对齐方式。

简而言之,FittedBox就是一个让Child可以适配Parent的组件。

Flexible

准确来说,Flexible不算是Box类布局容器,但它和Box布局方式息息相关,所以这里一起说了。

Flexible通常在Column或者Row中使用,借助Flexible,可以让Column和Row中的元素根据Flex比例进行布局。

Column(
  children: [
    Flexible(
      flex3,
      childContainer(
        color: Colors.cyan,
      ),
    ),
    Flexible(
      flex2,
      childContainer(
        color: Colors.green,
      ),
    ),
    Flexible(
      flex1,
      childContainer(
        color: Colors.purple,
      ),
    ),
  ],
)` 

同时,当子Widget有尺寸约束时,可以使用fit属性来控制Flex选择怎样的约束,如果是FlexFit.tight,那么Flexible将严格按照Flex布局,而忽略子Widget的尺寸约束,如果是FlexFit.loose,则会将尺寸设置为子Widget的尺寸。

OverflowBox

对于Flutter的子父Widget来说,子Widget一般都是限制于父Widget的尺寸约束之下,但如果一定要让子Widget超过父Widget的渲染区域,那么就可以通过OverflowBox来实现。

Container(
  color: Colors.blue,
  width200,
  height200,
  padding: const EdgeInsets.all(12.0),
  childOverflowBox(
    alignment: Alignment.topLeft,
    maxWidth300.0,
    maxHeight500.0,
    childContainer(
      color: Colors.red,
      width400.0,
      height400.0,
    ),
  ),
)` 

效果如图所示。