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

Flutter电商应用GridView.builder无限滚动分页异常问题

Flutter电商分类模块无限滚动修复方案

问题根源

你的代码存在几个核心问题导致无法保留历史数据:

  • 未在初始化时加载第一页数据到_items列表,且GridView始终只渲染FutureBuilder返回的当前页数据,完全没用到维护的_items集合
  • FutureBuilder的future参数在每次build时都会重新创建请求实例,引发重复请求
  • 使用SingleChildScrollView嵌套固定高度的GridView,不仅存在滚动冲突,也不符合无限滚动的布局逻辑

修复步骤

  1. 初始化加载第一页数据:在initState中调用接口加载第一页数据并存入_items
  2. 移除FutureBuilder,直接基于_items渲染列表:用_items作为数据源,避免每次build重新请求
  3. 优化布局结构:移除SingleChildScrollView和固定高度容器,直接将GridView作为Scaffold的body,避免滚动冲突
  4. 添加加载状态提示:在列表底部显示加载中或无更多数据的提示
  5. 避免重复请求:添加加载锁,防止滚动到底部时多次触发_fetchMore

修改后的完整代码

class CategoryDetail extends StatefulWidget {
  final CatHome catHome;
  CategoryDetail(this.catHome);

  @override
  _CategoryDetailState createState() => _CategoryDetailState(catHome);
}

class _CategoryDetailState extends State<CategoryDetail> {
  List<CategoryItems> _items = [];
  final CatHome catHome;
  int _currentPage = 1;
  final ScrollController _scrollController = ScrollController();
  bool _hasMore = true;
  bool _isLoading = false;

  _CategoryDetailState(this.catHome);

  @override
  void initState() {
    super.initState();
    _loadInitialData();
    _scrollController.addListener(() {
      if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
        _fetchMore();
      }
    });
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  // 加载初始第一页数据
  Future<void> _loadInitialData() async {
    setState(() => _isLoading = true);
    final initialItems = await catitems_api().fetchdeails(catHome, _currentPage);
    setState(() {
      _items.addAll(initialItems);
      _isLoading = false;
    });
  }

  // 加载更多数据
  Future<void> _fetchMore() async {
    if (_hasMore && !_isLoading) {
      setState(() {
        _currentPage++;
        _isLoading = true;
      });
      final newItems = await catitems_api().fetchdeails(catHome, _currentPage);
      setState(() {
        if (newItems.isEmpty) {
          _hasMore = false;
        } else {
          _items.addAll(newItems);
        }
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    final data = EasyLocalizationProvider.of(context).data;

    return EasyLocalizationProvider(
      data: data,
      child: Scaffold(
        appBar: AppBar(
          backgroundColor: Colors.white,
          title: Text(
            AppLocalizations.of(context).tr('Category'),
            style: const TextStyle(
              fontWeight: FontWeight.w700,
              fontSize: 17.0,
              color: Colors.black54,
              fontFamily: "Gotik",
            ),
          ),
          centerTitle: true,
          iconTheme: const IconThemeData(color: Color(0xFF6991C7)),
          elevation: 0.0,
        ),
        body: _isLoading && _items.isEmpty
            ? const Center(child: CircularProgressIndicator())
            : GridView.builder(
                controller: _scrollController,
                padding: const EdgeInsets.all(8.0),
                gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 2,
                  childAspectRatio: 0.7,
                  mainAxisSpacing: 8.0,
                  crossAxisSpacing: 8.0,
                ),
                itemCount: _items.length + (_hasMore ? 1 : 0),
                itemBuilder: (BuildContext context, int index) {
                  // 底部加载提示
                  if (index == _items.length) {
                    return _isLoading
                        ? const Center(child: CircularProgressIndicator())
                        : const SizedBox.shrink();
                  }

                  final CategoryItems item = _items[index];
                  return InkWell(
                    onTap: () {
                      productDet_api()
                          .fetchdeails(item.id)
                          .then((itemList) {
                        Navigator.of(context).push(
                          PageRouteBuilder(
                            pageBuilder: (_, __, ___) => detailProduk(itemList.first),
                            transitionDuration: const Duration(milliseconds: 950),
                            transitionsBuilder: (_, Animation<double> animation, __, Widget child) {
                              return Opacity(
                                opacity: animation.value,
                                child: child,
                              );
                            },
                          ),
                        );
                      });
                    },
                    child: Container(
                      decoration: BoxDecoration(
                        color: Colors.white,
                        boxShadow: [
                          BoxShadow(
                            color: Colors.black12.withOpacity(0.1),
                            spreadRadius: 0.2,
                            blurRadius: 5.0,
                            offset: const Offset(0.0, 2.0),
                          ),
                        ],
                      ),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.center,
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                          Image.network(
                            item.image,
                            width: 150.0,
                            height: 150.0,
                            fit: BoxFit.cover,
                          ),
                          const SizedBox(height: 10.0),
                          Text(
                            item.name,
                            style: const TextStyle(
                              fontWeight: FontWeight.w600,
                              fontSize: 14.0,
                              color: Colors.black,
                            ),
                          ),
                          const SizedBox(height: 5.0),
                          Text(
                            "\$${item.price.toString()}",
                            style: const TextStyle(
                              fontWeight: FontWeight.w600,
                              fontSize: 14.0,
                              color: Colors.black,
                            ),
                          ),
                        ],
                      ),
                    ),
                  );
                },
              ),
      ),
    );
  }
}

关键改动说明

  • 将_items的类型从List<FutureOr>改为List<CategoryItems>,类型更安全
  • 添加_isLoading状态锁,防止重复请求
  • 初始化时加载第一页数据到_items
  • 直接用GridView.builder基于_items渲染所有已加载的数据,itemCount包含加载提示项
  • 移除了多余的SingleChildScrollView和固定高度容器,解决滚动冲突
  • 在列表底部添加加载状态提示,提升用户体验

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 10:25:22