Flutter ListView Builder全局等待加载页面实现问题
Flutter ListView Builder全局等待加载页面实现问题
我看了你现在的代码,问题核心在于每个卡片的每个字段都单独发起网络请求,而且每个请求都套了FutureBuilder,这就导致页面加载时到处都是小加载圈,完全不是你想要的全局等待效果。咱们可以通过「提前统一加载所有数据+页面级状态管理」来解决这个问题,具体步骤如下:
一、重构数据获取逻辑:一次性加载所有所需数据
首先要改掉“每个字段发一次请求”的做法,这样既浪费网络资源,也很难统一控制加载状态。咱们可以先定义一个数据模型来存储每个条目完整信息,然后一次性获取所有条目的数据:
1. 定义数据模型
class ItemData { final String type; final String taille; final String prix; final String image; final String description; ItemData({ required this.type, required this.taille, required this.prix, required this.image, required this.description, }); // 从JSON数据转换为模型 factory ItemData.fromJson(Map<String, dynamic> json) { return ItemData( type: json["type"], taille: json["taille"], prix: json["prix"], image: json["image"], description: json["description"], ); } }
2. 新增统一获取所有数据的方法
修改你的getDataSearch,或者新增一个专门的方法来一次性获取所有条目数据(如果后端支持,最好让后端提供一个接口直接返回所有数据,这样效率更高;如果只能单个获取,就循环请求):
Future<List<ItemData>> getAllItems() async { // 先获取总条目数 var countUrl = Uri.parse("https://xf8s7auoez.preview.infomaniak.website/getdata.php"); var countRes = await http.post(countUrl, body: { "post": "rows", "file": "get_data", }); int totalCount = int.parse(countRes.body); // 循环获取每个条目的完整数据 List<ItemData> items = []; for (int i = 1; i <= totalCount; i++) { var detailUrl = Uri.parse("https://xf8s7auoez.preview.infomaniak.website/getdata.php"); var detailRes = await http.post(detailUrl, body: { "index": i.toString(), "post": "index", "file": "get_data", }); Map<String, dynamic> data = jsonDecode(detailRes.body); items.add(ItemData.fromJson(data)); } return items; }
二、页面级别管理加载状态
在_RefreshIndicatorViewState里添加加载状态变量,统一控制全局加载和列表显示:
1. 新增状态变量
class _RefreshIndicatorViewState extends State<RefreshIndicatorView> { int initNumber = 1; int selectedValue = 1; final GlobalKey<ScaffoldState> _key = GlobalKey(); // 新增加载状态变量 bool isLoading = true; // 存储所有条目数据 List<ItemData> itemsList = []; @override void initState() { super.initState(); // 初始化时加载所有数据 loadAllData(); } // 封装加载数据的方法 Future<void> loadAllData() async { try { List<ItemData> items = await getAllItems(); setState(() { itemsList = items; initNumber = items.length; isLoading = false; // 加载完成,关闭加载状态 }); } catch (e) { // 这里可以处理加载失败的情况,比如提示用户 setState(() { isLoading = false; }); print("加载失败:$e"); } }
2. 修改下拉刷新逻辑
原来的下拉刷新只刷新状态,现在要改成重新加载所有数据:
onRefresh: () async { setState(() { isLoading = true; // 刷新时重新显示加载状态 }); await loadAllData(); },
3. 根据加载状态显示对应UI
修改build方法,根据isLoading来显示全局加载页还是列表:
@override Widget build(BuildContext context) { return RefreshIndicator( // 原有配置保持不变 displacement: 250, backgroundColor: Color.fromARGB(255, 110, 155, 191), color: Colors.white, strokeWidth: 3, triggerMode: RefreshIndicatorTriggerMode.onEdge, onRefresh: () async { setState(() { isLoading = true; }); await loadAllData(); }, child: Scaffold( key: _key, backgroundColor: Color.fromARGB(255, 110, 155, 191), appBar: BaseAppBar(), drawer: createDrawer(context), // 根据加载状态切换UI body: isLoading ? Center(child: CircularProgressIndicator.adaptive()) // 全局加载指示器 : _buildCardDesign(), ), ); }
三、重构卡片组件:直接使用已加载的数据
原来的Type、Taille等组件不需要再用FutureBuilder了,直接从ItemData中取数据即可:
1. 修改卡片构建方法
Widget _buildCardDesign() { return Container( margin: EdgeInsets.all(5), child: ListView.builder( itemCount: itemsList.length, shrinkWrap: true, physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), scrollDirection: Axis.vertical, prototypeItem: _buildCardView(itemsList[0]), itemBuilder: (BuildContext context, int index) { return _buildCardView(itemsList[index]); }), ); } Widget _buildPhoto(ItemData item) { return ClipRRect( borderRadius: BorderRadius.circular(5.0), child: Image.network( item.image, height: 65.0, width: 65.0, fit: BoxFit.cover, ), ); } Widget _buildCardView(ItemData item) { return Container( width: MediaQuery.of(context).size.width, child: Container( margin: EdgeInsets.all(10), height: 100, width: MediaQuery.of(context).size.width, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(10))), child: GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ArticlePage( // 这里可以根据需要传递item的index或者整个item对象 itemsList.indexOf(item)+1, Text(item.type), Text(item.prix), Text(item.description) ), ), ); }, child: Container( padding: EdgeInsets.fromLTRB(15, 15, 30, 15), child: Row( children: [ _buildPhoto(item), DefaultTextStyle( style: TextStyle( fontSize: 19, fontWeight: FontWeight.w800, color: Color.fromARGB(255, 110, 155, 191)), child: Container( padding: EdgeInsets.only(left: 40), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(item.type, style: TextStyle(color: Color.fromARGB(255, 110, 155, 191), fontSize: 17)), Padding( padding: EdgeInsets.only(top: 8), child: Text(item.taille, style: TextStyle(color: Color.fromARGB(255, 168, 168, 168))), ), Padding( padding: EdgeInsets.only(top: 3), child: Row( children: [ Text(item.prix), Text(" €"), ], ), ), ], ), ), ), ], ), ), ), ), ); }
四、额外优化建议
- 如果你后端支持的话,强烈建议修改接口,让后端一次性返回所有条目的完整数据,这样可以避免循环发起多个请求,大幅提升加载速度和用户体验。
- 可以添加加载失败的UI状态,比如加载出错时显示一个“重新加载”按钮,让用户可以重试。
- 图片可以添加占位符和错误处理,比如用
FadeInImage或者给Image.network添加errorBuilder。
这样修改后,用户进入页面时会先看到一个全局的加载指示器,等所有数据都加载完成后,一次性渲染所有卡片,就不会出现到处都是小加载圈的问题了。
备注:内容来源于stack exchange,提问作者Julian Rochegude
相关产品推荐
相关产品推荐

