本文主要介紹Flutter布局中的LimitedBox、Offstage、OverflowBox、SizedBox四種控件,詳細(xì)介紹了其布局行為以及使用場(chǎng)景,并對(duì)源碼進(jìn)行了分析。
1. LimitedBox
A box that limits its size only when it's unconstrained.
1.1 簡(jiǎn)介
LimitedBox,通過(guò)字面意思,也可以猜測(cè)出這個(gè)控件的作用,是限制類(lèi)型的控件。這種類(lèi)型的控件前面也介紹了不少了,這個(gè)是對(duì)最大寬高進(jìn)行限制的控件。
1.2 布局行為
LimitedBox是將child限制在其設(shè)定的最大寬高中的,但是這個(gè)限定是有條件的。當(dāng)LimitedBox最大寬度不受限制時(shí),child的寬度就會(huì)受到這個(gè)最大寬度的限制,同理高度。
1.3 繼承關(guān)系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > LimitedBox
1.4 示例代碼
Row(
children: <Widget>[
Container(
color: Colors.red,
width: 100.0,
),
LimitedBox(
maxWidth: 150.0,
child: Container(
color: Colors.blue,
width: 250.0,
),
),
],
)
1.5 源碼解析
const LimitedBox({
Key key,
this.maxWidth = double.infinity,
this.maxHeight = double.infinity,
Widget child,
})
1.5.1 屬性解析
maxWidth:限定的最大寬度,默認(rèn)值是double.infinity,不能為負(fù)數(shù)。
maxHeight:同上。
1.5.2 源碼
先不說(shuō)其源碼,單純從其作用,前面介紹的SizedBox、ConstrainedBox都類(lèi)似,都是通過(guò)強(qiáng)加到child的constraint,來(lái)達(dá)到相應(yīng)的效果。
我們直接看其計(jì)算constraint的代碼
minWidth: constraints.minWidth,
maxWidth: constraints.hasBoundedWidth ? constraints.maxWidth : constraints.constrainWidth(maxWidth),
minHeight: constraints.minHeight,
maxHeight: constraints.hasBoundedHeight ? constraints.maxHeight : constraints.constrainHeight(maxHeight)
LimitedBox只是改變最大寬高的限定。具體的布局代碼如下:
if (child != null) {
child.layout(_limitConstraints(constraints), parentUsesSize: true);
size = constraints.constrain(child.size);
} else {
size = _limitConstraints(constraints).constrain(Size.zero);
}
根據(jù)最大尺寸,限制child的布局,然后將自身調(diào)節(jié)到child的尺寸。
1.6 使用場(chǎng)景
使用場(chǎng)景是不可能清楚了,光是找例子,就花了不少時(shí)間。Flutter的一些冷門(mén)控件,真的是除了官方的文檔,啥材料都木有。谷歌說(shuō)這個(gè)很有用,還是一臉懵逼。這種控件,也有其他的替代解決方案,LimitedBox可以達(dá)到的效果,ConstrainedBox都可以實(shí)現(xiàn)。
2. Offstage
A widget that lays the child out as if it was in the tree, but without painting anything, without making the child available for hit testing, and without taking any room in the parent.
2.1 簡(jiǎn)介
Offstage的作用很簡(jiǎn)單,通過(guò)一個(gè)參數(shù),來(lái)控制child是否顯示,日常使用中也算是比較常用的控件。
2.2 布局行為
Offstage的布局行為完全取決于其offstage參數(shù)
- 當(dāng)offstage為true,當(dāng)前控件不會(huì)被繪制在屏幕上,不會(huì)響應(yīng)點(diǎn)擊事件,也不會(huì)占用空間;
- 當(dāng)offstage為false,當(dāng)前控件則跟平常用的控件一樣渲染繪制;
另外,當(dāng)Offstage不可見(jiàn)的時(shí)候,如果child有動(dòng)畫(huà),應(yīng)該手動(dòng)停掉,Offstage并不會(huì)停掉動(dòng)畫(huà)。
2.3 繼承關(guān)系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > Offstage
2.4 示例代碼
Column(
children: <Widget>[
new Offstage(
offstage: offstage,
child: Container(color: Colors.blue, height: 100.0),
),
new CupertinoButton(
child: Text("點(diǎn)擊切換顯示"),
onPressed: () {
setState(() {
offstage = !offstage;
});
},
),
],
)
當(dāng)點(diǎn)擊切換按鈕的時(shí)候,可以看到Offstage顯示消失。
2.5 源碼解析
const Offstage({ Key key, this.offstage = true, Widget child })
2.5.1 屬性解析
offstage:默認(rèn)為true,也就是不顯示,當(dāng)為flase的時(shí)候,會(huì)顯示該控件。
2.5.2 源碼
我們先來(lái)看下Offstage的computeIntrinsicSize相關(guān)的方法:
@override
double computeMinIntrinsicWidth(double height) {
if (offstage)
return 0.0;
return super.computeMinIntrinsicWidth(height);
}
可以看到,當(dāng)offstage為true的時(shí)候,自身的最小以及最大寬高都會(huì)被置為0.0。
接下來(lái)我們來(lái)看下其hitTest方法:
@override
bool hitTest(HitTestResult result, { Offset position }) {
return !offstage && super.hitTest(result, position: position);
}
當(dāng)offstage為true的時(shí)候,也不會(huì)去執(zhí)行。
最后我們來(lái)看下其paint方法:
@override
void paint(PaintingContext context, Offset offset) {
if (offstage)
return;
super.paint(context, offset);
}
當(dāng)offstage為true的時(shí)候直接返回,不繪制了。
到此,跟上面所說(shuō)的布局行為對(duì)應(yīng)上了。我們一定要清楚一件事情,Offstage并不是通過(guò)插入或者刪除自己在widget tree中的節(jié)點(diǎn),來(lái)達(dá)到顯示以及隱藏的效果,而是通過(guò)設(shè)置自身尺寸、不響應(yīng)hitTest以及不繪制,來(lái)達(dá)到展示與隱藏的效果。
2.6 使用場(chǎng)景
當(dāng)我們需要控制一個(gè)區(qū)域顯示或者隱藏的時(shí)候,可以使用這個(gè)場(chǎng)景。其實(shí)也有其他代替的方法,但是成本會(huì)高很多,例如直接在tree上插入刪除,但是不建議這么做。
3. OverflowBox
A widget that imposes different constraints on its child than it gets from its parent, possibly allowing the child to overflow the parent.
3.1 簡(jiǎn)介
OverflowBox這個(gè)控件,允許child超出parent的范圍顯示,當(dāng)然不用這個(gè)控件,也有很多種方式實(shí)現(xiàn)類(lèi)似的效果。
3.2 布局行為
當(dāng)OverflowBox的最大尺寸大于child的時(shí)候,child可以完整顯示,當(dāng)其小于child的時(shí)候,則以最大尺寸為基準(zhǔn),當(dāng)然,這個(gè)尺寸都是可以突破父節(jié)點(diǎn)的。最后加上對(duì)齊方式,完成布局。
3.3 繼承關(guān)系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > OverflowBox
3.4 示例代碼
Container(
color: Colors.green,
width: 200.0,
height: 200.0,
padding: const EdgeInsets.all(5.0),
child: OverflowBox(
alignment: Alignment.topLeft,
maxWidth: 300.0,
maxHeight: 500.0,
child: Container(
color: Color(0x33FF00FF),
width: 400.0,
height: 400.0,
),
),
)
當(dāng)maxHeight大于height的時(shí)候,可以完全顯示下來(lái),當(dāng)maxHeight小于height的時(shí)候,則不會(huì)會(huì)被隱藏掉
3.5 源碼解析
構(gòu)造函數(shù)如下:
const OverflowBox({
Key key,
this.alignment = Alignment.center,
this.minWidth,
this.maxWidth,
this.minHeight,
this.maxHeight,
Widget child,
})
3.5.1 屬性解析
alignment:對(duì)齊方式。
minWidth:允許child的最小寬度。如果child寬度小于這個(gè)值,則按照最小寬度進(jìn)行顯示。
maxWidth:允許child的最大寬度。如果child寬度大于這個(gè)值,則按照最大寬度進(jìn)行展示。
minHeight:允許child的最小高度。如果child高度小于這個(gè)值,則按照最小高度進(jìn)行顯示。
maxHeight:允許child的最大高度。如果child高度大于這個(gè)值,則按照最大高度進(jìn)行展示。
其中,最小以及最大寬高度,如果為null的時(shí)候,就取父節(jié)點(diǎn)的constraint代替。
3.5.2 源碼
OverflowBox的源碼很簡(jiǎn)單,我們先來(lái)看一下布局代碼:
if (child != null) {
child.layout(_getInnerConstraints(constraints), parentUsesSize: true);
alignChild();
}
如果child不為null,child則會(huì)按照計(jì)算出的constraints進(jìn)行尺寸的調(diào)整,然后對(duì)齊。
至于constraints的計(jì)算,則還是上面的邏輯,如果設(shè)置的有的話,就取這個(gè)值,如果沒(méi)有的話,就拿父節(jié)點(diǎn)的。
3.6 使用場(chǎng)景
有時(shí)候設(shè)計(jì)圖上出現(xiàn)的角標(biāo),會(huì)超出整個(gè)模塊,可以使用OverflowBox控件。但我們應(yīng)該知道,不使用這種控件,也可以完成布局,在最外面包一層,也能達(dá)到一樣的效果。具體實(shí)施起來(lái)哪個(gè)比較方便,同學(xué)們自行取舍。
4. SizedBox
A box with a specified size.
4.1 簡(jiǎn)介
比較常用的一個(gè)控件,設(shè)置具體尺寸。
4.2 布局行為
SizedBox布局行為相對(duì)較簡(jiǎn)單:
- child不為null時(shí),如果設(shè)置了寬高,則會(huì)強(qiáng)制把child尺寸調(diào)到此寬高;如果沒(méi)有設(shè)置寬高,則會(huì)根據(jù)child尺寸進(jìn)行調(diào)整;
- child為null時(shí),如果設(shè)置了寬高,則自身尺寸調(diào)整到此寬高值,如果沒(méi)設(shè)置,則尺寸為0;
4.3 繼承關(guān)系
Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > SizedBox
4.4 示例代碼
Container(
color: Colors.green,
padding: const EdgeInsets.all(5.0),
child: SizedBox(
width: 200.0,
height: 200.0,
child: Container(
color: Colors.red,
width: 100.0,
height: 300.0,
),
),
)
4.5 源碼解析
構(gòu)造函數(shù)
const SizedBox({ Key key, this.width, this.height, Widget child })
4.5.1 屬性解析
width:寬度值,如果具體設(shè)置了,則強(qiáng)制child寬度為此值,如果沒(méi)設(shè)置,則根據(jù)child寬度調(diào)整自身寬度。
height:同上。
4.5.2 源碼
SizedBox內(nèi)部是通過(guò)RenderConstrainedBox來(lái)實(shí)現(xiàn)的。具體的源碼就不解析了,總體思路是,根據(jù)寬高值算好一個(gè)constraints,然后強(qiáng)制應(yīng)用到child上。
4.6 使用場(chǎng)景
這個(gè)控件,很多場(chǎng)景可以使用。但是,可以替代它的控件也有不少,例如Container、ConstrainedBox等。而且SizedBox就是ConstrainedBox的一個(gè)特例。還是那句話,精簡(jiǎn)啊,多提供一些常用的,不要提供一大堆重復(fù)的,場(chǎng)景很少的控件。
5. 后話
筆者建了一個(gè)Flutter學(xué)習(xí)相關(guān)的項(xiàng)目,Github地址,里面包含了筆者寫(xiě)的關(guān)于Flutter學(xué)習(xí)相關(guān)的一些文章,會(huì)定期更新,文章中的代碼也在這個(gè)項(xiàng)目中,歡迎大家關(guān)注。