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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.23 14:52:47