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

Flutter ListView搜索功能异常:加载状态显示问题求助

问题描述
  • 当前逻辑:在else分支设置了加载状态,当_searchController有文本时显示匹配卡片,否则每个空卡片位置都显示加载组件;仅存在1个匹配项时,会显示13个加载组件;原本想留空白的位置也被加载组件占据。
  • 期望效果:
    1. 当_searchController无文本且无数据时,仅显示1个加载组件,而非和data长度(14个)一致的加载组件
    2. 列表布局规整,有匹配项时不显示加载组件

错误截图:列表中出现大量加载组件,与少量匹配卡片混杂,布局杂乱


当前代码
ListView.builder(
  itemCount: data.length,
  itemBuilder: (BuildContext context, int index) {
    final video = data[index];
    print("Length is ${snapshot.data!.length}");
    try {
      if (_searchController.text.toString().isEmpty) {
        return InkWell(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (builder) => video_detail(
                  data: video['snippet']["publishTime"],
                  title: video['snippet']["title"],
                  videoId: video["id"]["videoId"],
                  channelTitle: video["snippet"]["channelTitle"],
                  descrption: video["snippet"]["description"],
                  thumbnail: video['snippet']['thumbnails']['medium']['url'],
                ),
              ),
            );
          },
          child: Card(
            margin: const EdgeInsets.all(8),
            color: AppColor.grey200,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Image.network(
                  video['snippet']['thumbnails']['medium']['url'],
                  width: MediaQuery.of(context).size.width * 1,
                ),
                Padding(
                  padding: const EdgeInsets.all(8),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        video['snippet']['title'],
                        style: const TextStyle(
                          fontWeight: FontWeight.bold,
                          fontSize: 18,
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        );
      }
      else if (video['snippet']['title']
          .toString()
          .toLowerCase()
          .contains(_searchController.text.toString().trim().toLowerCase()) ||
          video['snippet']['description']
              .toString()
              .toLowerCase()
              .contains(_searchController.text.toString().trim().toLowerCase())
      ) {
        return Padding(
          padding: const EdgeInsets.all(10.0),
          child: Card(
            child: ListTile(
              leading: Image.network(
                video['snippet']['thumbnails']['medium']['url'],
                width: 50,
              ),
              title: Text(video['snippet']['title']),
              trailing: Icon(Icons.arrow_forward),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (builder) => video_detail(
                      data: video['snippet']["publishTime"],
                      title: video['snippet']["title"],
                      videoId: video["id"]["videoId"],
                      channelTitle: video["snippet"]["channelTitle"],
                      descrption: video["snippet"]["description"],
                      thumbnail: video['snippet']['thumbnails']['medium']['url'],
                    ),
                  ),
                );
              },
            ),
          ),
        );
      }
      else{
        return Center(
          child: LoadingAnimationWidget.fourRotatingDots(
            color: AppColor.grey,
            size: 200,
          ),
        );
      }
    }
    catch(e){
      print(e);
    }
  },
),

解决方案

1. 提前处理过滤后的列表

不在itemBuilder里逐个判断匹配,先在外部生成过滤后的列表,精准控制列表数量和内容:

// 在build方法内、data获取后添加以下逻辑
List<dynamic> filteredVideos = [];
final searchQuery = _searchController.text.trim().toLowerCase();
if (searchQuery.isEmpty) {
  filteredVideos = data;
} else {
  filteredVideos = data.where((video) {
    final title = video['snippet']['title'].toString().toLowerCase();
    final description = video['snippet']['description'].toString().toLowerCase();
    return title.contains(searchQuery) || description.contains(searchQuery);
  }).toList();
}

2. 重构ListView.builder逻辑

  • 根据filteredVideos长度决定显示内容:无数据时显示单个加载组件;有数据时渲染对应卡片
  • 移除原else分支的加载组件,确保仅无匹配项时显示加载
ListView.builder(
  itemCount: filteredVideos.isEmpty ? 1 : filteredVideos.length,
  itemBuilder: (BuildContext context, int index) {
    // 无匹配数据时显示单个加载组件
    if (filteredVideos.isEmpty) {
      return const Center(
        child: LoadingAnimationWidget.fourRotatingDots(
          color: AppColor.grey,
          size: 200,
        ),
      );
    }

    final video = filteredVideos[index];
    try {
      // 无搜索文本时渲染原始卡片布局
      if (_searchController.text.trim().isEmpty) {
        return InkWell(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (builder) => video_detail(
                  data: video['snippet']["publishTime"],
                  title: video['snippet']["title"],
                  videoId: video["id"]["videoId"],
                  channelTitle: video["snippet"]["channelTitle"],
                  descrption: video["snippet"]["description"],
                  thumbnail: video['snippet']['thumbnails']['medium']['url'],
                ),
              ),
            );
          },
          child: Card(
            margin: const EdgeInsets.all(8),
            color: AppColor.grey200,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Image.network(
                  video['snippet']['thumbnails']['medium']['url'],
                  width: MediaQuery.of(context).size.width,
                ),
                Padding(
                  padding: const EdgeInsets.all(8),
                  child: Text(
                    video['snippet']['title'],
                    style: const TextStyle(
                      fontWeight: FontWeight.bold,
                      fontSize: 18,
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
      } 
      // 有搜索文本时渲染列表项布局
      else {
        return Padding(
          padding: const EdgeInsets.all(10.0),
          child: Card(
            child: ListTile(
              leading: Image.network(
                video['snippet']['thumbnails']['medium']['url'],
                width: 50,
              ),
              title: Text(video['snippet']['title']),
              trailing: const Icon(Icons.arrow_forward),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (builder) => video_detail(
                      data: video['snippet']["publishTime"],
                      title: video['snippet']["title"],
                      videoId: video["id"]["videoId"],
                      channelTitle: video["snippet"]["channelTitle"],
                      descrption: video["snippet"]["description"],
                      thumbnail: video['snippet']['thumbnails']['medium']['url'],
                    ),
                  ),
                );
              },
            ),
          ),
        );
      }
    } catch (e) {
      print(e);
      // 捕获异常时返回空白占位,避免布局崩溃
      return const SizedBox.shrink();
    }
  },
),

3. 额外优化建议

  • 给Image.network添加占位符和错误处理,避免图片加载失败导致布局异常
  • 给搜索框添加防抖处理,减少频繁过滤的性能消耗

内容的提问来源于stack exchange,提问作者Muhammad Zohaib

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 10:32:13