android EditText输入长度限制
我相信大家一定都遇到过这样的需求,PM要求你限制EditText输入字符的个数;很多同学肯定会想到使用
InputFilter.LengthFilter
。对于多数的情况也许使用InputFilter.LengthFilter就够了,但是如果碰到特殊一点的需求,就不行了。比如需要区分汉字和英文,一个汉字算两个英文字符的长度。
首先我们分析一下InputFilter.LengthFilter存在的问题:
1、InputFilter.LengthFilter使用的是char的长度,而不是真正意义上的字符长度;由于java采用的是UTF-16,多数的常用字符都是一个char表示,所以很多时候我们测试的时候不会出错;但是对于一些不常用的字符就是两个char表示,这时候就有可能出错;
2、InputFilter.LengthFilter超出长度,没有回调,如果想超出长度,给出提示,这个很难用InputFilter.LengthFilter实现
最近碰到一个需求,要求在做长度限制的时候,汉字算两个英文字符的长度;超出长度弹一个提示;为了满足这个需求我们必须重写自己的InputFilter.LengthFilter;和ios同学沟通,他们采用的是utf-8编码,他们的方式是在字符长度大于3字节的,就认为长度是2,小于的算1;为了实现两端一致,我们需要这样做。这就迫使我们先学习一下UTF-16和UTF-8的编码
UTF-16编码
UTF-16描述
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区块的码位来对辅助平面的字符的码位进行编码。
从U+0000至U+D7FF以及从U+E000至U+FFFF的码位
第一个Unicode平面(码位从U+0000至U+FFFF)包含了最常用的字符。该平面被称为基本多语言平面,缩写为BMP(Basic Multilingual Plane, BMP)。UTF-16与UCS-2编码这个范围内的码位为16比特长的单个码元,数值等价于对应的码位.
从U+10000到U+10FFFF的码位
辅助平面(Supplementary Planes)中的码位,在UTF-16中被编码为一对16比特长的码元(即32位,4字节),称作代理对(surrogate pair),具体方法是:
- 码位减去0x10000,得到的值的范围为20比特长的0…0xFFFFF.
- 高位的10比特的值(值的范围为0…0x3FF)被加上0xD800得到第一个码元或称作高位代理(high surrogate),值的范围是0xD800…0xDBFF.由于高位代理比低位代理的值要小,所以为了避免混淆使用,Unicode标准现在称高位代理为前导代理(lead surrogates)。
- 低位的10比特的值(值的范围也是0…0x3FF)被加上0xDC00得到第二个码元或称作低位代理(low surrogate),现在值的范围是0xDC00…0xDFFF.由于低位代理比高位代理的值要大,所以为了避免混淆使用,Unicode标准现在称低位代理为后尾代理(trail surrogates)。
举个例子
例如U+10437编码(?):
- 0x10437减去0x10000,结果为0x00437,二进制为0000 0000 0100 0011 0111。
- 分割它的上10位值和下10位值(使用二进制):0000000001 and 0000110111。
- 添加0xD800到上值,以形成高位:0xD800 + 0x0001 = 0xD801
- 添加0xDC00到下值,以形成低位:0xDC00 + 0x0037 = 0xDC37。
从U+D800到U+DFFF的码位
Unicode标准规定U+D800…U+DFFF的值不对应于任何字符。
但是在使用UCS-2的时代,U+D800…U+DFFF内的值被占用,用于某些字符的映射。但只要不构成代理对,许多UTF-16编码解码还是能把这些不匹配Unicode标准的字符映射正确的辨识、转换成合规的码元[2].按照Unicode标准,这种码元序列本来应算作编码错误。
UTF-8 编码
UTF-8的编码规则
自定义的lengthFilter
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