开发 Flutter 项目中碰到一个常见需求,Column 嵌套 ListView ,这种 UI 设计很常见,但我们实现起来就要考虑嵌套后滑动冲突的问题。
滑动
先分享 Column 内容滑动和滚动的使用,如果我们的布局在垂直内容很长,超出屏幕,直接运行,是会报错的。
A RenderFlex overflowed by *** pixels on the bottom.

这个超出屏幕的报错很常见,相信都见过。我们需要加一个滚动控件 SingleChildScrollView 包裹。
body: SingleChildScrollView(
child: Column(
children: [
Container(
height: 300,
color: Colors.yellow,
),
Container(
height: 300,
color: Colors.red,
),
...
],
),
),
嵌套 ListView
但如果往里面嵌套一个 ListView ,就会报如下错误。
RenderBox was not laid out: RenderViewport#80017 NEEDS-PAINT
'package:flutter/src/rendering/box.dart':
Failed assertion: line 1979 pos 12: 'hasSize'
这是因为 ListView 会在滚动方向上尽可能占据最大空间。而 Column 会要求每一个 Child Widget 有一个明确的高度。当 ListView 嵌套在 Column 里面当做子控件时,它们俩的需求就矛盾了。Column 希望子控件给它一个具体高度,ListView 希望占据最大的高度,需求相互矛盾,就报错了。
那我们给 ListView 外层添加一个高度,这个问题就解决了。
SizedBox(
height: 200,
child: buildListView()
)
Widget buildListView() {
return ListView.builder(
itemBuilder: (context, index) {
return ListTile(title: Text(_items[index]));
},
itemCount: _items.length,
);
}

设置一个明确的高度虽然可以解决问题,但如果 ListView 内容超过高度限制,就会在高度里滚动,在这个小范围内滑动。这应该不会满足我们的需求,我们需要 ListView 在整个页面上下滑动,随着屏幕滚动。
再说回 ListView ,它的默认特点是占据最大高度,但可以通过 shrinkWrap 属性设置,让它只占据内容高度,也就是内容计算出来多高,就占据多高。有点像 Android 开发里的 match_parent 和 wrap_content 。
ListView.builder(
shrinkWrap: true,
...
);
shrinkWrap 默认值是 false,占据最大高度,设置成 true 会包裹内容,占据包裹内容高度。
上面代码运行后,还是不对,因为 ListView 滑动还是只会在小范围内滑动,不会拖动整个屏幕,再加上下面属性。
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
...
);
所有问题都解决了,实际布局中 ListView 会自动给下方加一个间距,手动设置 padding 解决。
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
...
);
嵌套 Column
如果将上面的 ListView 换成 Column ,也就是 Column 里面 嵌套 Column ,也会报上面的错。因为 Column 默认也会占据最大高度,设置子 Column 的主轴,从 max 设置到 min。
Column(
children: [
Column(
mainAxisSize: MainAxisSize.min
)
],
),
OK,希望以上内容对你有用。
本文由老郭种树原创,转载请注明:https://guozh.net/column-nested-listview/