很多 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 表示不做過濾和限制;
- 返回 “” 表示全部過濾,不能輸入任何字元;
- 返回非空字串表示過濾通過字串的內容,當然字串的內容我們完全可以自定義;
有了以上認識可以更好的完成目標:
- 判斷新輸入的字元是中文還是英文(目前來看還沒有能夠同時輸入包含中英文的情況);
- 對中英文按不同長度分別計算總長度,去掉超出的部分;
- 返回對應的值
實現如下:
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