做 Android 的朋友都知道,在 Android 中,限定子元素大小的方法有三種,分別是 match_parent 、 wrap_content 、 fixed size 。如果使用 ConstraintLayout,還會有 match_constraint 。
這些值被設置到子元素的 layout_width 和 layout_height,由父元素解析生成 LayoutParams 設置給子元素,並在父元素的 measure 過程中使用它們以及父元素得到的 measureSpec 來確定子元素的大小。雖然子元素的大小最終由父元素決定,但父元素幾乎不會違背子元素對自身大小的期望。因此妳可以認為在 Android 中子元素的大小可以由子元素自己決定 。子元素想要什麽大小,給自身的 layout_width 和 layout_height 指定不同的值即可。
而在 Flutter 中,就完全不壹樣了。Flutter 積極組合的設計,希望壹切布局都盡量通過組合的方式來實現。組合優於繼承這是真理,但 Flutter 卻走向了極端。連指定子元素的大小、padding、margin、translate、background 等等都需要通過嵌套來實現,這就是導致嵌套地獄的根源。
以指定子元素的大小為例,妳需要給子元素套壹層 SizedBox,通過它來限定子元素的大小。基於此,妳可以理解為在壹般情況下, Flutter 中子元素的大小無法由子元素自己決定,始終由其父元素決定 ,這和 Android 有本質的區別。
Flutter 也沒有提供 match_parent 來讓子元素撐滿父元素, wrap_content 來讓子元素內容有多大就撐多大。但是它提供了 double.infinity。我們壹般使用它來讓子元素撐滿父元素。但其實 double.infinity 也用來表示 wrap_content 。這得從 Flutter 的布局過程說起。
Flutter 的布局過程總結起來就三句話:
這裏有壹個很重要的點是, 子元素的大小永遠不能違背父元素的約束 ,否則就會拋異常。子元素為自身的 size 賦值時,會觸發以下檢測:
為了避免這個異常,子元素在設置自身 size 之前,會先嘗試滿足父元素的約束:
這樣就能保證設置的 size 永遠滿足父元素的約束,不引發異常。
當妳使用 double.infinity 嘗試撐滿父元素時,這裏的 contentWidth 值為 double.infinity,但 constraints.constrainWidth 會返回父元素的寬度,這就起到了 match_parent 的作用。這裏的隱含意思是子元素的大小不可能真的為無限大,因為沒有任何壹個 UI 框架能渲染無限大的東西,因為那意味著無限大的資源消耗。
wrap_content 又是咋回事呢?其實也是同樣的道理。
當父元素對子元素沒有大小要求時,會向子元素傳遞寬松約束,即只限制最大的大小,壹般為父元素的大小。意思是子元素想要多大就給多大,但不能超過自身大小。但有些 Widget 就沒有這個限制,它們允許子元素的大小超過自己的大小,因此它們給子元素傳遞的最大大小為 double.infinity。但子元素測量自己時,發現父元素給的最大大小為 double.infinity,不可能真的將自身大小設置為 double.infinity,因為 Flutter 沒法渲染無限大的東西。如果妳嘗試著這麽做,同樣會拋異常。所以壹般情況下,子元素會直接將 double.infinity 當作 wrap_content 處理,向父元素反饋自己的內容大小。
所以總結下來就是:
當然壹切的原因都在於 Flutter 設計之初沒有考慮像 Android 那樣用 -1 來表示 match_parent ,-2 來表示 wrap_content 。如果這麽做的話,就沒有 double.infinity 什麽事了。
多數時候妳都可以使用 double.infinity 來達到 match_parent 的效果,但也不總是這樣。妳永遠如法用它來自下而上的達到 wrap_content 的效果。因為它只能自上而下。真想讓子元素自身大小為 wrap_content ,那就結合支持 wrap_content 的 Widget 比如 Center 使用吧。或者使用 Flutter ConstraintLayout 。它自帶了 matchParent 、 wrapContent 、 fixedSize 、 matchConstraint 的支持。