Flutter 列表视图
我不喜欢官方的列表视图。它缺少一些功能,并且 jumpTo 的性能不佳。我在 [功能] 部分重写了支持这些功能的列表
特点
- 支持跳转到索引
列表视图不支持跳转到索引。但这是一个有用的功能 - 支持保持位置
如果数据插入到其他项目之前,它会向下滚动。一些聊天软件可能希望在新消息到来时保持位置不变,不向下滚动。 - 在数据不足以填满整个视口时,支持反向模式下的顶部显示。
- 支持头部固定
- 支持集成下拉刷新
- 支持在初始化数据时滚动到指定索引
- 性能
当列表视图跳转到某个位置时,该位置之前的布局项总是会被加载。这并不是真正的懒加载。
屏幕
![]() |
![]() |
![]() |
![]() |
示例
FlutterListView(
delegate: FlutterListViewDelegate(
(BuildContext context, int index) =>
ListTile(title: Text('List Item ${data[index]}')),
childCount: data.length,
))
跳转到索引
flutterListViewController.jumpToIndex(100);
或者
/// Declare
FlutterListViewController controller = FlutterListViewController();
...
controller.sliverController
.jumpToIndex(100);
...
FlutterListView(
controller: controller,
delegate: FlutterListViewDelegate(
(BuildContext context, int index) => Container(
color: Colors.white,
child: ListTile(title: Text('List Item ${data[index]}')),
),
childCount: data.length,
))
如果您想要更好的用户体验,可以设置 preferItemHeight 或 onItemHeight。
- preferItemHeight
该包不知道列表项的高度。如果您不设置,该包在布局列表项之前一直认为列表项的高度是 50。如果您知道高度,应该进行设置。 - onItemHeight
与 preferItemHeight 类似,该函数用于在列表项布局之前获取每个列表项的高度。
保持位置
_renderList() {
return FlutterListView(
reverse: true,
delegate: FlutterListViewDelegate(
(BuildContext context, int index) => Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Container(
decoration: const BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20))),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
chatListContents[index].msg,
style: const TextStyle(
fontSize: 14.0, color: Colors.white),
),
),
),
),
),
childCount: chatListContents.length,
onItemKey: (index) => chatListContents[index].id.toString(),
keepPosition: true,
keepPositionOffset: 80,
firstItemAlign: FirstItemAlign.end));
}
注意:保持位置需要实现 onItemKey,onItemKey 用于识别列表项的唯一性。key 应该与其他列表项的 key 不同。
我们使用 key 来知道您将项目插入到当前列表的哪个位置。如果您在渲染的列表项之前插入一个项目,该包应该增加 scrollOffset。
固定头部
Widget _renderHeader(String text) {
return Container(
color: const Color(0xFFF3F4F5),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
child: Text(
text,
style: const TextStyle(fontSize: 18, color: Color(0xFF767676)),
),
),
);
}
Widget _renderItem(CountryModel itemData) {
return Padding(
padding: const EdgeInsets.only(right: 12.0),
child: ListTile(
title: Text(itemData.name), trailing: Text(itemData.phoneCode)));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Stick Header")),
body: FlutterListView(
delegate: FlutterListViewDelegate(
(BuildContext context, int index) {
var data = _countries[index];
if (data is AlphabetHeader) {
return _renderHeader(data.alphabet);
} else {
return _renderItem(data as CountryModel);
}
},
childCount: _countries.length,
onItemSticky: (index) => _countries[index] is AlphabetHeader,
),
controller: controller),
);
}
您也可以查看 doc/stickyHeader.md 文档
集成下拉刷新
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Intergrate pull to refresh in list"),
),
body: SmartRefresher(
enablePullDown: true,
enablePullUp: true,
header: const WaterDropHeader(),
footer: CustomFooter(
builder: (context, mode) {
Widget body;
if (mode == LoadStatus.idle) {
body = const Text("pull up load");
} else if (mode == LoadStatus.loading) {
body = const CupertinoActivityIndicator();
} else if (mode == LoadStatus.failed) {
body = const Text("Load Failed!Click retry!");
} else if (mode == LoadStatus.canLoading) {
body = const Text("release to load more");
} else {
body = const Text("No more Data");
}
return SizedBox(
height: 55.0,
child: Center(child: body),
);
},
),
controller: _refreshController,
onRefresh: _onRefresh,
onLoading: _onLoading,
child: FlutterListView(
delegate: FlutterListViewDelegate(
(BuildContext context, int index) =>
ListTile(title: Text('List Item ${data[index]}')),
childCount: data.length,
)),
),
);
}



