讓 Android 輸入框只能輸入固定長度的中英文

AlphaGao發表於2018-06-10

很多 App 都會要求輸入的字元種類、長度有所限制,在此之前我其實已經遇到了這樣的需求,只能輸入中文和英文,並且不同的語言所限制的長度不同。那個時候會覺得這樣的限制比較麻煩,因為總認為涉及到中文的判斷就比較麻煩,所以推脫說實現比較麻煩沒有太多的時間,就給暫時壓了下來。直到第二次遇到這樣的需求我直到沒辦法再退了,結果 google 了下發現判斷中文也沒那麼麻煩,使用正則判斷中文字元範圍即可。

另外類似需求的實現邏輯也可能不同,比如有的是中文、英文分別限制,中文可以輸入 7 字元,英文可以輸入 14 字元,但是隻要有 1 箇中文字就按照中文的長度限制,這樣你輸入了 1 箇中文字之後就只能輸入 6 個英文字元了,感覺並不合理。因此我的實現邏輯是:1 箇中文字元算兩個英文字元,然後計算總長度(在 Android 裡不論中英文都預設計算為 1 個字元)。這樣輸入 1 箇中文字後還能繼續輸入 12 個英文字。

一般簡單的限制輸入長度只需要設定 maxLength 即可,但是這種複合的需求,就只能通過 InputFilter 來實現了。InputFilter 是一個介面,並且 Android 官方提供很多種預設實現,有的很有意思,這裡不一一列舉,感興趣的可以自己去看看。

InputFilter 只有一個方法:

    public CharSequence filter(CharSequence source, int start, int end,
                               Spanned dest, int dstart, int dend);
複製程式碼

source 代表新輸入待驗證過濾的字串,start 和 end 是新輸入字串的起始; dest 是被驗證通過已經輸入的字元,dstart 和 dend 是已經輸入文字的起始; 如果是輸入狀態,那麼 dstart == dend,但是 start < end; 如果是刪除狀態,那麼 start == end,但是 dstart < dend ,並且兩者之差就是刪除字元的長度。

這個方法所返回值的不同所表示的意義也不同:

  • 返回 null 表示不做過濾和限制;
  • 返回 “” 表示全部過濾,不能輸入任何字元;
  • 返回非空字串表示過濾通過字串的內容,當然字串的內容我們完全可以自定義;

有了以上認識可以更好的完成目標:

  1. 判斷新輸入的字元是中文還是英文(目前來看還沒有能夠同時輸入包含中英文的情況);
  2. 對中英文按不同長度分別計算總長度,去掉超出的部分;
  3. 返回對應的值

實現如下:

class SizeFilterWithTextAndLetter(private var letterMaxLength: Int) : InputFilter {
    private val chinesePattern = Pattern.compile("[\u4e00-\u9fa5]+")
    private val letterPattern = Pattern.compile("[a-zA-Z]+")

    override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int): CharSequence? {
        val isAdd = dstart == dend
        if (!isAdd) {//刪除文字不限制
            return null
        }
        val originLength = calculateLength(dest)

        val isChinese = chinesePattern.matcher(source.toString()).find()
        val isLetter = letterPattern.matcher(source.toString()).find()
        if (isChinese) {
            return when {
                originLength >= letterMaxLength -> ""
                originLength + source.length * 2 > letterMaxLength -> {
                    var length = (letterMaxLength - originLength + 1) / 2
                    if (length < 0) length = 0
                    source.subSequence(0, length)
                }
                else -> null
            }
        } else if (isLetter) {
            return when {
                originLength >= letterMaxLength -> ""
                originLength + source.length > letterMaxLength -> {
                    letterMaxLength = source.length
                    val length = letterMaxLength
                    source.subSequence(0, length)
                }
                else -> null
            }
        }

        return ""
    }

    /**
     * 計算原有文字佔據的長度
     *
     * @param dest 原文字
     * @return 總長度
     */
    private fun calculateLength(dest: Spanned): Int {
        var chineseLength = 0
        var letterLength = 0
        for (i in 0 until dest.length) {
            if (chinesePattern.matcher(dest.subSequence(i, i + 1)).find()) {
                chineseLength += 2
            }
            if (letterPattern.matcher(dest.subSequence(i, i + 1)).find()) {
                letterLength += 1
            }
        }
        return chineseLength + letterLength
    }
}
複製程式碼

使用方法:

//過濾器是以陣列的形式設定,此處限制輸入 14 英文字元,也就是 7 中文字元
edit_value?.filters = arrayOf(SizeFilterWithTextAndLetter(14))
複製程式碼

本文最早發表在 alphagao.com

相關文章