在〈Expanded 子元件〉、〈使用輸入欄位〉、〈滑動底盤 Bottom Sheet〉等文件中,曾經遇過幾次元件繪製時 OVERFLOW 的情況,也就是出現了黃黑條紋警訊的狀況,雖然通常遇上了,就是簡單地透過 Expanded
、SingleChildScrollView
等就可以解決,選用什麼是視需求而定,不過最根本的還是要搞清楚,到底為什麼會 OVERFLOW?如此才能搞清楚,為什麼有些元件就不會 OVERFLOW,有些卻會?
不負責任的答案是,使用 Row
或 Column
很容易出現 OVERFLOW … XD
負責點但簡單的答案是,元件尺寸超過了被配給的可用空間。不過,可用空間是指什麼?元件尺寸又是什麼呢?元件的 width
、height
嗎?
首先要知道一件事,width
、height
不是元件最後真正的尺寸,基本上只是個參考用的資訊,能不能生效要看父元件給予的約束,也就是子元件可以獲得的最大寬高與最小寬高。
來看看作為 runApp
引數的根元件好了,給予根元件的約束,也就是根元件的最大寬高與最小寬高會是螢幕的尺寸,最大寬高與最小寬高都被限定的情況下,根元件 width
、height
會被忽略,獲得的可用空間也就是螢幕的尺寸,例如,雖然底下的 Container
設定了 width
、height
,然而範例運行後,整個螢幕都會是紅色:
import 'package:flutter/material.dart';
void main() => runApp(
Container(
color: Colors.red,
width: 100,
height: 100,
)
);
這是因為 width
、height
被忽略了,Container
直接使用整個可用空間(也就是螢幕尺寸)作為其尺寸,想讓 Container
的 width
、height
生效?放個 Center
:
import 'package:flutter/material.dart';
void main() => runApp(
Center(
child: Container(
color: Colors.red,
width: 100,
height: 100,
),
)
);
Center
取得整個螢幕作為可用空間,也就是其尺寸會被強制設定螢幕尺寸,Center
也會給予子元件約束,也就是告訴子元件最大寬高是螢幕尺寸,只要不超過,你要多大都可以,因此上面的範例運行後,就會看到一個 100x100 的紅色方塊置中於螢幕,如果你將 Container
的 width
或 height
設得比螢幕尺寸大,Center
會忽略 width
或 height
,這時只會看到螢幕都會是紅色,不會 OVERFLOW。
如果不想約束子元件呢?可以使用 UnconstrainedBox
:
import 'package:flutter/material.dart';
void main() => runApp(
UnconstrainedBox(
child: Container(
color: Colors.red,
width: 100,
height: 100,
),
)
);
UnconstrainedBox
預設會有置中有效果,不會對子元件施加約束,就這個例子來說不會有問題,因為尺寸只有 100x100,不比螢幕大,如果設高一點呢?
import 'package:flutter/material.dart';
void main() => runApp(
UnconstrainedBox(
child: Container(
color: Colors.red,
width: 100,
height: 1000, // 比螢幕高
),
)
);
UnconstrainedBox
是根元件,取得的可用空間是整個螢幕,能給予子元件的可用空間最多也就是螢幕尺寸,然而子元件的尺寸超出了,UnconstrainedBox
又不約束子元件,這時就會看到 OVERFLOW 了:
Flutter 的一些元件,可以明確地指定約束,通常是透過 constraints
之類的特性指定 BoxConstraints
,也有個 ConstrainedBox
元件,可以對子元件進行約束。例如:
import 'package:flutter/material.dart';
void main() => runApp(
UnconstrainedBox(
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: 300,
),
child: Container(
color: Colors.red,
width: 100,
height: 1000,
),
),
)
);
這次故意在 UnconstrainedBox
中設定 ConstrainedBox
作為子元件,BoxConstraints
可以設定 maxHeight
、minHeight
、maxWidth
、minWidth
,也就是最大、最小的寬高約束,這次就不會 OVERFLOW 了:
要注意的是,ConstrainedBox
是根據 constraints
,對子元件施加額外限制,例如:
import 'package:flutter/material.dart';
void main() => runApp(
UnconstrainedBox(
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: 150,
),
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: 300,
),
child: Container(
color: Colors.red,
width: 100,
height: 1000,
),
),
),
)
);
上面的範例中,ConstrainedBox
中還有 ConstrainedBox
,雖然內層的 maxHeight
是 300,然而外層的 maxHeight
已設定為 150,內層並不能大於外層,這時內層的 maxHeight
就會被忽略:
相對應地,如果外層設定了 minWidth
,minHeight
,內層的 ConstrainedBox
設定的 minWidth
,minHeight
,就不能小於外層。
因此,若 ConstrainedBox
作為 runApp
的根元件,constraints
只會被忽略,例如,底下也是整個螢幕都是紅色的:
import 'package:flutter/material.dart';
void main() => runApp(
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: 300,
),
child: Container(
color: Colors.red,
width: 100,
height: 1000,
),
)
);
因為 ConstrainedBox
作為根元件,最大、最小寬高被約束為螢幕尺寸,這時等於你怎麼設 ConstrainedBox
的 maxHeight
、minHeight
、maxWidth
、minWidth
來約束都沒有用,因為不能違反螢幕尺寸的最大、最小寬高約束。
為什麼 Row
或 Column
會容易出現 OVERFLOW 呢?Row
或 Column
預設不會對子元件作出約束,像是〈Expanded 子元件〉中第一個範例,Column
取得整個螢幕作為可用空間,子元件在沒有約束的情況下垂直排列,超出螢幕空間,也就 OVERFLOW 了。
結論就是,元件是否會 OVERFLOW,雖然主要是在於,元件尺寸是否超過了被配給的可用空間,然而可用空間有多少,必須搞清楚父子元件之間是否有約束關係。
另外,約束關係往往在決定了元件的 width
、height
之類的設定是否生效,如果你設定了 width
、height
卻沒有生效,也許就是父元件做出了約束。
另外,有些元件的 width
、height
設定,往往也會被拿來作為約束的參考,Container
就是個例子,它本身也可以設定 constraints
,與 width
、height
的關係可以從建構式的原始碼中得知:
Container({
Key key,
this.alignment,
this.padding,
this.color,
this.decoration,
this.foregroundDecoration,
double width,
double height,
BoxConstraints constraints,
this.margin,
this.transform,
this.child,
this.clipBehavior = Clip.none,
}) : assert(margin == null || margin.isNonNegative),
assert(padding == null || padding.isNonNegative),
assert(decoration == null || decoration.debugAssertIsValid()),
assert(constraints == null || constraints.debugAssertIsValid()),
assert(clipBehavior != null),
assert(color == null || decoration == null,
'Cannot provide both a color and a decoration\n'
'To provide both, use "decoration: BoxDecoration(color: color)".'
),
constraints = // width 與 height 會作為約束的來源資料
(width != null || height != null)
? constraints?.tighten(width: width, height: height)
?? BoxConstraints.tightFor(width: width, height: height)
: constraints,
super(key: key);
有些 Flutter 的內建元件,在內部的組建過程中,可能各自有 Align
、Container
、ConstrainedBox
之類的元件組成,因此情況可能更複雜,搞清楚這一切最好的方式,其實就在於 API 文件與原始碼,當然,如果想要有個比較簡單的開始,除了這邊的整理之外,也可以參考官方文件〈Understanding constraints〉。