Flutter SearchDelegate搜索位置时出现重复结果求助
搜索位置时列表重复条目问题修复
我使用SearchDelegate实现搜索功能,支持搜索序列号或站点位置。搜索序列号功能正常,但搜索位置时会出现结果重复的情况,列表重建后显示重复条目。

问题根源
- 双重循环嵌套:在
ListView.builder的itemBuilder回调中,又嵌套了一个for循环遍历整个suggestionList,导致每个itemBuilder索引都会渲染一遍完整的搜索结果列表,这是重复条目的直接原因。 - 冗余的嵌套滚动组件:代码中多层嵌套
SingleChildScrollView和ListView,不仅会导致滚动冲突,还加剧了重复渲染的问题。
修复后的代码
class Search extends SearchDelegate<String> { final List stationList; Search(this.stationList); ScrollController _scrollController = ScrollController(); @override List<Widget> buildActions(BuildContext context) { return [ Padding( padding: EdgeInsets.only(right: 2), child: IconButton( onPressed: () => buildResults(context), icon: Icon(Icons.search)), ), Padding( padding: EdgeInsets.only(left: 2), child: IconButton( onPressed: () => query = '', icon: Icon(Icons.clear)), ), ]; } @override Widget buildLeading(BuildContext context) { return IconButton( onPressed: () => Navigator.pop(context), icon: Icon(Icons.arrow_back_ios), ); } // 提取公共的列表项构建方法,避免代码重复 Widget _buildStationItem(BuildContext context, int index, List suggestionList) { return GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => StationData( stationSerialNumber: suggestionList[index]['serial_number'], ), ), ); }, child: Container( margin: EdgeInsets.only(bottom: ScreenSize.blockSizeVertical * 1), color: ColorTheme.white, child: Padding( padding: const EdgeInsets.all(15), child: Row( children: [ Expanded( flex: 1, child: LayoutBuilder( builder: (context, constraints) { return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ Icon( Icons.wifi_tethering, color: ColorTheme.secondary, size: constraints.maxWidth * 10 / 100, ), SizedBox(width: ScreenSize.blockSizeVertical * 1.5), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ SizedBox(width: constraints.maxWidth * 0.02), Text( '${suggestionList[index]['serial_number']}', style: TextStyle( fontSize: ScreenSize.blockSizeVertical * 2.5, ), ), SizedBox(width: constraints.maxWidth * 0.05), TextButton( onPressed: null, child: Text( '${suggestionList[index]['status']}', style: TextStyle( fontSize: ScreenSize.blockSizeVertical * 1.8, color: suggestionList[index]['status'] == 'Under QC' ? Colors.redAccent : Colors.greenAccent, fontWeight: FontWeight.bold, ), ), ), ], ), SizedBox(height: ScreenSize.blockSizeVertical * 1), Row( children: [ Icon( Icons.location_on, color: ColorTheme.secondary, size: constraints.maxWidth * 4 / 100, ), SizedBox(width: constraints.maxWidth * 1 / 100), Text( '${suggestionList[index]['location']}', style: TextStyle( fontSize: ScreenSize.blockSizeVertical * 2, color: ColorTheme.black, ), ), SizedBox(width: constraints.maxWidth * 5 / 100), Icon( Icons.access_time, color: ColorTheme.secondary, size: constraints.maxWidth * 4 / 100, ), SizedBox(width: constraints.maxWidth * 1 / 100), Text( 'Created at ${suggestionList[index]['date_added'].split(' ')[0]}', style: TextStyle( fontSize: ScreenSize.blockSizeVertical * 2, color: ColorTheme.black, ), ), SizedBox(width: constraints.maxWidth * 10 / 100), ], ), ], ), ], ), ), ], ); }, ), ), Align( alignment: Alignment.centerRight, child: IconButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => StationData( stationSerialNumber: suggestionList[index]['serial_number'], ), ), ); }, icon: Icon( Icons.navigate_next, color: ColorTheme.grey, size: ScreenSize.blockSizeVertical * 6, ), ), ), ], ), ), ), ); } @override Widget buildResults(BuildContext context) { final suggestionList = query.isEmpty ? stationList : stationList .where((element) => element['serial_number'] .toString() .toUpperCase() .startsWith(query.toUpperCase()) || element['location'] .toString() .toUpperCase() .startsWith(query.toUpperCase())) .toList(); return Scaffold( backgroundColor: Color.fromARGB(255, 255, 246, 247), body: ListView.builder( controller: _scrollController, physics: ClampingScrollPhysics(), itemCount: suggestionList.length, itemBuilder: (context, index) => _buildStationItem(context, index, suggestionList), ), ); } @override Widget buildSuggestions(BuildContext context) { final suggestionList = query.isEmpty ? stationList : stationList .where((element) => element['serial_number'] .toString() .toUpperCase() .startsWith(query.toUpperCase()) || element['location'] .toString() .toUpperCase() .startsWith(query.toUpperCase())) .toList(); return Scaffold( backgroundColor: Color.fromARGB(255, 255, 246, 247), body: ListView.builder( controller: _scrollController, physics: ClampingScrollPhysics(), itemCount: suggestionList.length, itemBuilder: (context, index) => _buildStationItem(context, index, suggestionList), ), ); } }
修复说明
- 移除双重循环:删除
itemBuilder内部的for循环,直接使用itemBuilder提供的index渲染单个条目,彻底解决重复显示问题。 - 提取公共组件:将重复的列表项构建逻辑提取为
_buildStationItem方法,减少代码冗余,方便后续维护。 - 简化滚动结构:移除多余的
SingleChildScrollView、Stack和嵌套ListView,只保留最外层的ListView.builder,避免滚动冲突。 - 优化代码风格:将部分匿名函数简化为箭头函数,提升代码可读性。
内容的提问来源于stack exchange,提问作者Eugene Lim
相关产品推荐
相关产品推荐

