本文主要介紹Flutter布局中的ListBody族跛、ListView礁哄、CustomMultiChildLayout控件溪北,詳細(xì)介紹了其布局行為以及使用場(chǎng)景之拨,并對(duì)源碼進(jìn)行了分析敦锌。
1. ListBody
A widget that arranges its children sequentially along a given axis.
1.1 簡(jiǎn)介
ListBody是一個(gè)不常直接使用的控件,一般都會(huì)配合ListView或者Column等控件使用颖变。ListBody的作用是按給定的軸方向腥刹,按照順序排列子節(jié)點(diǎn)衔峰。
1.2 布局行為
在主軸上垫卤,子節(jié)點(diǎn)按照順序進(jìn)行布局穴肘,在交叉軸上舔痕,子節(jié)點(diǎn)尺寸會(huì)被拉伸,以適應(yīng)交叉軸的區(qū)域邢笙。
在主軸上侍匙,給予子節(jié)點(diǎn)的空間必須是不受限制的(unlimited),使得子節(jié)點(diǎn)可以全部被容納筐骇,ListBody不會(huì)去裁剪或者縮放其子節(jié)點(diǎn)。
1.3 繼承關(guān)系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > MultiChildRenderObjectWidget > ListBody
1.4 示例代碼
Flex(
direction: Axis.vertical,
children: <Widget>[
ListBody(
mainAxis: Axis.vertical,
reverse: false,
children: <Widget>[
Container(color: Colors.red, width: 50.0, height: 50.0,),
Container(color: Colors.yellow, width: 50.0, height: 50.0,),
Container(color: Colors.green, width: 50.0, height: 50.0,),
Container(color: Colors.blue, width: 50.0, height: 50.0,),
Container(color: Colors.black, width: 50.0, height: 50.0,),
],
)],
)
1.5 源碼解析
構(gòu)造函數(shù)如下:
ListBody({
Key key,
this.mainAxis = Axis.vertical,
this.reverse = false,
List<Widget> children = const <Widget>[],
})
1.5.1 屬性解析
mainAxis:排列的主軸方向唬滑。
reverse:是否反向。
1.5.2 源碼
ListBody的布局代碼非常簡(jiǎn)單模她,根據(jù)主軸的方向侈净,對(duì)子節(jié)點(diǎn)依次排布畜侦。
當(dāng)向右的時(shí)候旋膳,布局代碼如下,向下的代碼類(lèi)似:
double mainAxisExtent = 0.0;
RenderBox child = firstChild;
switch (axisDirection) {
case AxisDirection.right:
final BoxConstraints innerConstraints = new BoxConstraints.tightFor(height: constraints.maxHeight);
while (child != null) {
child.layout(innerConstraints, parentUsesSize: true);
final ListBodyParentData childParentData = child.parentData;
childParentData.offset = new Offset(mainAxisExtent, 0.0);
mainAxisExtent += child.size.width;
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
size = constraints.constrain(new Size(mainAxisExtent, constraints.maxHeight));
break;
}
當(dāng)向左的時(shí)候,布局代碼如下减俏,向上的代碼類(lèi)似:
double mainAxisExtent = 0.0;
RenderBox child = firstChild;
case AxisDirection.left:
final BoxConstraints innerConstraints = new BoxConstraints.tightFor(height: constraints.maxHeight);
while (child != null) {
child.layout(innerConstraints, parentUsesSize: true);
final ListBodyParentData childParentData = child.parentData;
mainAxisExtent += child.size.width;
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
double position = 0.0;
child = firstChild;
while (child != null) {
final ListBodyParentData childParentData = child.parentData;
position += child.size.width;
childParentData.offset = new Offset(mainAxisExtent - position, 0.0);
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
size = constraints.constrain(new Size(mainAxisExtent, constraints.maxHeight));
break;
向右或者向下的時(shí)候垄懂,布局代碼很簡(jiǎn)單草慧,依次去排列仔雷。當(dāng)向左或者向上的時(shí)候舔示,首先會(huì)去計(jì)算主軸所占的空間惕稻,然后再去計(jì)算每個(gè)節(jié)點(diǎn)的位置俺祠。
1.6 使用場(chǎng)景
筆者自己從未使用過(guò)這個(gè)控件蜘渣,也想象不出場(chǎng)景,大家了解下有這么一個(gè)布局控件即可腿准。
2. ListView
A scrollable, linear list of widgets.
2.1 簡(jiǎn)介
ListView是一個(gè)非常常用的控件吐葱,涉及到數(shù)據(jù)列表展示的倦沧,一般情況下都會(huì)選用該控件窖认。ListView跟GridView相似扑浸,基本上是一個(gè)slivers里面只包含一個(gè)SliverList的CustomScrollView喝噪。
2.2 布局行為
ListView在主軸方向可以滾動(dòng)酝惧,在交叉軸方向,則是填滿ListView巫财。
2.3 繼承關(guān)系
Object > Diagnosticable > DiagnosticableTree > Widget > StatelessWidget > ScrollView > BoxScrollView > ListView
看繼承關(guān)系可知,這是一個(gè)組合控件悍及。ListView跟GridView類(lèi)似闽瓢,都是繼承自BoxScrollView。
2.4 示例代碼
ListView(
shrinkWrap: true,
padding: EdgeInsets.all(20.0),
children: <Widget>[
Text('I\'m dedicating every day to you'),
Text('Domestic life was never quite my style'),
Text('When you smile, you knock me out, I fall apart'),
Text('And I thought I was so smart'),
],
)
ListView.builder(
itemCount: 1000,
itemBuilder: (context, index) {
return ListTile(
title: Text("$index"),
);
},
)
兩個(gè)示例都是官方文檔上的例子心赶,第一個(gè)展示四行文字扣讼,第二個(gè)展示1000個(gè)item。
2.5 源碼解析
構(gòu)造函數(shù)如下:
ListView({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
double cacheExtent,
List<Widget> children = const <Widget>[],
})
同時(shí)也提供了如下額外的三種構(gòu)造方法园担,方便開(kāi)發(fā)者使用届谈。
ListView.builder
ListView.separated
ListView.custom
2.5.1 屬性解析
ListView大部分屬性同GridView,想了解的讀者可以看一下之前所寫(xiě)的GridView相關(guān)的文章弯汰。這里只介紹一個(gè)屬性
itemExtent:ListView在滾動(dòng)方向上每個(gè)item所占的高度值。
2.5.2 源碼
@override
Widget buildChildLayout(BuildContext context) {
if (itemExtent != null) {
return new SliverFixedExtentList(
delegate: childrenDelegate,
itemExtent: itemExtent,
);
}
return new SliverList(delegate: childrenDelegate);
}
ListView標(biāo)準(zhǔn)構(gòu)造布局代碼如上所示湖雹,底層是用到的SliverList去實(shí)現(xiàn)的咏闪。ListView是一個(gè)slivers里面只包含一個(gè)SliverList的CustomScrollView据某。源碼這塊兒可以參考GridView,在此不做更多的說(shuō)明。
2.6 使用場(chǎng)景
ListView使用場(chǎng)景太多了,一般涉及到列表展示的严拒,一般都會(huì)選擇ListView预鬓。
但是需要注意一點(diǎn),ListView的標(biāo)準(zhǔn)構(gòu)造函數(shù)適用于數(shù)目比較少的場(chǎng)景长窄,如果數(shù)目比較多
的話,最好使用ListView.builder
。
ListView的標(biāo)準(zhǔn)構(gòu)造函數(shù)會(huì)將所有item一次性創(chuàng)建,而ListView.builder會(huì)創(chuàng)建滾動(dòng)到屏幕上顯示的item垮媒。
3. CustomMultiChildLayout
A widget that uses a delegate to size and position multiple children.
3.1 簡(jiǎn)介
之前單節(jié)點(diǎn)布局控件中介紹過(guò)一個(gè)類(lèi)似的控件--CustomSingleChildLayout入桂,都是通過(guò)delegate去實(shí)現(xiàn)自定義布局呵晚,只不過(guò)這次是多節(jié)點(diǎn)的自定義布局的控件沮脖,通過(guò)提供的delegate免姿,可以實(shí)現(xiàn)控制節(jié)點(diǎn)的位置以及尺寸紊婉。
3.2 布局行為
CustomMultiChildLayout提供的delegate可以控制子節(jié)點(diǎn)的布局,具體在如下幾點(diǎn):
- 可以決定每個(gè)子節(jié)點(diǎn)的布局約束條件乔妈;
- 可以決定每個(gè)子節(jié)點(diǎn)的位置;
- 可以決定自身的尺寸,但是自身的自身必須不能依賴(lài)子節(jié)點(diǎn)的尺寸。
可以看到,跟CustomSingleChildLayout的delegate提供的作用類(lèi)似,只不過(guò)CustomMultiChildLayout的稍微會(huì)復(fù)雜點(diǎn)庶橱。
3.3 繼承關(guān)系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > MultiChildRenderObjectWidget > CustomMultiChildLayout
3.4 示例代碼
class TestLayoutDelegate extends MultiChildLayoutDelegate {
TestLayoutDelegate();
static const String title = 'title';
static const String description = 'description';
@override
void performLayout(Size size) {
final BoxConstraints constraints =
new BoxConstraints(maxWidth: size.width);
final Size titleSize = layoutChild(title, constraints);
positionChild(title, new Offset(0.0, 0.0));
final double descriptionY = titleSize.height;
layoutChild(description, constraints);
positionChild(description, new Offset(0.0, descriptionY));
}
@override
bool shouldRelayout(TestLayoutDelegate oldDelegate) => false;
}
Container(
width: 200.0,
height: 100.0,
color: Colors.yellow,
child: CustomMultiChildLayout(
delegate: TestLayoutDelegate(),
children: <Widget>[
LayoutId(
id: TestLayoutDelegate.title,
child: new Text("This is title",
style: TextStyle(fontSize: 20.0, color: Colors.black)),
),
LayoutId(
id: TestLayoutDelegate.description,
child: new Text("This is description",
style: TextStyle(fontSize: 14.0, color: Colors.red)),
),
],
),
)
上面的TestLayoutDelegate作用很簡(jiǎn)單预伺,對(duì)子節(jié)點(diǎn)進(jìn)行尺寸以及位置調(diào)整瞒御。可以看到烤惊,每一個(gè)子節(jié)點(diǎn)必須用一個(gè)LayoutId控件包裹起來(lái)
渡贾,在delegate中可以對(duì)不同id的控件進(jìn)行調(diào)整擂仍。
3.5 源碼解析
構(gòu)造函數(shù)如下:
CustomMultiChildLayout({
Key key,
@required this.delegate,
List<Widget> children = const <Widget>[],
})
3.5.1 屬性解析
delegate:對(duì)子節(jié)點(diǎn)進(jìn)行尺寸以及位置調(diào)整的delegate肋坚。
3.5.2 源碼
@override
void performLayout() {
size = _getSize(constraints);
delegate._callPerformLayout(size, firstChild);
}
CustomMultiChildLayout的布局代碼很簡(jiǎn)單诚卸,調(diào)用delegate中的布局函數(shù)進(jìn)行相關(guān)的操作辫愉,本身做的處理很少,在這里不做過(guò)多的解釋。
3.6 使用場(chǎng)景
一些比較復(fù)雜的布局場(chǎng)景可以使用砰苍,但是有很多可替代的控件潦匈,使用起來(lái)也沒(méi)有這么麻煩,大家還是按照自己熟練程度選擇使用赚导。
4. 后話
筆者建了一個(gè)Flutter學(xué)習(xí)相關(guān)的項(xiàng)目茬缩,Github地址,里面包含了筆者寫(xiě)的關(guān)于Flutter學(xué)習(xí)相關(guān)的一些文章吼旧,會(huì)定期更新凰锡,也會(huì)上傳一些學(xué)習(xí)Demo,歡迎大家關(guān)注圈暗。