前言
我們在寫Flutter UI的時候,經常碰到明明widget設定width或者height,但是看上去就是無效呢,明明沒有設定某個widget的大小,反而卻呈現出我們想要的大小,又或者是各種莫名奇妙的overflow warning
其實要搞清楚這些問題的原因,我們必須要了解Flutter的布局約束,
文章目錄
- 前言
- 約束布局規則
- 重要的限制
- 樣例解釋
- 樣例1
- 樣例2
- 樣例3
- 樣例4
- 樣例5
- 樣例6
- 樣例7
- 樣例8
- 樣例9
- 樣例10
- 樣例11
- 樣例12
- 樣例13
- 樣例14
- 樣例15
- 樣例16
- 樣例17
- 樣例18
- 樣例19
- 樣例20
- 樣例21
- 樣例22
- 樣例23
- 樣例24
- 樣例25
- 樣例26
- 樣例27
- 樣例28
- 樣例29
- 嚴格約束(Tight)VS寬松約束(Loose)
約束布局規則
了解Flutter布局約束之前,我們必須要先了解它的規則:
- 上層widget向下層widget傳遞約束條件
- 下層widget想上層widget傳遞大小資訊
- 上冊widget決定下層widget位置
如果我們在開發中不能熟練的運用這條規則,那么在布局的時候就會遇到各種麻煩,終究不能理解其原理,
細節描述:
- Widget會通過他的父級獲得自身的約束,約束實際上就是四個浮點型別的集合:最大、最小寬度,最大、最小高度,
- 然后,這個widget會逐漸遍歷他的children串列,向子級傳遞約束資訊(子級之間的約束可能會有所不同),然后詢問它的每個子級需要用于布局的大小,
- 然后這個widget就會對它的子級的children逐個進行布局,
- 最后,widget將會把它的大小資訊向上傳遞至父widget(包括其原始約束條件),
重要的限制
正如上述所介紹的布局規則所說的那樣,Flutter的布局引擎有一些重要的限制:
- 一個widget僅僅在其父級給的約束的情況下才能決定自身的大小,這意味著widget通常情況下不能任意獲得其想要的大小,
- 一個widget無法知道,也不需要決定其在螢屏中的位置,因為它的位置由其父級決定的,
- 當輪到父級決定其大小和位置的時候,同樣的也取決于它自身的父級,所以,在不考慮整棵樹的情況下,幾乎不可能精確確定任何widget的大小和位置
- 如果子級想要擁有和父級不同的大小,然而父級沒有足夠的空間對齊進行布局的話,子級的設定的大小可能會不生效,這時候需要明確指定它的對齊方式
樣例解釋
所有樣例代碼都是比較簡單,主要就是為了解釋場景
我們來看看統一的樣例dart代碼
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 隱藏狀態欄和底部操作欄
SystemChrome.setEnabledSystemUIOverlays([]);
//樣例布局代碼
return Container(
color: Colors.red,
);
}
}
一下樣例都是修改樣例布局代碼塊來實作效果解釋
樣例1
Container(color: red);
整個螢屏作為 Container 的父級,并且強制 Container 變成和螢屏一樣的大小,所以這個 Container 充滿了整個螢屏,并繪制成紅色,
樣例2
Container(width: 100, height: 100, color: red)
紅色的 Container 想要變成 100 x 100 的大小,但是它無法變成,因為螢屏強制它變成和螢屏一樣的大小,所以 Container 充滿了整個螢屏,
樣例3
Center(
child: Container(width: 100, height: 100, color: red),
)
螢屏強制 Center 變得和螢屏一樣大,所以 Center 充滿了螢屏,
然后 Center 告訴 Container 可以變成任意大小,但是不能超出螢屏,現在,Container 可以真正變成 100 × 100 大小了,
樣例4
Align(
alignment: Alignment.bottomRight,
child: Container(width: 100, height: 100, color: red),
)
與上一個樣例不同的是,我們使用了 Align 而不是 Center,
Align 同樣也告訴 Container,你可以變成任意大小,但是,如果還留有空白空間的話,它不會居中 Container,相反,它將會在允許的空間內,把 Container 放在右下角(bottomRight),
樣例5
Center(
child: Container(
width: double.infinity, height: double.infinity, color: red),
)
螢屏強制 Center 變得和螢屏一樣大,所以 Center 充滿螢屏,
然后 Center 告訴 Container 可以變成任意大小,但是不能超出螢屏,現在,Container 想要無限的大小,但是由于它不能比螢屏更大,所以就僅充滿螢屏,
樣例6
Center(
child: Container(color: red),
)
螢屏強制 Center 變得和螢屏一樣大,所以 Center 充滿螢屏,
然后 Center 告訴 Container 可以變成任意大小,但是不能超出螢屏,由于 Container 沒有子級而且沒有固定大小,所以它決定能有多大就有多大,所以它充滿了整個螢屏,
但是,為什么 Container 做出了這個決定?非常簡單,因為這個決定是由 Container widget 的創建者決定的,可能會因創造者而異,而且你還得閱讀 Container 檔案 來理解不同場景下它的行為,
樣例7
Center(
child: Container(
color: red,
child: Container(color: green, width: 30, height: 30),
),
)
螢屏強制 Center 變得和螢屏一樣大,所以 Center 充滿螢屏,
然后 Center 告訴紅色的 Container 可以變成任意大小,但是不能超出螢屏,由于 Container 沒有固定大小但是有子級,所以它決定變成它 child 的大小,
然后紅色的 Container 告訴它的 child 可以變成任意大小,但是不能超出螢屏,
而它的 child 是一個想要 30 × 30 大小綠色的 Container,由于紅色的 Container 和其子級一樣大,所以也變為 30 × 30,由于綠色的 Container 完全覆寫了紅色 Container,所以你看不見它了,
樣例8
Center(
child: Container(
padding: const EdgeInsets.all(20.0),
color: red,
child: Container(color: green, width: 30, height: 30),
),
)
紅色 Container 變為其子級的大小,但是它將其 padding 帶入了約束的計算中,所以它有一個 30 x 30 的外邊距,由于這個外邊距,所以現在你能看見紅色了,而綠色的 Container 則還是和之前一樣,
樣例9
ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 70,
minHeight: 70,
maxWidth: 150,
maxHeight: 150,
),
child: Container(color: red, width: 10, height: 10),
)
你可能會猜想 Container 的尺寸會在 70 到 150 像素之間,但并不是這樣, ConstrainedBox 僅對其從其父級接收到的約束下施加其他約束,
在這里,螢屏迫使 ConstrainedBox 與螢屏大小完全相同,因此它告訴其子 Widget 也以螢屏大小作為約束,從而忽略了其 constraints 引數帶來的影響,
樣例10
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 70,
minHeight: 70,
maxWidth: 150,
maxHeight: 150,
),
child: Container(color: red, width: 10, height: 10),
),
)
現在,Center 允許 ConstrainedBox 達到螢屏可允許的任意大小, ConstrainedBox 將 constraints 引數帶來的約束附加到其子物件上,
Container 必須介于 70 到 150 像素之間,雖然它希望自己有 10 個像素大小,但最侄訓得了 70 個像素(最小為 70),
樣例11
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 70,
minHeight: 70,
maxWidth: 150,
maxHeight: 150,
),
child: Container(color: red, width: 1000, height: 1000),
),
)
現在,Center 允許 ConstrainedBox 達到螢屏可允許的任意大小, ConstrainedBox 將 constraints 引數帶來的約束附加到其子物件上,
Container 必須介于 70 到 150 像素之間,雖然它希望自己有 1000 個像素大小,但最侄訓得了 150 個像素(最大為 150),
樣例12
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 70,
minHeight: 70,
maxWidth: 150,
maxHeight: 150,
),
child: Container(color: red, width: 100, height: 100),
),
)
現在,Center 允許 ConstrainedBox 達到螢屏可允許的任意大小, ConstrainedBox 將 constraints 引數帶來的約束附加到其子物件上,
Container 必須介于 70 到 150 像素之間,雖然它希望自己有 100 個像素大小,因為 100 介于 70 至 150 的范圍內,所以最侄訓得了 100 個像素,
樣例13
UnconstrainedBox(
child: Container(color: red, width: 20, height: 50),
)
螢屏強制 UnconstrainedBox 變得和螢屏一樣大,而 UnconstrainedBox 允許其子級的 Container 可以變為任意大小,
樣例14
UnconstrainedBox(
child: Container(color: red, width: 4000, height: 50),
)
螢屏強制 UnconstrainedBox 變得和螢屏一樣大,而 UnconstrainedBox 允許其子級的 Container 可以變為任意大小,
不幸的是,在這種情況下,容器的寬度為 4000 像素,這實在是太大,以至于無法容納在 UnconstrainedBox 中,因此 UnconstrainedBox 將顯示溢位警告(overflow warning),
樣例15
OverflowBox(
minWidth: 0.0,
minHeight: 0.0,
maxWidth: double.infinity,
maxHeight: double.infinity,
child: Container(color: red, width: 4000, height: 50),
)
螢屏強制 OverflowBox 變得和螢屏一樣大,并且 OverflowBox 允許其子容器設定為任意大小,
OverflowBox 與 UnconstrainedBox 類似,但不同的是,如果其子級超出該空間,它將不會顯示任何警告,
在這種情況下,容器的寬度為 4000 像素,并且太大而無法容納在 OverflowBox 中,但是 OverflowBox 會全部顯示,而不會發出警告,
樣例16
UnconstrainedBox(
child: Container(color: Colors.red, width: double.infinity, height: 100),
)
這將不會渲染任何東西,而且你能在控制臺看到錯誤資訊,
UnconstrainedBox 讓它的子級決定成為任何大小,但是其子級是一個具有無限大小的 Container,
Flutter 無法渲染無限大的東西,所以它拋出以下錯誤: BoxConstraints forces an infinite width.(盒子約束強制使用了無限的寬度)
樣例17
UnconstrainedBox(
child: LimitedBox(
maxWidth: 100,
child: Container(
color: Colors.red,
width: double.infinity,
height: 100,
),
),
)
這次你就不會遇到報錯了, UnconstrainedBox 給 LimitedBox 一個無限的大小;但它向其子級傳遞了最大為 100 的約束,
如果你將 UnconstrainedBox 替換為 Center,則LimitedBox 將不再應用其限制(因為其限制僅在獲得無限約束時才適用),并且容器的寬度允許超過 100,
上面的樣例解釋了 LimitedBox 和 ConstrainedBox 之間的區別,
樣例18
FittedBox(
child: Text('Some Example Text.',textDirection: TextDirection.ltr,)
);
螢屏強制 FittedBox 變得和螢屏一樣大,而 Text 則是有一個自然寬度(也被稱作 intrinsic 寬度),它取決于文本數量,字體大小等因素,
FittedBox 讓 Text 可以變為任意大小,但是在 Text 告訴 FittedBox 其大小后, FittedBox 縮放文本直到填滿所有可用寬度,
樣例19
const Center(
child: FittedBox(
child: Text('Some Example Text.', textDirection: TextDirection.ltr),
),
)
但如果你將 FittedBox 放進 Center widget 中會發生什么? Center 將會讓 FittedBox 能夠變為任意大小,取決于螢屏大小,
FittedBox 然后會根據 Text 調整自己的大小,然后讓 Text 可以變為所需的任意大小,由于二者具有同一大小,因此不會發生縮放,
樣例20
const Center(
child: FittedBox(
child: Text(
'This is some very very very large text that is too big to fit a regular screen in a single line.'),
textDirection: TextDirection.ltr,
),
)
然而,如果 FittedBox 位于 Center 中,但 Text 太大而超出螢屏,會發生什么?
FittedBox 會嘗試根據 Text 大小調整大小,但不能大于螢屏大小,然后假定螢屏大小,并調整 Text 的大小以使其也適合螢屏,
樣例21
const Center(
child: Text(
'This is some very very very large text that is too big to fit a regular screen in a single line.',
textDirection: TextDirection.ltr),
)
然而,如果你洗掉了 FittedBox, Text 則會從螢屏上獲取其最大寬度,并在合適的地方換行,
樣例22
FittedBox(
child: Container(
height: 20.0,
width: double.infinity,
color: Colors.red,
),
)
FittedBox 只能在有限制的寬高中對子 widget 進行縮放(寬度和高度不會變得無限大),否則,它將無法渲染任何內容,并且你會在控制臺中看到錯誤,
樣例23
Row(
textDirection: TextDirection.ltr,
children: [
Container(color: red, child: const Text('Hello!', textDirection: TextDirection.ltr,)),
Container(color: green, child: const Text('Goodbye!', textDirection: TextDirection.ltr,)),
],
)
螢屏強制 Row 變得和螢屏一樣大,所以 Row 充滿螢屏,
和 UnconstrainedBox 一樣, Row 也不會對其子代施加任何約束,而是讓它們成為所需的任意大小, Row 然后將它們并排放置,任何多余的空間都將保持空白,
樣例24
Row(
textDirection: TextDirection.ltr,
children: [
Container(
color: red,
child: const Text(
'This is a very long text that '
'won\'t fit the line.',
textDirection: TextDirection.ltr,
),
),
Container(
color: green,
child: const Text(
'Goodbye flutter ui !!!!!!',
textDirection: TextDirection.ltr,
)),
],
)
由于 Row 不會對其子級施加任何約束,因此它的 children 很有可能太大而超出 Row 的可用寬度,在這種情況下, Row 會和 UnconstrainedBox 一樣顯示溢位警告,
樣例25
Row(
textDirection: TextDirection.ltr,
children: [
Expanded(
child: Center(
child: Container(
color: red,
child: const Text(
'This is a very long text that won\'t fit the lineThis is a very long text that won\'t fit the line.',
textDirection: TextDirection.ltr,
),
),
),
),
Container(
color: green,
child: const Text(
'Goodbye!',
textDirection: TextDirection.ltr,
)),
],
)
當 Row 的子級被包裹在了 Expanded widget 之后, Row 將不會再讓其決定自身的寬度了,
取而代之的是,Row 會根據所有 Expanded 的子級來計算其該有的寬度,
換句話說,一旦你使用 Expanded,子級自身的寬度就變得無關緊要,直接會被忽略掉,
樣例26
Row(
textDirection: TextDirection.ltr,
children: [
Expanded(
child: Container(
color: red,
child: const Text(
'This is a very long text that won\'t fit the line.',
textDirection: TextDirection.ltr,
),
),
),
Expanded(
child: Container(
color: green,
child: const Text(
'Goodbye!',
textDirection: TextDirection.ltr,
),
),
),
],
)
如果所有 Row 的子級都被包裹了 Expanded widget,每一個 Expanded 大小都會與其 flex 因子成比例,并且 Expanded widget 將會強制其子級具有與 Expanded 相同的寬度,
換句話說,Expanded 忽略了其子 Widget 想要的寬度,
樣例27
Row(
textDirection: TextDirection.ltr,
children: [
Flexible(
child: Container(
color: red,
child: const Text(
'This is a very long text that won\'t fit the line.',
textDirection: TextDirection.ltr,
),
),
),
Flexible(
child: Container(
color: green,
child: const Text(
'Goodbye!',
textDirection: TextDirection.ltr,
),
),
),
],
)
如果你使用 Flexible 而不是 Expanded 的話,唯一的區別是,Flexible 會讓其子級具有與 Flexible 相同或者更小的寬度,而 Expanded 將會強制其子級具有和 Expanded 相同的寬度,但無論是 Expanded 還是 Flexible 在它們決定子級大小時都會忽略其寬度,
這意味著,
Row要么使用子級的寬度,要么使用Expanded和Flexible從而忽略子級的寬度,
樣例28
MaterialApp(
home: Scaffold(
body: Container(
color: blue,
child: Column(
children: const [
Text('Hello!'),
Text('Goodbye!'),
],
),
),
))
螢屏強制 Scaffold 變得和螢屏一樣大,所以 Scaffold 充滿螢屏,然后 Scaffold 告訴 Container 可以變為任意大小,但不能超出螢屏,
當一個 widget 告訴其子級可以比自身更小的話,我們通常稱這個 widget 對其子級使用 寬松約束(loose),
樣例29
MaterialApp(
home: Scaffold(
body: SizedBox.expand(
child: Container(
color: blue,
child: Column(
children: const [
Text('Hello!'),
Text('Goodbye!'),
],
),
),
),
)
)
如果你想要 Scaffold 的子級變得和 Scaffold 本身一樣大的話,你可以將這個子級外包裹一個 SizedBox.expand,
當一個 widget 告訴它的子級必須變成某個大小的時候,我們通常稱這個 widget 對其子級使用 嚴格約束(tight),
嚴格約束(Tight)VS寬松約束(Loose)
以后你經常會聽到一些約束為嚴格約束或寬松約束,你花點時間來弄明白它們是值得的,
嚴格約束給你了一種獲得確切大小的選擇,換句話來說就是,它的最大/最小寬度是一致的,高度也一樣,
一個 寬松 約束,換句話來說就是設定了最大寬度/高度,但是讓允許其子 widget 獲得比它更小的任意大小,換句話來說,寬松約束的最小寬度/高度為 0,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/347169.html
標籤:其他
上一篇:Android執行緒思考
