Flutter ListView延迟加载

Val*_*kin 30 flutter

如何实现无限列表视图的延迟加载项?当用户滚动到listview的末尾时,我想通过网络加载更多项目.

Rém*_*let 39

你可以听一个ScrollController.

ScrollController有一些有用的信息,如scrolloffset和列表ScrollPosition.

在你的情况下,有趣的部分是controller.position当前可见的ScrollPosition.它代表可滚动的一部分.

ScrollPosition包含有关它在可滚动内部的位置的信息.如extentBeforeextentAfter.或者它的大小,有extentInside.

考虑到这一点,您可以触发基于extentAfter其的服务器调用,该调用表示可用的剩余卷轴空间.

这是使用我所说的基本示例.

class MyHome extends StatefulWidget {
  @override
  _MyHomeState createState() => new _MyHomeState();
}

class _MyHomeState extends State<MyHome> {
  ScrollController controller;
  List<String> items = new List.generate(100, (index) => 'Hello $index');

  @override
  void initState() {
    super.initState();
    controller = new ScrollController()..addListener(_scrollListener);
  }

  @override
  void dispose() {
    controller.removeListener(_scrollListener);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Scrollbar(
        child: new ListView.builder(
          controller: controller,
          itemBuilder: (context, index) {
            return new Text(items[index]);
          },
          itemCount: items.length,
        ),
      ),
    );
  }

  void _scrollListener() {
    print(controller.position.extentAfter);
    if (controller.position.extentAfter < 500) {
      setState(() {
        items.addAll(new List.generate(42, (index) => 'Inserted $index'));
      });
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

您可以清楚地看到,当到达滚动的末尾时,由于加载了更多项目,滚动条会消耗掉.

  • @RémiRousselet在extentAfter变得小于500之后,这个条件-&gt;controller.position.extentAfter&lt;500多次不会为真。 (3认同)
  • @RémiRousselet 一个小提示,“super.initState()”应该始终是“initState()”方法中的第一行。让我更新你的答案。 (2认同)

Pan*_*rld 15

感谢RémiRousselet的方法,但它并没有解决所有问题.特别是当ListView滚动到底部时,它仍然会调用scrollListener几次.更好的方法是将Notification Listener与Remi的方法结合起来.这是我的解决方案:

bool _handleScrollNotification(ScrollNotification notification) {
  if (notification is ScrollEndNotification) {
    if (_controller.position.extentAfter == 0) {
      loadMore();
    }
  }
  return false;
}

@override
Widget build(BuildContext context) {
    final Widget gridWithScrollNotification = NotificationListener<
            ScrollNotification>(
        onNotification: _handleScrollNotification,
        child: GridView.count(
            controller: _controller,
            padding: EdgeInsets.all(4.0),
          // Create a grid with 2 columns. If you change the scrollDirection to
          // horizontal, this would produce 2 rows.
          crossAxisCount: 2,
          crossAxisSpacing: 2.0,
          mainAxisSpacing: 2.0,
          // Generate 100 Widgets that display their index in the List
          children: _documents.map((doc) {
            return GridPhotoItem(
              doc: doc,
            );
          }).toList()));
    return new Scaffold(
      key: _scaffoldKey,
      body: RefreshIndicator(
       onRefresh: _handleRefresh, child: gridWithScrollNotification));
}
Run Code Online (Sandbox Code Playgroud)

  • `ScrollNotification` 有 `notification.metrics.extentAfter`,所以你不需要使用 `ScrollController`。另外,当我第一次使用两者时,直到我从“ListView”中删除“_controller”后,它才起作用。 (6认同)
  • 可能由于版本差异,您不需要带有此代码的滚动控制器。```bool _handleScrollNotification(ScrollNotification notification) { if (通知是 ScrollEndNotification &amp;&amp; notification.metrics.extentAfter == 0) { loadMore(); } 返回假;}``` (2认同)

chu*_*han 10

该解决方案使用 ScrollController,我看到了有关页面的评论。
我想分享我对包incrementally_loading_listview https://github.com/MaikuB/incrementally_loading_listview 的发现 。
正如打包所说:这可用于加载从 API 请求收到的分页数据。

基本上,当 ListView 构建最后一个项目时,这意味着用户已向下滚动到底部。
希望能帮到有类似问题的人。

出于演示目的,我已更改示例以让页面仅包含一项并添加一个 CircularProgressIndicator。

在此处输入图片说明

...
bool _loadingMore;
bool _hasMoreItems;
int  _maxItems = 30;
int  _numItemsPage = 1;
...
_hasMoreItems = items.length < _maxItems;    
...
return IncrementallyLoadingListView(
              hasMore: () => _hasMoreItems,
              itemCount: () => items.length,
              loadMore: () async {
                // can shorten to "loadMore: _loadMoreItems" but this syntax is used to demonstrate that
                // functions with parameters can also be invoked if needed
                await _loadMoreItems();
              },
              onLoadMore: () {
                setState(() {
                  _loadingMore = true;
                });
              },
              onLoadMoreFinished: () {
                setState(() {
                  _loadingMore = false;
                });
              },
              loadMoreOffsetFromBottom: 0,
              itemBuilder: (context, index) {
                final item = items[index];
                if ((_loadingMore ?? false) && index == items.length - 1) {
                  return Column(
                    children: <Widget>[
                      ItemCard(item: item),
                      Card(
                        child: Padding(
                          padding: const EdgeInsets.all(16.0),
                          child: Column(
                            children: <Widget>[
                              Row(
                                crossAxisAlignment:
                                    CrossAxisAlignment.start,
                                children: <Widget>[
                                  Container(
                                    width: 60.0,
                                    height: 60.0,
                                    color: Colors.grey,
                                  ),
                                  Padding(
                                    padding: const EdgeInsets.fromLTRB(
                                        8.0, 0.0, 0.0, 0.0),
                                    child: Container(
                                      color: Colors.grey,
                                      child: Text(
                                        item.name,
                                        style: TextStyle(
                                            color: Colors.transparent),
                                      ),
                                    ),
                                  )
                                ],
                              ),
                              Padding(
                                padding: const EdgeInsets.fromLTRB(
                                    0.0, 8.0, 0.0, 0.0),
                                child: Container(
                                  color: Colors.grey,
                                  child: Text(
                                    item.message,
                                    style: TextStyle(
                                        color: Colors.transparent),
                                  ),
                                ),
                              )
                            ],
                          ),
                        ),
                      ),
                      Center(child: CircularProgressIndicator())
                    ],
                  );
                }
                return ItemCard(item: item);
              },
            );
Run Code Online (Sandbox Code Playgroud)

完整示例https://github.com/MaikuB/incrementally_loading_listview/blob/master/example/lib/main.dart

包使用 ListView index = last item 和 loadMoreOffsetFromBottom 来检测何时加载更多。

    itemBuilder: (itemBuilderContext, index) {    
              if (!_loadingMore &&
              index ==
                  widget.itemCount() -
                      widget.loadMoreOffsetFromBottom -
                      1 &&
              widget.hasMore()) {
            _loadingMore = true;
            _loadingMoreSubject.add(true);
          }
Run Code Online (Sandbox Code Playgroud)


Blo*_*oss 9

这是我查找 listView 结尾的解决方案

_scrollController.addListener(scrollListenerMilli);


if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
      getMoreData();
    }
Run Code Online (Sandbox Code Playgroud)

如果您想在列表视图大小的 1/2 或 3/4 时加载更多数据,请使用这种方式。

if (_scrollController.position.pixels == (_scrollController.position.maxScrollExtent * .75)) {//.5
      getMoreData();
    }
Run Code Online (Sandbox Code Playgroud)

附加-> 确保getMore在到达底部时仅调用 API 一次。您可以通过多种方式解决这个问题,这是通过布尔变量解决这个问题的方法之一。

bool loadMore = false;

if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent && !loadMore) {
     loadMore = true;
     getMoreData().then(() => loadMore = false);
}
Run Code Online (Sandbox Code Playgroud)