Flutter中如何将GridView的CircularProgressIndicator显示在网格下方?
解决Flutter GridView中CircularProgressIndicator显示位置错误的问题
你当前的问题是把加载指示器当成了GridView的最后一个网格项,导致它被网格布局约束,显示在网格内部。要把它移到GridView下方,需要调整布局结构,将指示器和GridView拆分为两个独立组件,而非网格项的一部分。
修正思路
- 移除
itemCount中的+1,让GridView仅渲染商品列表 - 将GridView与指示器放入垂直布局容器,让指示器固定在GridView下方
- 通过加载状态变量控制指示器的显示/隐藏
代码实现
方案一:Column + Expanded(推荐,简单直接)
利用Expanded让GridView占满剩余空间,指示器自然位于底部:
Widget buildCatalog(List<Product> product, bool isLoading) => Column( children: [ Expanded( child: GridView.builder( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 300, childAspectRatio: MediaQuery.of(context).size.width * 1.22 / MediaQuery.of(context).size.height, crossAxisSpacing: 10, mainAxisSpacing: 10, ), controller: sController, itemCount: product.length, // 移除+1,仅渲染商品 itemBuilder: (context, index) { final media = product[index].media?.map((e) => e.toJson()).toList(); final photo = media?[0]['links']['local']['thumbnails']['350']; final productItem = product[index]; return GestureDetector( onTap: () {}, child: Container( child: Column( children: <Widget>[ InkWell( child: Container( margin: EdgeInsets.only(top: 5), child: Image.network(photo ?? ''), ), ), SizedBox(height: 10), Text( '${productItem.name}\n', textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 14), ), SizedBox(height: 8), Text('${productItem.price} ₸', textAlign: TextAlign.start, style: TextStyle(fontSize: 20, color: Colors.red)), SizedBox(height: 5), buildButton(index, productItem.id), ], ), ), ); }, ), ), // 加载指示器固定在GridView下方 if (isLoading) Padding( padding: EdgeInsets.symmetric(vertical: 16), child: const Center(child: CircularProgressIndicator()), ), ], );
方案二:CustomScrollView(适配复杂滚动场景)
如果需要更灵活的滚动效果,可以将GridView作为Sliver组件,通过SliverToBoxAdapter添加底部指示器:
Widget buildCatalog(List<Product> product, bool isLoading) => CustomScrollView( controller: sController, slivers: [ SliverGrid( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 300, childAspectRatio: MediaQuery.of(context).size.width * 1.22 / MediaQuery.of(context).size.height, crossAxisSpacing: 10, mainAxisSpacing: 10, ), delegate: SliverChildBuilderDelegate( (context, index) { final media = product[index].media?.map((e) => e.toJson()).toList(); final photo = media?[0]['links']['local']['thumbnails']['350']; final productItem = product[index]; return GestureDetector( onTap: () {}, child: Container( child: Column( children: <Widget>[ InkWell( child: Container( margin: EdgeInsets.only(top: 5), child: Image.network(photo ?? ''), ), ), SizedBox(height: 10), Text( '${productItem.name}\n', textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 14), ), SizedBox(height: 8), Text('${productItem.price} ₸', textAlign: TextAlign.start, style: TextStyle(fontSize: 20, color: Colors.red)), SizedBox(height: 5), buildButton(index, productItem.id), ], ), ), ); }, childCount: product.length, ), ), // 底部加载指示器 if (isLoading) SliverToBoxAdapter( child: Padding( padding: EdgeInsets.symmetric(vertical: 16), child: const Center(child: CircularProgressIndicator()), ), ), ], );
注意事项
- 新增的
isLoading参数需要在父组件中维护,请求数据时设为true,加载完成后设为false - 给指示器添加
Padding可以避免紧贴GridView,提升视觉舒适度
内容的提问来源于stack exchange,提问作者st4tic
相关产品推荐
相关产品推荐

