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

Flutter RTL TextField添加Emoji后输入RTL字符乱码问题求助

问题描述
  • 聊天模块实现底部弹出表情选择器,通过文本拼接方式将emoji添加到TextEditingController,表情与文本混合在LTR模式下正常
  • 适配阿拉伯语等RTL语言,文本方向可随首个RTL字符自动切换
  • 仅安卓真机出现异常:TextField处于RTL模式时,添加至少一个emoji后输入RTL字符,最后一个Unicode字符会显示乱码(示例:أهلا 🙃😂😇𒈩?)
  • iOS模拟器、安卓模拟器无此问题,使用emoji_picker_flutter包后问题依旧
  • 临时方案:在每个emoji后添加ZWSP(零宽空格,\u200C)可解决乱码,但用户需两次删除才能移除emoji,体验不佳
原因分析

安卓系统原生双向文本排版算法对emoji(中性Unicode字符)与RTL语言字符的组合处理存在兼容性缺陷:

  • emoji属于中性字符,在RTL语境下会被双向排版算法归类为LTR方向
  • 当emoji后紧跟RTL字符时,算法错误干扰了RTL字符的连写字形组合(阿拉伯语等RTL语言依赖字符连写渲染),导致最后一个字符被错误解析为乱码
解决方案

方案1:优化零宽字符删除体验(推荐)

在emoji后添加ZWSP的同时,监听TextEditingController的文本变化,当用户删除ZWSP时自动连带删除前面的emoji,实现一次删除即可移除emoji:

// 添加emoji的方法
void addEmoji(String emoji) {
  final currentText = widget.msg.text;
  // 拼接emoji和ZWSP
  final newText = '$currentText$emoji\u200C';
  final newCursorPosition = newText.length;
  widget.msg.value = TextEditingValue(
    text: newText,
    selection: TextSelection.collapsed(offset: newCursorPosition),
  );
}

// 初始化时添加文本监听
@override
void initState() {
  super.initState();
  widget.msg.addListener(_handleTextChange);
}

// 处理文本变化,优化删除逻辑
void _handleTextChange() {
  final text = widget.msg.text;
  final selection = widget.msg.selection;
  
  // 检查是否删除了ZWSP且前一个字符是emoji
  if (selection.start == selection.end && selection.start > 0) {
    final prevChar = text[selection.start - 1];
    if (prevChar == '\u200C' && selection.start >= 2) {
      final charBeforeZwsp = text[selection.start - 2];
      final emojiRune = charBeforeZwsp.runes.first;
      // 判断是否为emoji(覆盖常见emoji Unicode范围)
      if ((emojiRune >= 0x1F600 && emojiRune <= 0x1F64F) ||
          (emojiRune >= 0x1F300 && emojiRune <= 0x1F5FF) ||
          (emojiRune >= 0x2600 && emojiRune <= 0x26FF)) {
        // 同时删除emoji和ZWSP
        widget.msg.value = TextEditingValue(
          text: text.substring(0, selection.start - 2) + text.substring(selection.start),
          selection: TextSelection.collapsed(offset: selection.start - 2),
        );
      }
    }
  }
}

@override
void dispose() {
  widget.msg.removeListener(_handleTextChange);
  super.dispose();
}

方案2:使用双向控制字符替代ZWSP

在emoji后添加右到左标记(RLM,\u200F),强制后续文本保持RTL方向,避免排版算法出错,RLM视觉不可见,部分安卓输入法支持一次删除emoji+RLM:

void addEmoji(String emoji) {
  final currentText = widget.msg.text;
  final newText = '$currentText$emoji\u200F';
  widget.msg.value = TextEditingValue(
    text: newText,
    selection: TextSelection.collapsed(offset: newText.length),
  );
}

方案3:自定义TextInputFormatter自动处理排版

通过TextInputFormatter拦截输入,自动在emoji后添加RLM,无需修改添加emoji的逻辑:

class RtlEmojiFormatter extends TextInputFormatter {
  @override
  TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
    // 仅在RTL模式下处理
    if (Directionality.of(WidgetsBinding.instance.context) == TextDirection.rtl) {
      final text = newValue.text;
      // 匹配常见emoji的正则
      final emojiRegex = RegExp(
        r'[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]',
        unicode: true,
      );
      // 在每个emoji后添加RLM
      final processedText = emojiRegex.replaceAllMapped(text, (match) => '${match.group(0)}\u200F');
      return TextEditingValue(
        text: processedText,
        selection: TextSelection.collapsed(offset: processedText.length),
        composing: TextRange.empty,
      );
    }
    return newValue;
  }
}

// 在TextField中添加formatter
TextField(
  // ...其他原有属性
  inputFormatters: [RtlEmojiFormatter()],
)
验证建议
  • 在安卓真机上测试上述方案,重点验证RTL字符连续输入、emoji删除的流畅性
  • 针对阿拉伯语等连写语言,需测试多字符组合后的渲染效果

内容的提问来源于stack exchange,提问作者OKKO

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 17:43:18