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

Flutter/Dart如何正确截断含emoji等特殊字符的字符串避免应用崩溃

Dart/Flutter 多字节字符安全截断方案

问题根因

Dart 中 String 类型默认以 UTF-16 码位(code unit)为计数单位,普通英文字符占1个码位,emoji等特殊Unicode字符占2个甚至更多码位。你当前使用的substring方法直接按码位截断,当截断位置刚好落在多码位字符中间时,会生成非法的UTF-16字符片段,Flutter的Text组件无法解析该片段就会触发崩溃。

最优实现方案

推荐使用官方维护的characters库处理,该库以用户可见的单个字形(grapheme cluster)为处理单位,无论字符占用多少码位都不会被拆分,效果和PHP的mb_substr完全一致。

1. 实现代码

import 'package:characters/characters.dart';

class FormatMixin {
  String mbTrim(String s, int trimLength) {
    final visibleChars = s.characters;
    // 字符数未超过限制直接返回
    if (visibleChars.length <= trimLength) return s;
    // 取前trimLength个可见字符后拼接省略号
    return '${visibleChars.take(trimLength)}...';
  }
}

如果更习惯用扩展方法调用,也可以写成:

extension StringMbTrim on String {
  String mbTrim(int maxLength, {bool addEllipsis = true}) {
    final chars = characters;
    if (chars.length <= maxLength) return this;
    final trimmed = chars.take(maxLength).toString();
    return addEllipsis ? '$trimmed...' : trimmed;
  }
}
// 调用方式:"testme😊socool😊abc".mbTrim(7)

2. 测试用例验证

测试文本:text = "testme😊socool😊abc"

  • mbTrim(text, 6) 取前6个可见字符,返回 testme
  • mbTrim(text, 7) 取前7个可见字符,返回 testme😊
  • mbTrim(text, 8) 取前8个可见字符,返回 testme😊s
    完全匹配预期效果。

无第三方依赖原生实现

如果不想引入额外依赖,也可以基于Dart原生的runes(Unicode标量值)实现,满足绝大多数普通场景需求:

class FormatMixin {
  String mbTrim(String s, int trimLength) {
    if (s.runes.length <= trimLength) return s;
    return '${String.fromCharCodes(s.runes.take(trimLength))}...';
  }
}

注意:原生runes方案对带组合标记的特殊emoji(如带肤色、带性别标识的组合emoji)支持不完善,复杂场景还是推荐使用characters库方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.10 10:54:02