Flutter开发:如何实现从全部用户列表中排除已选用户,生成可选用户列表?
解决Flutter中已选用户重复出现在搜索结果的问题
我明白你现在的困扰——想让搜索结果自动排除已经选中的用户,但它们总是又冒出来。咱们一步步来修复这个问题:
核心问题分析
- User类未重写相等判断:默认情况下,Dart对象的
==是比较内存地址的,就算两个User的uid完全一致,也会被当成不同对象,导致remove或者集合差集操作根本失效。 - 差集逻辑只执行一次:你在
initState里计算了一次_selectableUsers,但当时_users是空的,而且后续_selectedUsers变化时也没重新计算可选列表。 - 搜索后的用户移除方式低效且不可靠:用
forEach+remove的方式,不仅效率低,还因为上面的相等判断问题,根本删不掉已选用户。
具体修复步骤
1. 给User类重写==和hashCode
这是解决问题的核心!我们需要用唯一标识(比如uid)来判断两个User是否是同一个人:
class User { final String uid; final String fullname; final String profileImageUrl; User({required this.uid, required this.fullname, required this.profileImageUrl}); // 基于uid判断两个用户是否相等 @override bool operator ==(Object other) => identical(this, other) || other is User && runtimeType == other.runtimeType && uid == other.uid; @override int get hashCode => uid.hashCode; }
2. 重构可选用户列表的生成逻辑
写一个专门的方法,每次_users或_selectedUsers变化时,自动重新计算可选列表:
void _updateSelectableUsers() { // 利用Set的差集特性,快速计算所有用户减去已选用户的结果 final allUsersSet = Set.from(_users); final selectedSet = Set.from(_selectedUsers); final selectableSet = allUsersSet.difference(selectedSet); setState(() { _selectableUsers = selectableSet.toList(); }); }
3. 修正搜索逻辑
搜索完成后,直接对返回的用户列表和已选用户做差集,替代原来低效的循环remove:
onSubmitted: (input) async { if (input.trim().isNotEmpty) { List<User> searchedUsers = await Provider.of<DatabaseService>(context, listen: false) .searchUsers(currentUserUid, input); // 排除已选用户,生成最终可选列表 final searchedSet = Set.from(searchedUsers); final selectedSet = Set.from(_selectedUsers); final filteredUsers = searchedSet.difference(selectedSet).toList(); setState(() { _selectableUsers = filteredUsers; }); } }
4. 同步用户选择/取消的逻辑
当用户选中或取消选中用户时,调用_updateSelectableUsers()来自动更新可选列表:
- 选中用户时:
onTap: () { setState(() { _selectedUsers.add(user); }); _updateSelectableUsers(); }
- 取消选中用户时:
onTap: () { setState(() { _selectedUsers.remove(selectedUser); }); _updateSelectableUsers(); }
完整修改后的SearchGroup代码
class SearchGroup extends StatefulWidget { const SearchGroup({Key? key}) : super(key: key); @override State<SearchGroup> createState() => _SearchGroupState(); } class _SearchGroupState extends State<SearchGroup> { final TextEditingController _searchController = TextEditingController(); List<User> _users = []; // 这里假设后续会从其他地方赋值初始用户列表 List<User> _selectedUsers = []; List<User> _selectableUsers = []; @override void initState() { super.initState(); // 如果有初始用户列表,初始化时就计算可选列表 _updateSelectableUsers(); } void _updateSelectableUsers() { final allUsersSet = Set.from(_users); final selectedSet = Set.from(_selectedUsers); final selectableSet = allUsersSet.difference(selectedSet); setState(() { _selectableUsers = selectableSet.toList(); }); } _clearSearch() { WidgetsBinding.instance .addPostFrameCallback((_) => _searchController.clear()); setState(() => _selectableUsers = []); } @override void dispose() { super.dispose(); _searchController.dispose(); } @override Widget build(BuildContext context) { final currentUserUid = Provider.of<UserProvider>(context).getUser?.uid ?? ''; return Scaffold( appBar: AppBar( title: TextField( controller: _searchController, hintText: 'Search & select users by fullname', suffixIcon: _selectableUsers.isEmpty ? Icon(Icons.search, size: 20.0, color: Color.fromARGB(255, 235, 228, 228)) : IconButton( iconSize: 15, icon: Icon(CupertinoIcons.clear_circled_solid), onPressed: _clearSearch, color: Color.fromARGB(255, 235, 228, 228)), onSubmitted: (input) async { if (input.trim().isNotEmpty) { List<User> searchedUsers = await Provider.of<DatabaseService>(context, listen: false) .searchUsers(currentUserUid, input); // 排除已选用户 final searchedSet = Set.from(searchedUsers); final selectedSet = Set.from(_selectedUsers); final filteredUsers = searchedSet.difference(selectedSet).toList(); setState(() { _selectableUsers = filteredUsers; }); } }), body: Column( children: [ Padding( padding: const EdgeInsets.only(left: 5.0), child: Container( width: double.infinity, height: 100, child: ListView.builder( itemCount: _selectedUsers.length, scrollDirection: Axis.horizontal, itemBuilder: (BuildContext context, int index) { User selectedUser = _selectedUsers[index]; return Container( margin: EdgeInsets.all(10), width: 60, height: 60, decoration: BoxDecoration(shape: BoxShape.circle), child: GestureDetector( onTap: () { setState(() { _selectedUsers.remove(selectedUser); }); _updateSelectableUsers(); }, child: Stack( alignment: AlignmentDirectional.bottomEnd, children: [ CircleAvatar( radius: 60, child: CachedNetworkImage( imageUrl: selectedUser.profileImageUrl, imageBuilder: (context, imageProvider) => Container( height: 60, width: 60, decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(100)), image: DecorationImage( image: imageProvider, fit: BoxFit.cover, ), ), ), ), ), Positioned( top: 3, child: Icon( Icons.remove_circle, size: 20, color: Colors.red, ), ), ], ), ), ); }), ), ), Expanded( child: ListView.separated( separatorBuilder: (BuildContext context, int index) { return const Divider(thickness: 1.0); }, // 已选用户已在顶部横向列表展示,这里只显示可选用户 itemCount: _selectableUsers.length, itemBuilder: (BuildContext context, int index) { User user = _selectableUsers[index]; return ListTile( leading: CircleAvatar( radius: 28, child: CachedNetworkImage( imageUrl: user.profileImageUrl, imageBuilder: (context, imageProvider) => Container( decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(50)), image: DecorationImage( image: imageProvider, fit: BoxFit.cover), ), ), ), ), title: Text( user.fullname, style: TextStyle(fontSize: 14), ), trailing: Icon( CupertinoIcons.circle, color: Colors.grey, ), onTap: () { setState(() { _selectedUsers.add(user); }); _updateSelectableUsers(); }, ); }, ), ), ], ), ); } }
额外说明
我把下方列表的itemCount改成了只显示可选用户,因为已选用户已经在顶部的横向滚动栏里展示,这样界面逻辑更清晰。如果你确实需要下方列表也显示已选用户(但禁止重复选择),可以改回原来的itemCount,但要注意已选用户的交互逻辑是取消选择,并且确保它们不会出现在可选部分。
内容的提问来源于stack exchange,提问作者Eren
相关产品推荐
相关产品推荐

