Flutter學習篇(四)—— 尺寸解惑

導航

前言

最近,筆者在寫佈局的時候,發現諸如此類的報錯: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

注意紅色部分,若是Column的child的constraint是unbounded的話,就沒法給出真實大小。而對於ListView,垂直高度爲double.infinity,即無限制,因此ListView的constraint是unbounded的。那麼有什麼辦法能夠解決呢,咱們到ListView的源碼去一探究竟吧。

在ListView的源碼搜索unbound關鍵字,很快發現有這麼一個屬性shrinkWrapbash

其中有一句很關鍵的註釋, 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。app

  • 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,以下: ide

猜猜咱們看到了什麼,前言提到的報錯 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經過把主軸進行收縮,這樣一來就可使得主軸方向是肯定的,從而解決問題。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盒約束
Flutter 約束知識

倉庫

點擊flutter_demo,查看完整代碼。

相關文章
相關標籤/搜索