EditText過濾特殊符號

EditText過濾特殊符號

序言

在開發(fā)過程中總是會遇到產(chǎn)品要求某個輸入框只能輸入特定的字符。因為這些特殊字符作為url連接參數(shù),sql語句參數(shù)等地方會有問題。

需求如下

  • 只能輸入某些特定的字符
  • 在用戶輸入不正確的字符的時候不顯示這些錯誤字符
  • 不能有奇怪的bug

思路

那么這邊會快速的想到三種解決方案

  1. 過濾器,使用過濾器InputFilter可以直接過濾掉不想要的字符
  2. 監(jiān)聽鍵盤點擊事件产徊,只讓用戶點擊需要的按鍵才有反應(yīng)
  3. 監(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)吧。希望我的文章會讓你們少躺坑滴肿。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末岳悟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子泼差,更是在濱河造成了極大的恐慌贵少,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件堆缘,死亡現(xiàn)場離奇詭異滔灶,居然都是意外死亡,警方通過查閱死者的電腦和手機套啤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進店門宽气,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人潜沦,你說我怎么就攤上這事萄涯。” “怎么了唆鸡?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵涝影,是天一觀的道長。 經(jīng)常有香客問我争占,道長燃逻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任臂痕,我火速辦了婚禮伯襟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘握童。我一直安慰自己姆怪,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布澡绩。 她就那樣靜靜地躺著稽揭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肥卡。 梳的紋絲不亂的頭發(fā)上溪掀,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天,我揣著相機與錄音步鉴,去河邊找鬼搞监。 笑死,一個胖子當(dāng)著我的面吹牛吗购,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沮稚,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼册舞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起障般,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤调鲸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后挽荡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體藐石,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年定拟,在試婚紗的時候發(fā)現(xiàn)自己被綠了于微。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡青自,死狀恐怖株依,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情延窜,我是刑警寧澤恋腕,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站逆瑞,受9級特大地震影響荠藤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜获高,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一哈肖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧念秧,春花似錦淤井、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至严就,卻和暖如春总寻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背梢为。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工渐行, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留轰坊,地道東北人。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓祟印,卻偏偏與公主長得像肴沫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蕴忆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,876評論 2 361