Flutter 列表视图

我不喜欢官方的列表视图。它缺少一些功能,并且 jumpTo 的性能不佳。我在 [功能] 部分重写了支持这些功能的列表

特点

  1. 支持跳转到索引
    列表视图不支持跳转到索引。但这是一个有用的功能
  2. 支持保持位置
    如果数据插入到其他项目之前,它会向下滚动。一些聊天软件可能希望在新消息到来时保持位置不变,不向下滚动。
  3. 在数据不足以填满整个视口时,支持反向模式下的顶部显示。
  4. 支持头部固定
  5. 支持集成下拉刷新
  6. 支持在初始化数据时滚动到指定索引
  7. 性能
    当列表视图跳转到某个位置时,该位置之前的布局项总是会被加载。这并不是真正的懒加载。

屏幕

示例

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,
      )),
    ),
  );
}

GitHub

查看 Github