当输入框的长度限制是以UTF-8字节为单位,但又需要支持输入中文、emoji,并且还不能出现emoji乱码?
实现以上需求,你需要知道的事:
-
中文字符转UTF-8为3个字节,emoji为4个字节。
-
emoji由高位代理和低位代理组成,少其中一个都会导致乱码。
-
InputFilter中的source、start、end、dest、dstart、dend都代表了什么?
理解以上要点之后,就着手代码的编写,方法的核心是:
- 先将所有的字符(英文、中文、emoji、其他字符)都转成UTF-8,判断新输入的字符是否满足长度需求。
- 若不满足,则需要按照最大允许的长度,截断输入的字符。
- 判断截断后的字符是否存在emoji末位乱码情况,有则回退,不允许输入半个emoji
/**
* 限制emoji末尾乱码的字节长度限制过滤器
*
* @param source 输入的文字
* @param start 输入-0,删除-0
* @param end 输入-文字的长度,删除-0
* @param dest 原先显示的内容
* @param dstart 输入-原光标位置,删除-光标删除结束位置
* @param dend 输入-原光标位置,删除-光标删除开始位置
* @return
*/
class LimitCharLengthFilter(private var max: Int) : InputFilter {
override fun filter(
source: CharSequence,
start: Int,
end: Int,
dest: Spanned,
dstart: Int,
dend: Int
): CharSequence {
GlobalTouchUtil.onGlobalTouch()
val bytes = dest.toString().toByteArray(StandardCharsets.UTF_8).size
val sourceBytes = source.toString().toByteArray(StandardCharsets.UTF_8).size
var keep = 0
if (bytes + sourceBytes <= max) {
//输入source之后整体长度不会超过max限制
keep = source.length
} else {
for (i in source.indices) {
//转成UTF-8
val currentSourceBytes = source.subSequence(0, i + 1)
.toString().toByteArray(StandardCharsets.UTF_8).size
if (bytes + currentSourceBytes > max) {
//超长则截断,记录当前标记,为了不显示后面的字符
keep = i
//若截断后是半个emoji结尾
if (Character.isHighSurrogate(source[ keep ]) || Character.isLowSurrogate(source[ keep ])) {
//回退这半个emoji,不展示这个emoji,防止乱码
--keep
if (keep == start) {
return ""
}
}
break
}
}
}
return if (keep <= 0) "" else source.subSequence(start, keep + start)
}
}
方法调用举例如下:
const val USER_NAME_MAX_LENGTH = 63
//ChineseAndEnglishInputFilter是自定义的限制输入字符范围的filter
//LimitCharLengthFilter(USER_NAME_MAX_LENGTH )是本文介绍的限制emoji末尾乱码的字节长度限制过滤器
etInput.getEditText()?.filters = listOf(ChineseAndEnglishInputFilter,LimitCharLengthFilter(USER_NAME_MAX_LENGTH ))