Flutter电商应用GridView.builder无限滚动分页异常问题
Flutter电商分类模块无限滚动修复方案
问题根源
你的代码存在几个核心问题导致无法保留历史数据:
- 未在初始化时加载第一页数据到
_items列表,且GridView始终只渲染FutureBuilder返回的当前页数据,完全没用到维护的_items集合 FutureBuilder的future参数在每次build时都会重新创建请求实例,引发重复请求- 使用
SingleChildScrollView嵌套固定高度的GridView,不仅存在滚动冲突,也不符合无限滚动的布局逻辑
修复步骤
- 初始化加载第一页数据:在
initState中调用接口加载第一页数据并存入_items - 移除FutureBuilder,直接基于
_items渲染列表:用_items作为数据源,避免每次build重新请求 - 优化布局结构:移除
SingleChildScrollView和固定高度容器,直接将GridView作为Scaffold的body,避免滚动冲突 - 添加加载状态提示:在列表底部显示加载中或无更多数据的提示
- 避免重复请求:添加加载锁,防止滚动到底部时多次触发
_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
相关产品推荐
相关产品推荐

