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),具体方法是:

  1. 码位减去0x10000,得到的值的范围为20比特长的0…0xFFFFF.
  2. 高位的10比特的值(值的范围为0…0x3FF)被加上0xD800得到第一个码元或称作高位代理(high surrogate),值的范围是0xD800…0xDBFF.由于高位代理比低位代理的值要小,所以为了避免混淆使用,Unicode标准现在称高位代理为前导代理(lead surrogates)。
  3. 低位的10比特的值(值的范围也是0…0x3FF)被加上0xDC00得到第二个码元或称作低位代理(low surrogate),现在值的范围是0xDC00…0xDFFF.由于低位代理比高位代理的值要大,所以为了避免混淆使用,Unicode标准现在称低位代理为后尾代理(trail surrogates)。

举个例子
例如U+10437编码(?):

  1. 0x10437减去0x10000,结果为0x00437,二进制为0000 0000 0100 0011 0111
  2. 分割它的上10位值和下10位值(使用二进制):0000000001 and 0000110111
  3. 添加0xD800到上值,以形成高位:0xD800 + 0x0001 = 0xD801
  4. 添加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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值