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
相关产品推荐
相关产品推荐

