【非常难受】Flutter Container宽高不生效的背后



前言

写Flutter你一定也碰到过给Widget设置了宽高但是不生效的情况,然后你去网上搜或是问AI,解决方式一般是让你包一层Expanded/Center/Align或是设置alignment参数,你一试,不报错了,便继续手头的开发工作,但是下一次还会遇到别的布局问题,要么出现了黄黑条框框,要么直接报错连程序都跑不起来。其实会碰到这些问题的本质是你在用写Web的布局思维写Flutter。


一、Flutter的布局思维应该是怎么样?

这时候我找到了这篇官方文档Understanding constraints
来自里面的一句话:Constraints go down. Sizes go up. Parent sets position. 下面我用自己的理解解释一下这三句话:

  1. 子组件被父组件约束,约束向下传递,说人话,就是包在里面的元素不能比它的框框大嘛。(实际有更复杂的情况,后面细讲)
  2. 然后子组件告诉父组件它的宽高,父组件要决定子组件的position,这是我认为Flutter和Web编程思维最大的不同,在Flutter的世界里元素不能决定自己的position,只能由父组件来决定,【非常难受+1】。
  3. 最后父组件再拿子组件设置的宽高来继续往下绘制子组件。

接下来我们来实践一下,新建一个空项目,首页直接返回一个Container,把宽高设为100

    return Container(
      color: Colors.blue,
      width: 100,
      height: 100,
    );

运行结果会是一个撑满了屏幕的色块,宽高不会生效,根据上面的理论,1.我这个Container没有包父组件,那它的父组件其实就是屏幕,显然宽高100是不会超出屏幕的,所以这条没问题 2. 父组件要决定子组件的position,oh原来是这一步没做。

在这里插入图片描述
那么我们给Container外层加上Center这个父组件以后,就绘制出了位于屏幕中央的宽高100的Container,这个应该很多人已经知道了,只是可能没思考过背后的这层逻辑,父组件一定要提供position信息。

    return Center(
      child: Container(
        color: Colors.blue,
        width: 100,
        height: 100,
      ),
    );

在这里插入图片描述

至此,我突然感觉好像能解决一大半设置宽高不生效的问题了,不就是在父组件里设置子组件的position嘛。然而在我出于严谨考虑去看了Container的官方文档后我才发现我想得太简单了。
在这里插入图片描述

二、一起来看看Container官方文档

Layout behavior这一部分中看到官方列出来的规则,下面简单翻译一下,并附上自己试的代码:

Rule 1:

如果Container没有child、宽高和约束,并且它的父组件提供了无界约束,则 Container 会尝试将尺寸设置得尽可能小。If the widget has no child, no height, no width, no constraints, and the parent provides unbounded constraints, then Container tries to size as small as possible.

那我们就来试试用UnconstrainedBox这个无界约束的框框包住一个没有child、宽高和约束的Container

    /*  Example - Rule 1: 
    如果Container没有child、宽高和约束,并且它的parent提供了无界约束,
    则 Container 会尝试将尺寸设置得尽可能小。*/
    return UnconstrainedBox( // parent提供无界约束
      alignment: Alignment.center, 
      child: Container(
        color: Colors.green,
        // 没有宽高和约束
        // 没有child
      ),
    );

果然运行结果如下,一片漆黑,原来没有宽高和约束情况下的尽可能小,就是直接消失的意思,这个绿色Container是不会被渲染出来的。
在这里插入图片描述

Rule 2:

如果Container没有child和alignment,但有宽高或是约束,则 Container会尽量小以适应这些约束。If the widget has no child and no alignment, but a height, width, or constraints are provided, then the Container tries to be as small as possible given the combination of those constraints and the parent’s constraints.

    /*  Example - Rule 2:
    如果Container没有child和alignment,但有宽高或是约束,
    则 Container会尽量小以适应这些约束。*/
    return Align(
      alignment: Alignment.topLeft,
      child: Container(
        color: Colors.blue,
        width: 100, // 父组件约束为宽高100
        height: 100,
        alignment: Alignment.center,
        child: Container(
          color: Colors.green,
          width: 200, // 有宽高
          height: 200,
          // 没有child
          // 没有aligmnent
        ),
      ),
    );

在这里插入图片描述
可以看到这种情况下虽然设置了200的宽高,但是因为父组件设置了100的宽高作为约束传给了子组件,在这个尽可能小的规则下,最终渲染出了宽高100的绿色Container。

Rule 3:

如果Container没有child、宽高、约束、alignment,但是父组件提供了有界约束,则Container会扩大撑满父组件。If the widget has no child, no height, no width, no constraints, and no alignment, but the parent provides bounded constraints, then Container expands to fit the constraints provided by the parent.

    /*  Example - Rule 3: 
    如果Container没有child、宽高、约束、alignment,
    但是父组件提供了有界约束,
    则Container会扩大撑满父组件。*/
    return Align(
      alignment: Alignment.topLeft,
      child: Container(
        color: Colors.blue,
  		width: 100, // 父组件约束为宽高100
        height: 100,
        alignment: Alignment.center,
        child: Container(
          color: Colors.green,
          // 没有alignment
          // 没有宽高约束
          // 没有child
        ),
      ),
    );

运行结果会和Rule2的一样,虽然没有设置绿色Container的宽高,但是在这种规则下它撑大了自己,渲染出一个宽高100的绿色Container。

Rule 4:

如果Container有alignment,并且父组件提供无界约束,则Container会尝试围绕子元素调整自身大小。If the widget has an alignment, and the parent provides unbounded constraints, then the Container tries to size itself around the child.

    /*  Example - Rule 4:
    如果Container有alignment,
    并且父组件提供无界约束,
    则Container会尝试围绕子元素调整自身大小。*/
    return Align(
      alignment: Alignment.topLeft,
      child: UnconstrainedBox(
        // 父组件提供无界约束
        child: Container(
          color: Colors.green,
          alignment: Alignment.center, // 有alignment
          child: const Text('test'),
        ),
      ),
    );

可以看到绿色Container根据它的子元素Text调整了自己的大小。
在这里插入图片描述

Rule 5:

如果Container有alignment,并且父组件提供有界约束,则Container会撑满父容器,然后根据alignment将子容器放置在其自身内。If the widget has an alignment, and the parent provides bounded constraints, then the Container tries to expand to fit the parent, and then positions the child within itself as per the alignment.

    /*  Example - Rule 5:
    如果Container有alignment,
    并且父组件提供有界约束,
    则Container会撑满父容器,然后根据alignment将子容器放置在其自身内。*/
    return Align(
      alignment: Alignment.topLeft,
      child: Container(
        color: Colors.blue,
        width: 100, // 父组件提供有界约束
        height: 100,
        child: Container(
          color: Colors.green,
          alignment: Alignment.center, // 有alignment
          child: const Text(
            'test',
            style: TextStyle(fontSize: 14),
          ),
        ),
      ),
    );

这一次绿色Container撑满了父组件蓝色的Container所以我们看不到蓝色,为了看清楚alignment的设置结果我把字体调小了一点,可以看到其子组件Text居中排列了
在这里插入图片描述

Rule 6:

Container有child,没有宽高约束、alignment,则Container会把父组件的约束直接传给子组件,然后根据子组件的尺寸调整自身尺寸。 Otherwise, the widget has a child but no height, no width, no constraints, and no alignment, and the Container passes the constraints from the parent to the child and sizes itself to match the child.

    /*  Example - Rule 6:
    Container有child,没有宽高约束、alignment,
    则Container会把父组件的约束直接传给子组件,然后根据子组件的尺寸调整自身尺寸。*/
    return Align(
      alignment: Alignment.topLeft,
      child: Container(
        color: Colors.blue,
        width: 100, // 父组件约束为宽高100
        height: 100,
        child: Container(
          color: Colors.green,
          child: const Text('test'), // 有child
          // 没有宽高、约束
          // 没有alignment
        ),
      ),
    );

运行结果如下,运行过程是绿色Container把蓝色Container的宽高作为约束传给了子组件Text, 这时候Text的宽高就变成了100,然后绿色Container再适应Text的宽高自己渲染成宽高100的方块。
在这里插入图片描述

终于试完了这6个Rules!令人头大,为什么会设计成这样?Flutter官方文档的原话是 “But why does the Container decide that? Simply because that’s a design decision by those who created the Container widget. ” 说发明出Container的人就是这样设计的!你~ 能~ 拿~ 我~ 怎么办嘛~(此处带入罗翔老师的语气)。写到这里我再一次感到【非常难受+2】

三、强行总结一下方法论

虽说熟能生巧,但是Flutter的Widget那么多,咱不可能每个都记下来啊,总归要有一个通用的方法论作为开发指导吧?首先把一里面说的那三条我们再来复习一下,Constraints go down. Sizes go up. Parent sets position. 大方向从这个思路切入还是没问题的。但如果按照这个思路发现运行结果好像不对?还是Flutter官方,提供了一个最原始的方法:看源码。

在这里插入图片描述
继续拿Container举例,我们在编辑器里把鼠标放到Container上按Ctrl+鼠标单击也可以进入源码看到上诉规则部分的代码:

// \flutter\packages\flutter\lib\src\widgets\container.dart
if (child == null && (constraints == null || !constraints!.isTight)) {
      current = LimitedBox(
        maxWidth: 0.0,
        maxHeight: 0.0,
        child: ConstrainedBox(constraints: const BoxConstraints.expand()),
      );
    } else if (alignment != null) {
      current = Align(alignment: alignment!, child: current);
    }
    ...
     if (constraints != null) {
      current = ConstrainedBox(constraints: constraints!, child: current);
    }

是不是【非常难受+3】

总结

作为一个刚刚写了三个月Flutter的菜鸡,我自己还是一知半解的程度,没有办法做更详细的解读,但是我相信存在必合理,万物皆有其规律所在,我还相信实践出真知,等我再写一段时间Flutter有了新的发现再来更新。谢谢你看到这里!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值