EditText過濾特殊符號
序言
在開發(fā)過程中總是會遇到產(chǎn)品要求某個輸入框只能輸入特定的字符。因為這些特殊字符作為url連接參數(shù),sql語句參數(shù)等地方會有問題。
需求如下
- 只能輸入某些特定的字符
- 在用戶輸入不正確的字符的時候不顯示這些錯誤字符
- 不能有奇怪的bug
思路
那么這邊會快速的想到三種解決方案
- 過濾器,使用過濾器InputFilter可以直接過濾掉不想要的字符
- 監(jiān)聽鍵盤點擊事件产徊,只讓用戶點擊需要的按鍵才有反應(yīng)
- 監(jiān)聽EditText輸入框的變化
實踐
我們這邊的案例需求為可以輸入數(shù)字、英文蜀细、漢字舟铜,不能輸入任何中英文標(biāo)點符號,以及emoji表情奠衔。
1. 使用過濾器
那么我們在網(wǎng)絡(luò)上找到了兩種實現(xiàn)方案谆刨,一種是直接繼承InputFilter另一種是繼承InputFilter的子類,使用方法如下(kotlin代碼):
editText.filters = arrayOf(EtInputFilters())
下面是InputFilter(Java)實現(xiàn)類:
import android.text.InputFilter;
import android.text.Spanned;
import android.text.TextUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class EtInputFilters implements InputFilter {
/**
* 限制輸入的最大值
*/
public static final int TYPE_MAXNUMBER = 1;
/**
* 限制輸入最大長度
*/
public static final int TYPE_MAXLENGTH = 2;
/**
* 限制輸入小數(shù)位數(shù)
*/
public static final int TYPE_DECIMAL = 3;
/**
* 限制輸入最小整數(shù)
*/
public static final int TYPE_MINNUMBER = 4;
/**
* 限制輸入手機號
*/
public static final int TYPE_PHONENUMBER = 5;
/**
* 限制輸入數(shù)字归斤,漢字痊夭,英文
*/
public static final int TYPE_NORMAL = 6;
private Pattern mPattern;
private double mMaxNum; //最大數(shù)值
private int mMaxLength; //最大長度
private int mType = 0;
public EtInputFilters(int type) {
this.mType = type;
}
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
switch (mType) {
case TYPE_MAXNUMBER:
return filterMaxNum(source, start, end, dest, dstart, dend);
case TYPE_MAXLENGTH:
return filterMaxLength(source, start, end, dest, dstart, dend);
case TYPE_DECIMAL:
return filterDecimal(source, dest, dstart, dend);
case TYPE_MINNUMBER:
return filterMinnum(source, dest, dstart);
case TYPE_PHONENUMBER:
return filterPhoneNum(source, dest, dstart);
case TYPE_NORMAL:
return stringFilter(source);
}
return source;
}
/**
* 最大值的限制
*
* @param min 允許的最小值
* @param maxNum 允許的最大值
* @param numOfDecimals 允許的小數(shù)位
*/
public EtInputFilters setMaxNum(int min, double maxNum, int numOfDecimals) {
this.mMaxNum = maxNum;
this.mPattern = Pattern.compile("^" + (min < 0 ? "-?" : "")
+ "[0-9]*\\.?[0-9]" + (numOfDecimals > 0 ? ("{0," + numOfDecimals + "}$") : "*"));
return this;
}
/**
* 過濾最大值
*/
private CharSequence filterMaxNum(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
if (source.equals(".")) {
if (dstart == 0 || !(dest.charAt(dstart - 1) >= '0' && dest.charAt(dstart - 1) <= '9') || dest.charAt(0) == '0') {
return "";
}
}
if (source.equals("0") && (dest.toString()).contains(".") && dstart == 0) {
return "";
}
StringBuilder builder = new StringBuilder(dest);
builder.delete(dstart, dend);
builder.insert(dstart, source);
if (!mPattern.matcher(builder.toString()).matches()) {
return "";
}
if (!TextUtils.isEmpty(builder)) {
double num = Double.parseDouble(builder.toString());
if (num > mMaxNum) {
return "";
}
}
return source;
}
/**
* 設(shè)置最大長度
*
* @param maxLength 最大長度
*/
public EtInputFilters setMaxNum(int maxLength) {
this.mMaxLength = maxLength;
return this;
}
/**
* 過濾最大長度
*/
private CharSequence filterMaxLength(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
int keep = mMaxLength - (dest.length() - (dend - dstart));
if (keep <= 0) {
return "";
} else if (keep >= end - start) {
return null; // keep original
} else {
keep += start;
if (Character.isHighSurrogate(source.charAt(keep - 1))) {
--keep;
if (keep == start) {
return "";
}
}
return source.subSequence(start, keep);
}
}
/**
* 設(shè)置可輸入小數(shù)位數(shù)
*
* @param decimal 允許的小數(shù)位
*/
public EtInputFilters setDecimal(int decimal) {
this.mPattern = Pattern.compile("^[0-9]*\\.?[0-9]"
+ (decimal > 0 ? ("{0," + decimal + "}$") : "*"));
return this;
}
/**
* 過濾小數(shù)
*/
private CharSequence filterDecimal(CharSequence source, Spanned dest, int dstart, int dend) {
if (source.equals(".")) {
if (dstart == 0 || !(dest.charAt(dstart - 1) >= '0' && dest.charAt(dstart - 1) <= '9') || dest.charAt(0) == '0') {
return "";
}
}
if (source.equals("0") && (dest.toString()).contains(".") && dstart == 0) { //防止在369.369的最前面輸入0變成0369.369這種不合法的形式
return "";
}
StringBuilder builder = new StringBuilder(dest);
builder.delete(dstart, dend);
builder.insert(dstart, source);
if (!mPattern.matcher(builder.toString()).matches()) {
return "";
}
return source;
}
/**
* 設(shè)置只能輸入整數(shù),限制最小整數(shù)
*
* @param minnum 最小整數(shù)
*/
public EtInputFilters setMinnumber(int minnum) {
this.mPattern = Pattern.compile("^" + (minnum < 0 ? "-?" : "") + "[0-9]*$");
return this;
}
/**
* 過濾整數(shù)
*/
private CharSequence filterMinnum(CharSequence source, Spanned dest, int dstart) {
StringBuilder builder = new StringBuilder(dest);
builder.insert(dstart, source);
if (!mPattern.matcher(builder.toString()).matches()) {
return "";
}
return source;
}
/**
* 設(shè)置只能輸入手機號
*
* @return
*/
public EtInputFilters setPhone() {
this.mPattern = Pattern.compile("^((13[0-9])|(15[^4])|(18[0-9])|(17[0-8])|(1[57]))\\d{8}$");
return this;
}
/**
* 過濾手機號
*/
private CharSequence filterPhoneNum(CharSequence source, Spanned dest, int dstart) {
StringBuilder builder = new StringBuilder(dest);
builder.insert(dstart, source);
int length = builder.length();
if (length == 1) {
if (builder.charAt(0) == '1') {
return source;
} else {
return "";
}
}
if (length > 0 && length <= 11) {
if (mPattern.matcher(builder.toString()).matches()) {
return source;
} else {
return "";
}
}
return "";
}
public CharSequence stringFilter(CharSequence source) {
// 只允許字母脏里、數(shù)字和漢字
String regEx = "[^a-zA-Z0-9\u4E00-\u9FA5]";//正則表達式
Pattern p = Pattern.compile(regEx);
Matcher m = p.matcher(source);
return m.replaceAll("").trim();
}
}
那么這個方案有致命的缺陷她我,在華為mate10、華為mate20迫横、還有部分vivo手機上番舆。他們自帶的原皮百度版輸入法,百度輸入法vivo版矾踱,出現(xiàn)了按刪除按鈕edittext也會顯示聯(lián)想詞匯的問題恨狈,以及英文輸入法界面“.”和“?”這兩個按鈕點擊會直接刪除已有的文字。 經(jīng)過一番搜索之后發(fā)現(xiàn)是百度輸入的bug呛讲,只要換個輸入法皮膚就好了禾怠,但畢竟不能逃避問題,那我們用下面這個filter
import android.text.LoginFilter;
public class MyInputFilter extends LoginFilter.UsernameFilterGMail {
public MyInputFilter() {
super();
}
@Override
public boolean isAllowed(char c) {
// return true;
if ('0' <= c && c <= '9')
return true;
if ('a' <= c && c <= 'z')
return true;
if ('A' <= c && c <= 'Z')
return true;
if ('.' == c || '?' == c)
return false;
else
return isChineseByBlock(c);
}
//使用UnicodeBlock方法判斷
public boolean isChineseByBlock(char c) {
Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
return ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B
|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_C
|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_D
|| ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
|| ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT;
}
}
那么問題來了圣蝎,這個還是有英文輸入法界面“.”和“?”這兩個按鈕點擊會直接刪除已有的文字刃宵。 的問題這是不能忍的。
那么到這里九十九步差一步就實現(xiàn)需求了徘公,我們想到了監(jiān)聽點擊事件來屏蔽掉.和?這兩個按鈕牲证。
監(jiān)聽點擊事件
那么好onkeydown還有其他的兩個key事件全部監(jiān)聽失敗沒有抓取到任何的鍵盤輸入信息,至此以上流程走不通关面。單獨的監(jiān)聽鍵盤點擊也是不可取的坦袍,因為你要屏蔽的按鈕也可能會聯(lián)想出表情包。
直接通過監(jiān)聽EditText的文字變化
這里就有個問題了就是光標(biāo)的處理等太,一開始用上面的辦法就是為了避免光標(biāo)的計算問題才迂回處理的捂齐。
思路
- changeListener有start這個值那么你就可以根據(jù)這個值去設(shè)置光標(biāo)而不會IndexOutOfBoundException
- 在變化的一瞬間就干掉不符合規(guī)則的輸入
- 如果本身輸入框里面有文字,那么第一次顯示的時候就要把不符合的規(guī)則的文字全刪掉缩抡,然后把光標(biāo)放到字符串最后一格
代碼如下(kotlin)
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
var string = s.toString()
val chars = string.toCharArray()
for (char in chars) {
if (!isAllowed(char)) {
string = string.replace(char.toString(), "")
}
}
if (string != s.toString()) {
text.setText(string)
text.setSelection(start)
}
}
private fun isAllowed(c: Char): Boolean {
// return true;
if (c in '0'..'9')
return true
if (c in 'a'..'z')
return true
if (c in 'A'..'Z')
return true
return if ('.' == c || '?' == c)
false
else
isChineseByBlock(c)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_edit)
text.addTextChangedListener(this)
var content = intent.getStringExtra(Constant.CONTENT)
val chars = content.toCharArray()
for (char in chars) {
if (!isAllowed(char)) {
content = content.replace(char.toString(), "")
}
}
text.setText(content)
text.setSelection(text.length())
}
以上代碼直接設(shè)置到對應(yīng)的EditText上就可以了奠宜。在每次設(shè)置EditText文字的時候需要自己手動的去刪除不符合標(biāo)準(zhǔn)的字符比如上面的onCreate方法里面。試運行不兼容的機型沒有任何問題,試運行本來就沒啥問題的小米和nexus也沒有問題压真。
結(jié)語
谷歌本身給的過濾器還是機型適配有問題的娩嚼,大家還是不要偷懶自己處理光標(biāo)和第一次顯示的過濾來實現(xiàn)吧。希望我的文章會讓你們少躺坑滴肿。