You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中infinite_scroll_pagination提前加载全部页面问题求助

Flutter懒加载异常:未滚动到底部就一次性加载全部页面数据

我正在开发一款Flutter应用,采用懒加载(Lazy Loading)方式从服务器加载数据。页面组件结构如下:

  • Stack(包含按钮和容器)
  • SizedBox(用于设置间距)
  • SliverPagedList(展示懒加载数据)

由于SliverPagedList是Sliver组件,我使用CustomScrollView整合前面的Stack和SizedBox组件。目前遇到的问题是:还未滚动到页面底部,所有页面的数据就被一次性全部获取。

相关代码如下:

class CategoryArticlePage extends StatefulWidget {
  CategoryArticlePage({required this.category});
  Category category;

  @override
  State<CategoryArticlePage> createState() => _CategoryArticlePageState();
}

class _CategoryArticlePageState extends State<CategoryArticlePage> {
  SharedPreferences? _sharedPreferences;
  final PostService _postService = PostService();
  Future<void>? _loadData;

  final PagingController<int, posts.Post2> _pageController =
      PagingController(firstPageKey: 0);
  @override
  void initState() {
    _pageController.addPageRequestListener((pageKey) {
      _loadArticles(pageKey);
    });
    super.initState();
  }

  Future<void> _loadArticles(int pageKey) async {
    try {
      _sharedPreferences ??= await SharedPreferences.getInstance();
      final _posts = await _postService.getAllPostsForCategory(
          token: _sharedPreferences!.getString(BEARER_TOKEN)!,
          categoryid: widget.category.categoryId,
          pageKey: pageKey,
          pageSize: PAGE_SIZE);
      final isLastPage = _posts.length < PAGE_SIZE;
      if (isLastPage) {
        _pageController.appendLastPage(_posts);
      } else {
        final nextPageKey = pageKey + 1;
        _pageController.appendPage(_posts, nextPageKey);
      }
    } catch (error) {
      _pageController.error = error;
    }
  }

  @override
  Widget build(BuildContext context) {
    final height = MediaQuery.of(context).size.height -
        MediaQuery.of(context).padding.top -
        MediaQuery.of(context).padding.bottom;
    final width = MediaQuery.of(context).size.width;
    return Scaffold(
      body: SafeArea(
        child: Consumer<CategoryProvider>(
          builder: ((context, categoryProvider, child) {
            return RefreshIndicator(
              onRefresh: () {
                return Future.sync(
                  () => _pageController.refresh(),
                );
              },
              child: CustomScrollView(
                slivers: <Widget>[
                  SliverToBoxAdapter(
                    child: Column(
                      children: [
                        Stack(
                          children: [
                            Container(
                              decoration: categoryTopDecoration,
                              padding: EdgeInsets.only(
                                left: 2,
                                right: 2,
                                bottom: 2,
                              ),
                              alignment: Alignment.bottomLeft,
                              height: height * 0.4,
                              child: ListTile(
                                title: Text(
                                  '${widget.category.categoryName}',
                                  style: TextStyle(
                                    fontSize: 40.sp,
                                    fontWeight: FontWeight.bold,
                                  ),
                                ),
                                subtitle: Text(
                                  '${widget.category.categoryDescription}',
                                  style: TextStyle(
                                    fontSize: 20.sp,
                                    fontWeight: FontWeight.w700,
                                  ),
                                ),
                              ),
                            ),
                            Positioned(
                              right: 10,
                              top: 10,
                              child: Padding(
                                padding: EdgeInsets.only(
                                  right: 4,
                                ),
                                child: OutlinedButton(
                                  onPressed: () async {
                                    if (categoryProvider.subscribedCategoriesIDs
                                        .contains(widget.category.categoryId)) {
                                      bool result = await categoryProvider
                                          .unsubscribeFromCategory(
                                              token: _sharedPreferences!
                                                  .getString(BEARER_TOKEN)!,
                                              userid: _sharedPreferences!
                                                  .getInt(USER_ID)!,
                                              categoryid:
                                                  widget.category.categoryId);
                                    } else {
                                      bool result = await categoryProvider
                                          .subscribeToCategory(
                                              token: _sharedPreferences!
                                                  .getString(BEARER_TOKEN)!,
                                              userid: _sharedPreferences!
                                                  .getInt(USER_ID)!,
                                              categoryid:
                                                  widget.category.categoryId);
                                    }
                                  },
                                  child: (categoryProvider
                                          .subscribedCategoriesIDs
                                          .contains(widget.category.categoryId)
                                      ? Text('Subscribed')
                                      : Text('Subscribe')),
                                ),
                              ),
                            ),
                          ],
                        ),
                        SizedBox(
                          height: height * 0.02,
                        ),
                      ],
                    ),
                  ),
                  PagedSliverList<int, posts.Post2>.separated(
                    pagingController: _pageController,
                    builderDelegate: PagedChildBuilderDelegate<posts.Post2>(
                      animateTransitions: true,
                      transitionDuration: const Duration(milliseconds: 500),
                      itemBuilder: (context, item, index) {
                        return Padding(
                          padding: EdgeInsets.only(
                            bottom: 2,
                            top: 2,
                          ),
                          child: GestureDetector(
                            onTap: () {
                              Get.to(() => ExploreViewArticle(
                                  post: item,
                                  sharedPreferences: _sharedPreferences!));
                            },
                            child: ListTile(
                              leading: SizedBox(
                                child: SizedBox(
                                  height: 100,
                                  width: 100,
                                  child: CachedNetworkImage(
                                    imageUrl:
                                        'https://<blobstorage>/imagecontainer/${item.image}',
                                    placeholder: (context, url) => Center(
                                      child: Container(
                                        height: 50,
                                        width: 50,
                                        child: DataLoadingIndicator(),
                                      ),
                                    ),
                                    errorWidget: (context, url, error) =>
                                        Image.asset(
                                            'images/category_default.jpg'),
                                    fit: BoxFit.cover,
                                  ),
                                ),
                              ),
                              title: Text('${item.title}'),
                            ),
                          ),
                        );
                      },
                    ),
                    separatorBuilder: (context, index) {
                      return Container(
                        height: 2,
                        color: Colors.black,
                      );
                    },
                  ),
                ],
              ),
            );
          }),
        ),
      ),
    );
  }

  @override
  void dispose() {
    log('CategoryArticlePage dispose called');
    _pageController.dispose();
    super.dispose();
  }
}

解决思路

  1. 调整visibleThreshold参数
    PagedSliverList默认会在滚动到距离底部一定数量的item时触发下一页加载。可以显式设置该参数,控制触发时机:
PagedSliverList<int, posts.Post2>.separated(
  visibleThreshold: 3, // 仅当距离底部还有3个item时才加载下一页
  pagingController: _pageController,
  // 其他参数保持不变
)
  1. 检查PAGE_SIZE与列表项高度
    如果PAGE_SIZE过小,或者单个列表项高度偏低,第一页加载后内容无法填满视口,组件会自动触发多页请求直到填满。可以尝试调大PAGE_SIZE,或者优化列表项的高度设置。

  2. 验证服务器分页逻辑
    确认getAllPostsForCategory方法的分页参数(pageKey、pageSize)正确传递给服务器,且服务器返回的每页数据数量符合预期(最后一页除外)。若服务器错误返回全部数据,也会导致一次性加载。

  3. 检查滚动物理效果
    确保CustomScrollView的physics属性未设置强制滚动的逻辑(如AlwaysScrollableScrollPhysics()),避免不必要的加载触发。

内容的提问来源于stack exchange,提问作者ayush

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 12:25:14