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

Flutter开发:如何实现从全部用户列表中排除已选用户,生成可选用户列表?

解决Flutter中已选用户重复出现在搜索结果的问题

我明白你现在的困扰——想让搜索结果自动排除已经选中的用户,但它们总是又冒出来。咱们一步步来修复这个问题:

核心问题分析

  1. User类未重写相等判断:默认情况下,Dart对象的==是比较内存地址的,就算两个User的uid完全一致,也会被当成不同对象,导致remove或者集合差集操作根本失效。
  2. 差集逻辑只执行一次:你在initState里计算了一次_selectableUsers,但当时_users是空的,而且后续_selectedUsers变化时也没重新计算可选列表。
  3. 搜索后的用户移除方式低效且不可靠:用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 13:12:27