最近,筆者在寫佈局的時候,發現諸如此類的報錯:html
Vertical viewport was given unbounded height.
Vertical viewport was given unbounded width.
複製代碼
大概意思就是指沒有限定視圖的高度,寬度。典型的場景以下:git
Column(
children: <Widget>[
ListView(
children: <Widget>[
Container(color:Colors.red, child:Text("1")),
Container(color:Colors.orange, child:Text("2")),
],
),
],
)
複製代碼
在列視圖的子視圖添加了ListView, 就會報上面的錯:
Vertical viewport was given unbounded height.
github
對於Column來說,主軸長度即垂直方向的高度是由MainAxisSize
決定的,MainAxisSize
有兩種類型,分別是min和max,咱們到源碼看看他們的描述: api
在ListView的源碼搜索unbound關鍵字,很快發現有這麼一個屬性shrinkWrap: bash
其中有一句很關鍵的註釋, If the scroll view has unbounded constraints in the [scrollDirection], then [shrinkWrap] must be true. 。若是在滾動方向上,約束沒有限制的話,那麼shrinkWrap應該設置爲true, 回頭看看,ListView的外層是Column,一樣在垂直高度也爲unbounded,因此Column的constraint也爲unbounded,因此咱們按照提示把ListView的shrinkWrap設置爲true。結果不言而喻,天然是顯示正常。if (shrinkWrap) {
return ShrinkWrappingViewport(
axisDirection: axisDirection,
offset: offset,
slivers: slivers,
);
}
return Viewport(
axisDirection: axisDirection,
offset: offset,
slivers: slivers,
cacheExtent: cacheExtent,
center: center,
anchor: anchor,
);
複製代碼
原來是根據shrinkWrap來選擇不一樣的ViewPort。app
@override
RenderViewport createRenderObject(BuildContext context) {
return (
axisDirection: axisDirection,
crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection),
anchor: anchor,
offset: offset,
cacheExtent: cacheExtent,
);
}
複製代碼
由於是在測量階段,因此咱們先找到performResize,以下: ide
猜猜咱們看到了什麼,前言提到的報錯 Vertical viewport was given unbounded height.。接着再看判斷條件的代碼:bool get hasBoundedHeight => maxHeight < double.infinity;
複製代碼
因爲ListView的maxHeight爲double.infinity,因此天然返回了false,因此纔會致使上述的報錯。佈局
RenderShrinkWrappingViewport
這個類,這個類的官方介紹部分以下:A render object that is bigger on the inside and shrink wraps its children in the main axis.
複製代碼
收縮主軸上的子視圖,看到這應該就能夠大概理解了,ListView原來的主軸即垂直方向是unbounded的,而RenderShrinkWrappingViewport經過把主軸進行收縮,這樣一來就可使得主軸方向是肯定的,從而解決問題。post
固然除了這種方式,咱們還能夠直接經過在ListView嵌套一層肯定高度的佈局來解決這個問題:學習
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
height: 100.0, child: ListView(
shrinkWrap: false,
children: <Widget>[
Container(color:Colors.red, child:Text("1")),
Container(color:Colors.orange, child:Text("2")),
],
)
)]
))
複製代碼
因此本質上,解決問題的關鍵就在於:肯定ListView的高度, shrinkWrap也好,container也好,都是爲了給出ListView的具體高度。
其實上面分析了那麼多,牽涉到Flutter的一個基本概念,那就是Constraint,意爲約束。 Flutter的約束是從父節點傳到子節點,子節點根據約束從新調整自身的大小。舉個最簡單例子 :
return Scaffold(
appBar: AppBar(title: Text("佈局測試")),
backgroundColor: Colors.green,
body: Container(
color: Colors.amber,
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
color: Colors.blue,
height: 300.0,
child: Row(mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: <Widget>[
Container(color: Colors.teal, width: 100, height: 100),
Container(color: Colors.purple, width: 100, height: 100),
]),
),
Container(
height: 300,
width: 200,
color: Colors.lime,
child: Column(
children: <Widget>[
Container(color: Colors.purple, width: 100, height: 100),
Container(color: Colors.teal, width: 100, height: 100),
],
),
)
],
)));
}
複製代碼
效果以下:
對於最外層的Column而言,它的父節點約束是屏幕,加上它的垂直方向是max,能夠看到最外層的Column高度是整個屏幕高度(橙色部分),而裏面的Column的約束則是height爲300的Container,因此它的高度是300(黃色部分)。 而對於Row,它的約束條件也是高度300,雖然在水平方向是無限制,可是因爲水平方向用了min,因此Row的寬度跟隨了子節點的總寬度,即200。若是水平方向用了max,那麼Row的寬度則爲屏幕寬度。
出了問題,善從源碼找出爲何😎😆。
點擊flutter_demo,查看完整代碼。