導航
前言
最近,筆者在寫佈局的時候,發現諸如此類的報錯:
Vertical viewport was given unbounded height.
Vertical viewport was given unbounded width.
複製程式碼
大概意思就是指沒有限定檢視的高度,寬度。典型的場景如下:
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.
分析
對於Column來講,主軸長度即垂直方向的高度是由MainAxisSize
決定的,MainAxisSize
有兩種型別,分別是min和max,我們到原始碼看看他們的描述:
在ListView的原始碼搜尋unbound關鍵字,很快發現有這麼一個屬性shrinkWrap:
其中有一句很關鍵的註釋, 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。結果不言而喻,自然是顯示正常。那為什麼把屬性設定為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。
- shrikWrap為true的情況:
進入ViewPort之後,發現真實的渲染物件為RenderViewPort,so,繼續前進。
@override
RenderViewport createRenderObject(BuildContext context) {
return (
axisDirection: axisDirection,
crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection),
anchor: anchor,
offset: offset,
cacheExtent: cacheExtent,
);
}
複製程式碼
因為是在測量階段,所以我們先找到performResize,如下:
猜猜我們看到了什麼,前言提到的報錯 Vertical viewport was given unbounded height.。接著再看判斷條件的程式碼: bool get hasBoundedHeight => maxHeight < double.infinity;
複製程式碼
由於ListView的maxHeight為double.infinity,所以自然返回了false,所以才會導致上述的報錯。
- shrikWrap為true的情況:
可以看到使用了RenderShrinkWrappingViewport
這個類,這個類的官方介紹部分如下:
A render object that is bigger on the inside and shrink wraps its children in the main axis.
複製程式碼
收縮主軸上的子檢視,看到這應該就可以大概理解了,ListView原來的主軸即垂直方向是unbounded的,而RenderShrinkWrappingViewport通過把主軸進行收縮,這樣一來就可以使得主軸方向是確定的,從而解決問題。
當然除了這種方式,我們還可以直接通過在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,檢視完整程式碼。