Flutter列表过滤异常:过滤后首项始终显示原列表首项问题
问题分析与修复方案
核心问题
- TextEditingController 初始化位置错误:你把
searchController放在了build方法里,每次组件重建(比如调用setState或Provider数据变化)都会重新创建控制器,不仅丢失输入框文本状态,还可能干扰过滤逻辑的正确性。 - ListView 未给子项设置唯一Key:Flutter的
ListView.builder会复用widget实例,当过滤后列表数据变化时,第一个item因无唯一标识,会复用未过滤时的旧widget,导致数据不更新。
修复步骤
1. 调整TextEditingController的初始化位置
将控制器移到State类的成员变量中,在initState初始化,dispose时销毁以避免内存泄漏:
class _DiscoverPageState extends State<DiscoverPage> { late TextEditingController searchController; @override void initState() { super.initState(); searchController = TextEditingController(); } @override void dispose() { searchController.dispose(); super.dispose(); } // ... 其他代码 }
2. 给GameCard添加唯一Key
在ListView.builder的itemBuilder中,给GameCard传入基于游戏唯一标识的Key(比如游戏id),确保widget能正确更新:
Expanded( child: ListView.builder( itemCount: filteredGames.length, itemBuilder: (context, index) { final game = filteredGames[index]; return GameCard( key: ValueKey(game.id), game: game, ); }, ), )
3. 优化搜索触发逻辑(可选)
Provider的searchGames会自动通知组件重建,无需额外调用setState:
suffixIcon: InkWell( onTap: () { context.read<FilterProvider>().searchGames(searchController.text); }, child: Icon(Icons.search_sharp, color: Colors.white), ),
修复后的完整代码
// ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables import 'package:flutter/material.dart'; import 'package:megacosm_game_store/data/games_data.dart'; import 'package:megacosm_game_store/models/game_model.dart'; import 'package:megacosm_game_store/providers/filter_provider.dart'; import 'package:megacosm_game_store/utils/blackray_app_bar.dart'; import 'package:megacosm_game_store/utils/user_option_drawer.dart'; import 'package:megacosm_game_store/widgets/game_card.dart'; import 'package:provider/provider.dart'; class DiscoverPage extends StatefulWidget { const DiscoverPage({super.key}); @override State<DiscoverPage> createState() => _DiscoverPageState(); } class _DiscoverPageState extends State<DiscoverPage> { late TextEditingController searchController; @override void initState() { super.initState(); searchController = TextEditingController(); } @override void dispose() { searchController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { List<Game> filteredGames = context.watch<FilterProvider>().filteredGames; return Scaffold( endDrawer: UserOptionsDrawer(), appBar: BlackRayAppBar(), body: Container( color: const Color.fromRGBO(16, 16, 16, 1), child: Column(children: [ Container( height: 60, color: const Color.fromRGBO(16, 16, 16, 1), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SizedBox( width: 250, height: 40, child: TextFormField( controller: searchController, cursorColor: Colors.white, decoration: InputDecoration( contentPadding: const EdgeInsets.symmetric(horizontal: 20), suffixIcon: InkWell( onTap: () { context.read<FilterProvider>().searchGames(searchController.text); }, child: const Icon( Icons.search_sharp, color: Colors.white, )), focusColor: Colors.grey.shade900, fillColor: Colors.grey.shade800, filled: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(20), borderSide: BorderSide.none, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(20), borderSide: const BorderSide(width: 2, color: Colors.white24), )), ), ), InkWell( onTap: () { setState(() {}); }, child: const Icon( Icons.filter_alt_sharp, size: 30, )) ], ), ), ), Expanded( child: ListView.builder( itemCount: filteredGames.length, itemBuilder: (context, index) { final game = filteredGames[index]; return GameCard( key: ValueKey(game.id), game: game, ); }, ), ) ]), ), ); } }
验证说明
修改后,过滤后的列表首项会正确更新,输入框文本状态也能保持稳定。如果Game模型没有id字段,可使用其他唯一属性(比如不重复的name)作为ValueKey的参数。
内容的提问来源于stack exchange,提问作者Mohamed Elmalky
相关产品推荐
相关产品推荐

