我用装修房子+生活场景的大白话给你讲透,保证你看完再也不会忘,写代码也不会踩坑!
一、先记住Flutter布局的唯一 黄金法则(一句话)
**爸爸(父Widget)给儿子(子Widget)定规矩(约束):你只能在这个范围内长;
儿子告诉爸爸:我实际长这么大(尺寸)。**
- 约束:从上往下传(父→子)
- 尺寸:从下往上传(子→父)
- 核心:父级说了算范围,子级说了算自己具体多大
二、三种约束分别是什么?(人话版)
1. 紧约束(Tight Constraints)
人话翻译:必须刚好这么大,一点都不能变!
- 父级给子级:
最小宽=最大宽且最小高=最大高 - 子级:没有任何选择空间,必须严格按父级给的尺寸渲染
生活例子:
你定制了一个嵌入式 衣柜,刚好卡进墙里的凹槽:
- 凹槽宽1米、高2米(紧约束)
- 衣柜必须刚好宽1米、高2米,不能大也不能小
代码例子:
SizedBox(
width: 100, // 紧约束:必须100宽
height: 100, // 紧约束:必须100高
child: Container(color: Colors.blue), // Container只能是100x100
)
2. 松约束(Loose Constraints)
人话翻译:别超过这个范围,你想多大就多大!
- 父级给子级:
最小宽=0且最小高=0,只给最大宽高 - 子级:可以选0到最大值之间的任意尺寸
生活例子:
你给孩子一个墙角放玩具箱:
- 墙角最大能放1米宽、1米高的箱子(松约束)
- 孩子可以放一个50cm的小箱子,也可以放1米的大箱子,只要不超过1米就行
代码例子:
Center( // Center给子级松约束
child: Container(
width: 50, // Container自己选50宽,不用填满Center
height: 50,
color: Colors.blue,
),
)
3. 无边界约束(Unbounded Constraints)
人话翻译:你想多大就多大,我不管!(但这是个坑)
- 父级给子级:
最大宽/高 = 无限大(double.infinity) - 子级:可以无限延伸
生活例子:
你在无限长的走廊里铺地毯:
- 走廊没有尽头(无边界约束)
- 地毯可以一直铺下去,想铺多长铺多长
代码例子:
ListView( // ListView在滚动方向给子级无边界约束
children: [
// 子Item可以无限延伸
],
)
三、最常见的异常:viewport has unbounded height 到底啥意思?
人话翻译:
“无限大的爸爸”遇到了“想填满爸爸的儿子”,形成了死循环!
核心根因:
- 父级(比如ListView、SingleChildScrollView)给了子级无边界约束(“你想长多长长多长”)
- 子级(比如另一个ListView、Column)又想填满父级的所有空间(“爸爸多大我多大”)
- Flutter懵了:“到底是无限大?还是要填满?我算不出来尺寸了!”
- 于是抛出异常。
最常见的3个踩坑场景+解决办法(人话版)
场景1:Column里直接放ListView(90%的人都踩过)
踩坑代码:
Column( // Column是固定高度的(紧约束)
children: [
Text('标题'),
ListView( // ListView在垂直方向是无边界约束
children: [...],
),
],
)
为什么报错?
- Column:“我高度固定,你(ListView)告诉我你要多高?”
- ListView:“我是无限长的,你(Column)给我多少空间我就占多少!”
- 死循环 → 报错
解决办法(2选1):
-
给ListView定个固定高度:
Column( children: [ Text('标题'), SizedBox( height: 300, // 定死高度:你就300高,别无限长了 child: ListView(...), ), ], ) ``` -
用
Expanded包裹ListView(推荐):Column( children: [ Text('标题'), Expanded( // 意思:Column剩下的空间全给你 child: ListView(...), ), ], ) ```
场景2:SingleChildScrollView里嵌套ListView
踩坑代码:
SingleChildScrollView( // 父级:垂直方向无边界约束
child: ListView( // 子级:垂直方向也是无边界约束
children: [...],
),
)
为什么报错?
- 两个都是“无限长”,Flutter不知道到底听谁的 → 报错
解决办法:
让ListView别自己滚动了,高度根据内容自适应,跟着父级一起滚:
SingleChildScrollView(
child: ListView(
shrinkWrap: true, // 关键:ListView高度根据子Item总高度自适应
physics: NeverScrollableScrollPhysics(), // 关键:关闭ListView自己的滚动
children: [...],
),
)
场景3:自定义RenderObject里用了无限大尺寸
踩坑代码:
// 自定义布局里,给子级传了无限约束,子级又用double.infinity设置尺寸
// 死循环 → 报错
解决办法:
自定义布局时,先判断是不是无边界约束,如果是,给子级设个合理的上限,别真的让它无限大。
四、复杂自定义布局的性能优化(人话版)
核心就是一句话:别做重复工作,别算没用的东西。
-
少嵌套:别一层套一层Row/Column/Stack,能用一个Widget实现的,别拆成好几个。
- 就像装修:能直接在墙上挂画的,别先装个架子再挂画。
-
别每次都重新算布局:布局约束没变的话,就用上次算好的结果,别每帧都重新算一遍。
-
只算看得见的:长列表里,只算屏幕里能看到的Item,看不见的先不算。
-
提前算好静态的:固定不变的尺寸,提前算好存起来,别每次build/layout都重新算。
五、终极极简总结
-
黄金法则:父级给范围(约束),子级给尺寸。
-
三种约束:
- 紧约束:必须刚好这么大
- 松约束:别超范围,随便多大
- 无边界约束:想多大多大(容易踩坑)
-
常见异常:无限大的父级遇到想填满的子级 → 死循环
-
解决办法:定高度、用Expanded、让子级自适应
-
性能优化:少嵌套、别重复算、只算看得见的