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

如何在Flutter TextField中检测@符号并实现仅输入@时触发弹窗的功能

嘿,我帮你搞定这个自定义的@提及功能!不用第三方包也能完美实现,核心就是监听输入内容,精准触发弹窗~

自定义TextField的@提及弹窗实现方案

核心思路

我们要手动监听TextField的输入变化,判断用户是否输入了@符号(避免连续输入@重复触发),然后控制弹窗的显示/隐藏,同时处理选择用户后的文本插入逻辑,完全贴合你的需求。

具体实现步骤(以Flutter为例)

1. 准备状态变量

先定义几个关键状态来控制整个流程:

  • TextEditingController:用来管理TextField的输入内容
  • bool _showMentionPopup:标记弹窗是否显示
  • int _mentionStartIndex:记录@符号的位置,方便后续插入用户昵称
  • 模拟用户列表:用来展示可选的提及用户

2. 监听输入,触发弹窗逻辑

给TextField的onChanged回调添加判断逻辑:只有当用户输入的最后一个字符是@,且前一个字符不是@时,才显示弹窗;其他情况(比如删除@、输入非@字符)则关闭弹窗。

3. 弹窗布局与交互

用Stack将TextField和弹窗组合,把弹窗定位在TextField上方;用户选择列表中的用户后,自动将@替换为@用户名,并调整光标位置到文本末尾,提升体验。

完整可运行代码示例

class MentionTextField extends StatefulWidget {
  const MentionTextField({super.key});

  @override
  State<MentionTextField> createState() => _MentionTextFieldState();
}

class _MentionTextFieldState extends State<MentionTextField> {
  final TextEditingController _textController = TextEditingController();
  bool _showMentionPopup = false;
  int _mentionStartIndex = -1;
  // 模拟业务中的用户列表
  final List<String> _userList = ["张三", "李四", "王五", "赵六"];

  @override
  void dispose() {
    _textController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Stack(
        children: [
          TextField(
            controller: _textController,
            onChanged: (inputValue) {
              bool shouldShow = false;
              if (inputValue.isNotEmpty) {
                final lastChar = inputValue[inputValue.length - 1];
                // 判断是否触发弹窗:最后一个字符是@,且不是连续输入的@
                if (lastChar == '@' && (inputValue.length == 1 || inputValue[inputValue.length - 2] != '@')) {
                  shouldShow = true;
                  _mentionStartIndex = inputValue.length - 1;
                }
              }
              setState(() {
                _showMentionPopup = shouldShow;
              });
            },
            decoration: const InputDecoration(
              hintText: "输入@提及用户",
              border: OutlineInputBorder(),
            ),
          ),
          // 提及弹窗
          if (_showMentionPopup)
            Positioned(
              top: -(_userList.length * 56), // 根据用户数量动态调整弹窗高度
              left: 0,
              right: 0,
              child: Material(
                elevation: 4,
                borderRadius: BorderRadius.circular(8),
                child: Column(
                  children: _userList.map((userName) {
                    return ListTile(
                      title: Text(userName),
                      onTap: () {
                        // 将@替换为@用户名,并调整光标位置
                        String newText = _textController.text.replaceRange(
                          _mentionStartIndex,
                          _mentionStartIndex + 1,
                          "@$userName ",
                        );
                        _textController.text = newText;
                        _textController.selection = TextSelection.fromPosition(
                          TextPosition(offset: newText.length),
                        );
                        setState(() {
                          _showMentionPopup = false;
                        });
                      },
                    );
                  }).toList(),
                ),
              ),
            ),
        ],
      ),
    );
  }
}

可扩展优化点

  • 支持@后输入字符过滤用户:比如用户输入@张,自动筛选出名字带张的用户
  • 动态计算弹窗位置:通过RenderBox获取TextField的实际位置,让弹窗更精准地对齐
  • 处理光标在中间输入@的情况:如果用户在文本中间插入@,也能触发弹窗

内容的提问来源于stack exchange,提问作者Gbenga B Ayannuga

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 23:34:06