Flutter 中如何为Wrap布局的商品列表实现无限滚动加载功能?
Flutter 标签页滚动加载改造方案
- 第一步:将
StatelessWidget改为StatefulWidget,滚动加载需要维护页码、加载状态、商品数据等可变状态,无状态组件无法实现。 - 第二步:声明状态变量,包括当前分页页码、是否加载中标识、是否已加载完全部数据标识、存储商品数据的列表、滚动控制器
ScrollController。 - 第三步:在
initState中给滚动控制器添加监听,判断滚动位置距离底部小于阈值时,触发下一页数据加载逻辑,加载过程中要锁死加载状态避免重复请求。 - 第四步:替换原有的固定
List.generate逻辑,改为通过商品列表动态渲染,列表末尾根据加载状态渲染加载中提示或无更多数据提示。 - 第五步:如果商品总数较大,建议将
Wrap替换为GridView.builder,懒加载渲染组件性能更优。
class ProductList extends StatefulWidget { const ProductList({Key? key}) : super(key: key); @override State<ProductList> createState() => _ProductListState(); } class _ProductListState extends State<ProductList> { final ScrollController _scrollController = ScrollController(); List<dynamic> productList = []; // 替换为实际商品数据模型 int _currentPage = 1; bool _isLoading = false; bool _hasMore = true; final int _pageSize = 10; @override void initState() { super.initState(); // 初始化加载第一页数据 _loadProductList(); // 添加滚动监听 _scrollController.addListener(() { if (_isLoading || !_hasMore) return; // 滚动到距离底部不足100px时触发加载 if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100) { _loadMoreProductList(); } }); } // 加载第一页数据 Future<void> _loadProductList() async { setState(() => _isLoading = true); // 这里替换为实际的接口请求 final newData = await Future.delayed(const Duration(milliseconds: 500), () { return List.generate(_pageSize, (index) => index); }); setState(() { productList = newData; _currentPage = 1; _hasMore = newData.length >= _pageSize; _isLoading = false; }); } // 加载更多数据 Future<void> _loadMoreProductList() async { setState(() => _isLoading = true); // 这里替换为实际的接口请求,传入_currentPage+1 final newData = await Future.delayed(const Duration(milliseconds: 500), () { return List.generate(_pageSize, (index) => productList.length + index); }); setState(() { productList.addAll(newData); _currentPage += 1; _hasMore = newData.length >= _pageSize; _isLoading = false; }); } @override void dispose() { _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SingleChildScrollView( controller: _scrollController, child: Wrap( direction: Axis.horizontal, alignment: WrapAlignment.start, crossAxisAlignment: WrapCrossAlignment.start, runAlignment: WrapAlignment.start, runSpacing: 8.0, spacing: 15.0, children: [ ...List.generate(productList.length, (index) { return SizedBox( width: getProportionateScreenWidth(160), child: GestureDetector( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ AspectRatio( aspectRatio: 1.02, child: Container( padding: EdgeInsets.all(getProportionateScreenWidth(5)), decoration: BoxDecoration( color: kSecondaryColor.withOpacity(0.3), borderRadius: BorderRadius.circular(15), ), child: null), ), const SizedBox(height: 10), ], ), ), ); }), // 底部加载状态提示 if (_isLoading) const Padding( padding: EdgeInsets.symmetric(vertical: 16), child: Center(child: CircularProgressIndicator()), ), if (!_hasMore && productList.isNotEmpty) const Padding( padding: EdgeInsets.symmetric(vertical: 16), child: Center(child: Text('没有更多商品了')), ) ], ), ); } }
额外优化点:可按需添加接口请求失败的重试逻辑、下拉刷新逻辑,数据量超过50条时建议替换为
GridView.builder减少内存占用。
内容的提问来源于stack exchange,提问作者2421zeaman
相关产品推荐
相关产品推荐

