InputFilter.LengthFilter
。对于多数的情况也许使用InputFilter.LengthFilter就够了,但是如果碰到特殊一点的需求,就不行了。比如需要区分汉字和英文,一个汉字算两个英文字符的长度。
最近碰到一个需求,要求在做长度限制的时候,汉字算两个英文字符的长度;超出长度弹一个提示;为了满足这个需求我们必须重写自己的InputFilter.LengthFilter;和ios同学沟通,他们采用的是utf-8编码,他们的方式是在字符长度大于3字节的,就认为长度是2,小于的算1;为了实现两端一致,我们需要这样做。这就迫使我们先学习一下UTF-16和UTF-8的编码
Unicode的编码空间从U+0000到U+10FFFF,共有1,112,064个码位(code point)可用来映射字符. Unicode的编码空间可以划分为17个平面(plane),每个平面包含216(65,536)个码位。17个平面的码位可表示为从U+xx0000到U+xxFFFF,其中xx表示十六进制值从0016到1016,共计17个平面。第一个平面称为基本多语言平面(Basic Multilingual Plane, BMP),或称第零平面(Plane 0)。其他平面称为辅助平面(Supplementary Planes)。基本多语言平面内,从U+D800到U+DFFF之间的码位区块是永久保留不映射到Unicode字符。UTF-16就利用保留下来的0xD800-0xDFFF区块的码位来对辅助平面的字符的码位进行编码。
第一个Unicode平面(码位从U+0000至U+FFFF)包含了最常用的字符。该平面被称为基本多语言平面,缩写为BMP(Basic Multilingual Plane, BMP)。UTF-16与UCS-2编码这个范围内的码位为16比特长的单个码元,数值等价于对应的码位.
辅助平面(Supplementary Planes)中的码位,在UTF-16中被编码为一对16比特长的码元(即32位,4字节),称作代理对(surrogate pair),具体方法是:
举个例子
例如U+10437编码(?):
Unicode标准规定U+D800…U+DFFF的值不对应于任何字符。
但是在使用UCS-2的时代,U+D800…U+DFFF内的值被占用,用于某些字符的映射。但只要不构成代理对,许多UTF-16编码解码还是能把这些不匹配Unicode标准的字符映射正确的辨识、转换成合规的码元[2].按照Unicode标准,这种码元序列本来应算作编码错误。
UTF-8的编码规则
import android.text.InputFilter
import android.text.Spanned
class EditTextLengthFilter(private val mLength: Int, private val listener: ExceedLengthListener? = null) : InputFilter {
companion object {
private const val THRESHOLD = 0x0800 //utf-8从这个字符开始需要3个字节表示,ios目前吧这个字符之后字符长度当成2处理
}
override fun filter(
source: CharSequence?,
start: Int,
end: Int,
dest: Spanned?,
dstart: Int,
dend: Int
): CharSequence? {
var keep = mLength - (getLength(dest, 0, dest?.length ?: 0) - getLength(dest, dstart, dend))
return when {
keep <= 0 -> {
listener?.onExceed(dest)
""
}
keep >= getLength(source, start, end) -> {
null
}
else -> {
listener?.onExceed(dest)
subString(source, start, keep)
}
}
}
private fun getLength(source: CharSequence?, start: Int, end: Int): Int {
var count = 0
if (source == null || start > end || start >= source.length) {
return count
}
var i = Math.max(start, 0)
val length = Math.min(end, source.length)
while (i < length) {
val c1 = source[i++]
var codePoint: Int
codePoint = if (Character.isHighSurrogate(c1) && i < length) {
val c2 = source[i]
if (Character.isLowSurrogate(c2)) {
i++
Character.toCodePoint(c1, c2)
} else {
c1.toInt()
}
} else {
c1.toInt()
}
count += if (codePoint >= THRESHOLD) {
2
} else {
1
}
}
return count
}
private fun subString(source: CharSequence?, start: Int, maxLength: Int): CharSequence? {
if (source == null || start < 0) {
return null
}
var count = 0
var i = Math.max(start, 0)
val length = source.length
var step =1
while (i < length) {
val c1 = source[i++]
var codePoint: Int
step = 1
codePoint = if (Character.isHighSurrogate(c1) && i < length) {
val c2 = source[i]
if (Character.isLowSurrogate(c2)) {
i++
step = 2
Character.toCodePoint(c1, c2)
} else {
c1.toInt()
}
} else {
c1.toInt()
}
count += if (codePoint >= THRESHOLD) {
2
} else {
1
}
if (count == maxLength) {
return source.subSequence(start, i)
} else if (count == maxLength + 1) {
return source.subSequence(start, i - step)
}
}
return source
}
interface ExceedLengthListener {
fun onExceed(originString: CharSequence?)
}
}
https://it.baiked.com/jdkapi1.8/java/lang/Character.UnicodeBlock.html
https://zh.wikipedia.org/wiki/UTF-8
https://zh.wikipedia.org/wiki/UTF-16