Flutter布局总纲——向下传递束缚,向上传递尺度。

Box束缚

束缚是Flutter布局的中心,在Flutter中,束缚的表现形式是经过Constraints类来完成的,所有的非翻滚布局模型,都经过BoxConstraints来进行束缚,它的代码如下。

Flutter布局指南之约束和尺寸

从上面的代码能够看出,束缚本质上便是「宽」「高」上的「最大」「最小」规模。

BoxConstraints具有传递性,束缚会在组件树上传递,当时Widget会受到来自父级的束缚,一起也会将束缚传递给它的子Widget。

经过一个比如,咱们来了解下束缚是怎么进行传递的。

void main() {
  runApp(
    Container(
      color: Colors.cyan.shade200,
      width: 10,
      height: 10,
      child: Center(
        child: Container(
          color: Colors.red.shade200,
          width: 300,
          height: 300,
          child: FlutterLogo(size: 1000),
        ),
      ),
    ),
  );
}

咱们先提出这样几个问题:

  • 第一个Container的10×10能否收效
  • 第二个Container的300×300能否收效
  • FlutterLogo的1000×1000能否收效

运转结果如下。

Flutter布局指南之约束和尺寸

从运转作用来看,第一个Container的尺度被无视了,第二个Container的尺度收效了,FlutterLogo的尺度也被无视了。那么为什么会这样呢?一图胜千言,跟着下面这张图的线路,咱们能够好好了解下束缚是怎么进行传递的。
Flutter布局指南之约束和尺寸

在Flutter中,每个组件都有自己的布局行为:

  • Root,传递紧束缚,即它的子元素,有必要是设备的尺度,不然Root底子不知道未被撑满的内容该怎么显现
  • Container,在有Child的时分,传递紧束缚,即子元素有必要和它相同大,不然Container也不知道该怎么放置Child
  • Center,将紧束缚转换为松束缚,Center能够将父级的紧束缚,变松,这样它的子元素能够挑选放置在居中的方位,而子元素详细有多大?只要不超越父容器巨细都能够

这便是Flutter布局的中心思维。

父容器一层层向下先传递束缚,即最大最小宽高,子元素根据父元素的束缚,修改自己的束缚,并继续向下传递,到根子节点之后,将根据束缚修正后得到的尺度,返回给父级,直到根节点。

这也是为什么有些元素设置的尺度,会被束缚吃掉的原因。在Flutter中,元素的尺度,在不同的父级组件下,会展示出不同的束缚作用,然后展示出不同的款式,这是和Android View十分不同的一点。

更多的示例,咱们能够参阅下面几篇文章。
Flutter布局综述
Flutter布局指南之深化了解BoxConstraints
Flutter布局指南之Box套盒子

调试束缚

不同组件的束缚行为不相同,咱们平时能够经过下面两种方法来调试,获取组件当时的束缚。

LayoutBuilder

首先咱们能够经过LayoutBuilder来打印当时Widget的束缚,示例代码如下。

runApp(
  LayoutBuilder(
    builder: (BuildContext context, BoxConstraints constraints) {
      print('Root constraints $constraints');
      return Container(
        color: Colors.cyan.shade200,
        width: 10,
        height: 10,
        child: Center(
          child: LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
            print('Center constraints $constraints');
            return Container(
              color: Colors.red.shade200,
              width: 300,
              height: 300,
              child: FlutterLogo(size: 1000),
            );
          }),
        ),
      );
    },
  ),
);

输出:

flutter: Root constraints BoxConstraints(w=390.0, h=844.0)
flutter: Center constraints BoxConstraints(0.0<=w<=390.0, 0.0<=h<=844.0)

凭借它,咱们就能够很便利的查到当时束缚详细是什么束缚,以及到底是多少束缚。但是这样做仍是有点费事,所以咱们能够凭借Flutter的调试工具。

Flutter Inspector

在Flutter Inspector中,咱们能够查看当时Widget Tree的束缚情况,在Layout Explorer中,能够看到束缚的详细数值,如下所示。

Flutter布局指南之约束和尺寸

在Widget Detail Tree中,咱们还能够看到详细的BoxConstraints对象,如下所示。
Flutter布局指南之约束和尺寸

这种方法比前面打log的方法更加直观便利。

束缚的松与紧

BoxConstraints界说了最大最新规模之后,还界说了两个语义名词——「松束缚」「紧束缚」。

  • 松束缚:minWidth和minHeight都为0的束缚
  • 紧束缚:minWidth和maxWidth持平,并且minHeight和maxHeight持平的束缚

松束缚和紧束缚并不是相对的,它们是能够一起存在的。

对于Child来说,它无法违法父级的布局束缚,就像下面这个比如。

void main() => runApp(
      Container(
        color: Colors.cyan.shade200,
        width: 10,
        height: 10,
        child: FlutterLogo(size: 1000),
      ),
    );

Container�虽然对Child施加了紧束缚,但由于Root对Container施加的也是紧束缚,所以Container的束缚失效了。

不同的Widget,在不同的场景下所发生的束缚是不相同的,对于Container来说:

  • 有Child就挑选Child的尺度(有设置alignment时会将束缚放松)
  • 没有Child就撑满父级空间(父级空间为Unbound时,尺度为0)

打破束缚束缚

由于父组件的紧束缚会强制子组件也施赶紧束缚,这种束缚在某些场景下不太灵敏,所以Flutter提供了UnconstrainedBox�来解除这种束缚,仍是上面的比如,咱们加上UnconstrainedBox�。

void main() => runApp(
  UnconstrainedBox(
        child: Container(
          color: Colors.cyan.shade200,
          width: 10,
          height: 10,
          child: FlutterLogo(size: 1000),
        ),
      ),
);

�这个时分,Container施加的新的紧束缚(10,10)就能够收效了。
除此之外,还有一些组件,例如——Align。

void main() => runApp(
  Align(
        alignment: Alignment.topLeft,
        child: Container(
          color: Colors.cyan.shade200,
          width: 100,
          height: 100,
          child: FlutterLogo(size: 1000),
        ),
      ),
);

�这些相似的组件,也会将父Widget的紧束缚放松为松束缚,从它们的运用方法上就能够了解它们的行为,由于如果不能去除紧束缚的话,相似对齐的需求就没有办法完成了。

Flex束缚

前面看的都是单个Child的容器布局,这类布局方法,咱们称之为Box布局,相对而言,相似Column和Row这样的布局方法,咱们称之为Flex布局。

Row本质上是direction: Axis.horizontal的Flex Widget,Column本质上是direction: vertical的Flex Widget。

在Column和Row中,有两类束缚组件,一种是清晰知道本身尺度的Widget,例如Text、Button,有束缚的Container等,还有一种是弹性组件,例如Expanded和Flexible等组件。

所以Column和Row在布局时,选用的是Flex束缚进行布局,布局依照下面的规则进行。

  • 先依照unbound束缚,核算所有非Flex布局的组件的尺度
  • 再对Flex组件进行布局,布局根据flex特点来分配剩下空间(Flex组件向下传递紧束缚)

上面的布局规则是针对主轴来说的,Flex的主轴束缚为unbound,Flex束缚在交叉轴上会设置为松束缚(如果crossAxisAlignment设置为stretch,那么会变成紧束缚)。

以Row为例,Row对child的束缚会修改为松束缚,然后不会束缚child在主轴方向上的尺度,所以当Row内的Child宽度大于屏幕宽度时,就会发生内容溢出的正告。

所以咱们通常会在Flex组件中运用Expanded组件来避免内容的溢出。Expanded组件会将主轴方向上的Child施赶紧束缚,然后避免溢出。咱们查看下Expanded的源码

Flutter布局指南之约束和尺寸

能够发现,Expanded其实便是Flexible�的封装,仅仅将fit设置为了FlexFit.tight�。所以,下面的代码是等价的。

Expanded(child: ColoredBox(color: Colors.yellow)),
Flexible(child: ColoredBox(color: Colors.cyan), fit: FlexFit.tight),

咱们再来看下面的这个比如。

Center(
  child: SizedBox(
    height: 100,
    child: Row(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        Container(width: 50, color: Colors.red),
        Expanded(child: ColoredBox(color: Colors.yellow)),
        Flexible(child: ColoredBox(color: Colors.cyan), fit: FlexFit.loose),
        Container(width: 50, color: Colors.purple),
      ],
    ),
  ),
)

在上面的代码中,咱们得到了下面的结果。

Flutter布局指南之约束和尺寸

如果把Flexible中的fit改为FlexFit.tight�,那么作用如下。
Flutter布局指南之约束和尺寸

结合这个比如,咱们能够更好的了解Flex的布局模型(先核算非Flex Widget的尺度,再将剩下空间依照Flex进行拆分,所以不论fit怎么,其尺度是固定的)。

Expanded�组件便是根据FlexFit.tight的封装,它用于撑满剩下空间,FlexFit.loose虽然没有封装的组件,但它的运用也很常见,咱们能够很简单的完成Android束缚布局中的ConstraintedWidth这样的作用。

Container(width: 50, color: Colors.red),
Expanded(child: ColoredBox(color: Colors.yellow)),
Flexible(child: ColoredBox(color: Colors.cyan, child: Text('data')), fit: FlexFit.loose),
Container(width: 50, color: Colors.purple),

Flutter布局指南之约束和尺寸

当内容变长时,会束缚其最大宽度,如下所示。
Flutter布局指南之约束和尺寸

Wrap束缚

Wrap组件与Flex组件有些相似,但又有些不同,拿前面的比如来说,Row中的child组件如果超越了屏幕宽度,就会导致内容溢出,由于Flex组件其主轴上的束缚为unbound,而Wrap组件,其主轴上的束缚会被修改为松束缚,交叉轴上的束缚会被改为unbound,这样就能够完成流式的布局作用。

所以Wrap组件和Flex组件在本质上是相反的两种布局行为。

Stack布局束缚

Stack是一类比较特别的层叠组件,它的束缚方法和Column、Row相似,但又不完全相同,在Stack中,相同也分为两类组件,一类是Positioned组件,一类是非Positioned组件,然后Stack会依照下面的布局方法进行。

  • 先依照非Positioned组件的尺度进行核算,将本身尺度设置为非Positioned组件的最大值
  • 再对Positioned组件进行布局,依照方位束缚进行布局,但不能再改动Stack的尺度

特别场景下:如果悉数是Positioned组件,那么Stack将取得父容器的最大束缚,如果悉数是非Positioned组件,那么Stack将取得子元素的最大尺度

从束缚上来说,Stack相同会放松父布局的紧束缚,其行为和Align是相似的。

Stack的Fit特点

Stack有个Fit特点,需求特别注意,它能够设置为:

  • StackFit.loose:向下传递送束缚(默许行为)
  • StackFit.expand:向下传递紧束缚
  • StackFit.passthrough:将父级的束缚向下传递

Stack设置Fit特点后,并不会对本身尺度有影响,它改动的是Child的尺度,经过修改束缚是紧束缚仍是松束缚,来影响Child的尺度,然后改动自己的尺度。所以Stack的Fit特点默许是loose,即松束缚。如果设置为expand,那么Stack将向Child传递一个紧束缚。

IntrinsicHeight与IntrinsicWidth

这两个组件在Flutter中的运用十分少,但在某些极点的场景下,却是十分有用的,它的主要功能,便是为了完成相似Android束缚布局中的Barrier的功能。

咱们以IntrinsicWidth为例,来看看它的作用。

Column(
  mainAxisSize: MainAxisSize.min,
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    Container(height: 100, width: 50, color: Colors.red),
    Container(height: 100, color: Colors.blue),
  ],
)

�这个布局作用如下所示。

Flutter布局指南之约束和尺寸

由于蓝色的Container没有width束缚,所以它在交叉轴方向上的巨细是父布局最大尺度,这时分,咱们给它加上IntrinsicWidth�。

IntrinsicWidth(
  child: Column(
    mainAxisSize: MainAxisSize.min,
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      Container(height: 100, width: 50, color: Colors.red),
      Container(height: 100, color: Colors.blue),
    ],
  ),
)

�这时分作用如下所示。

Flutter布局指南之约束和尺寸

能够发现,蓝色Container被强制加上了赤色Container的尺度束缚,这便是IntrinsicWidth的作用——在宽度或者高度上施赶紧束缚来束缚Child的尺度,其束缚来自于Child的固有宽度或者高度。

凭借这个特性,咱们能够很便利的完成一些作用。

Column(
  mainAxisSize: MainAxisSize.min,
  mainAxisAlignment: MainAxisAlignment.center,
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: const [
    OutlinedButton(onPressed: null, child: Text('btn1')),
    OutlinedButton(onPressed: null, child: Text('btn222')),
    OutlinedButton(onPressed: null, child: Text('btn333333')),
  ],
)

�这个比如,展示了3个不同长度的Button,经过stretch�特点将其交叉轴的长度设置为父布局的最大尺度,经过添加IntrinsicWidth,咱们能够完成下面的作用。

IntrinsicWidth(
  child: Column(
    mainAxisSize: MainAxisSize.min,
    mainAxisAlignment: MainAxisAlignment.center,
    crossAxisAlignment: CrossAxisAlignment.stretch,
    children: const [
      OutlinedButton(onPressed: null, child: Text('btn1')),
      OutlinedButton(onPressed: null, child: Text('btn222')),
      OutlinedButton(onPressed: null, child: Text('btn333333')),
    ],
  ),
)

�这样的作用如下所示。

Flutter布局指南之约束和尺寸

再例如这个比如。

Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    Container(color: Colors.red, height: 50),
    Container(color: Colors.blue, height: 50, child: Text('data' * 8)),
    Container(width: 100, color: Colors.yellow, height: 50),
  ],
)

作用如下。

Flutter布局指南之约束和尺寸

便是由于赤色Container没有宽度束缚,所以撑满了,咱们现在想让赤色Container跟从蓝色Container的宽度而变化,那就能够运用IntrinsicWidth。
Flutter布局指南之约束和尺寸

更多「Flutter」「Android」「Kotlin」内容,请关注我的微信公众号——【群英传】