Flutter ListView搜索功能异常:加载状态显示问题求助
问题描述
- 当前逻辑:在else分支设置了加载状态,当
_searchController有文本时显示匹配卡片,否则每个空卡片位置都显示加载组件;仅存在1个匹配项时,会显示13个加载组件;原本想留空白的位置也被加载组件占据。 - 期望效果:
- 当
_searchController无文本且无数据时,仅显示1个加载组件,而非和data长度(14个)一致的加载组件 - 列表布局规整,有匹配项时不显示加载组件
- 当
错误截图:列表中出现大量加载组件,与少量匹配卡片混杂,布局杂乱
当前代码
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
相关产品推荐
相关产品推荐

