Android EditText輸入手機號自帶分隔符

圖片發(fā)自簡書App

輸入手機號時玻靡,為了看著更加方便可能會顯示成xxx xxxx xxxx剃浇,如圖

為了這個需求律适,自己簡單的研究了一下,寫了個這個小東西自己練練思維

簡單來看就是按照3-4-4來分割淆衷,通過添加addTextChangedListener()就能獲得文本修改的值,可以在afterTextChanged(Editable s)方法中處理就行了渤弛,主要就是在對應的位置添加分隔符就OK了

private StringBuffer mStringBuffer = new StringBuffer();
/** 分割符 */
private char separator = ' ';
/** 分割符插入位置規(guī)則 */
private int[] RULES = {3, 4, 4};

@Override
public void afterTextChanged(Editable s) {
    if (!TextUtils.equals(s, mStringBuffer)) {
        mStringBuffer = new StringBuffer();
        for (int i = 0, length = s.length(); i < length; i++) {
            char c = s.charAt(i);
            if (c != separator) {
                mStringBuffer.append(c);
            }
            int standardPos = 0;
            int offset = 0;
            for (int pos : RULES) {
                standardPos += pos + offset++;
                if (mStringBuffer.length() == standardPos) {
                    mStringBuffer.append(separator);
                    break;
                }
            }
        }
        editText.setText(mStringBuffer);
    }
}

這些代碼基本已經實現了主要功能祝拯,但是還有不少小問題,例如:

  1. 光標的位置問題
  2. 空格刪除問題
  3. ……

后續(xù)再處理這些問題的過程中進行了一些優(yōu)化,還擴展了一些使用方式佳头,最終是以實現TextWatcher的方式來封裝的鹰贵,不說廢話,上代碼

public class AutoSeparateTextWatcher implements TextWatcher {
    /***/
    private StringBuffer mStringBuffer = new StringBuffer();
    /** 分割符 */
    private char separator = ' ';
    /** 分割符插入位置規(guī)則 */
    private int[] RULES = {3, 4, 4};
    /** 最大輸入長度 */
    private int MAX_INPUT_LENGTH;
    /** EditText */
    private EditText editText;
    /** 最大輸入長度InputFilter */
    private InputFilter.LengthFilter mLengthFilter;

    /**
     * @param editText 目標EditText
     */
    public AutoSeparateTextWatcher(@NonNull EditText editText) {
        this.editText = editText;
        //更新輸入最大長度
        setupMaxInputLength();
    }

    /**
     * 設置分割規(guī)則
     * @param RULES 分割規(guī)則數組
     *              例如:138 383 81438的分割數組是{3,3,5}
     */
    public void setRULES(@NonNull int[] RULES) {
        this.RULES = RULES;
        setupMaxInputLength();
        String originalText = removeSpecialSeparator(editText, this.separator);
        if (!TextUtils.isEmpty(originalText)) {
            editText.setText(originalText);
            editText.setSelection(editText.getText().length());
        }
    }

    /**
     * 設置分割符
     * @param separator 分隔符康嘉,默認為空格
     */
    public void setSeparator(char separator) {
        String originalText = removeSpecialSeparator(editText, this.separator);
        this.separator = separator;
        if (!TextUtils.isEmpty(originalText)) {
            editText.setText(originalText);
            editText.setSelection(editText.getText().length());
        }
    }

    public char getSeparator() {
        return separator;
    }

    /** 更新最大輸入長度 */
    private void setupMaxInputLength() {
        MAX_INPUT_LENGTH = RULES.length - 1;
        for (int value : RULES) {
            MAX_INPUT_LENGTH += value;
        }
        //更新LengthFilter
        InputFilter[] filters = editText.getFilters();
        if (filters.length > 0 && mLengthFilter != null) {
            //判斷editText的InputFilter中是否已經包含mLengthFilter
            for (int i = 0; i < filters.length; i++) {
                InputFilter filter = filters[i];
                if (mLengthFilter == filter) {
                    mLengthFilter = new InputFilter.LengthFilter(MAX_INPUT_LENGTH);
                    filters[i] = mLengthFilter;
                    return;
                }
            }
        }
        addLengthFilter(filters);
    }

    /**
     * @param filters
     */
    private void addLengthFilter(InputFilter[] filters) {
        if (filters == null) {
            filters = new InputFilter[0];
        }
        InputFilter[] newFilters = new InputFilter[filters.length + 1];
        System.arraycopy(filters, 0, newFilters, 0, filters.length);
        mLengthFilter = new InputFilter.LengthFilter(MAX_INPUT_LENGTH);
        newFilters[newFilters.length - 1] = mLengthFilter;
        editText.setFilters(newFilters);
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }

    @Override
    public void afterTextChanged(Editable s) {
        if (!TextUtils.equals(s, mStringBuffer)) {
            //刪除mStringBuffer中的文本
            mStringBuffer.delete(0, mStringBuffer.length());
            //添加分隔符
            mStringBuffer.append(handleText(s, RULES, separator));
            //刪除多余字符
            if (mStringBuffer.length() > MAX_INPUT_LENGTH) {
                mStringBuffer.delete(MAX_INPUT_LENGTH, mStringBuffer.length());
            }
            final int currSelectStart = editText.getSelectionStart();
            //計算分隔符導致的光標offset
            int separatorOffset = calculateSeparatorOffset(s, mStringBuffer, currSelectStart);
            editText.setText(mStringBuffer);
            //計算并設置當前的selectStart位置
            int selectStart = currSelectStart + separatorOffset;
            if (selectStart < 0) {
                selectStart = 0;
            } else if (selectStart > mStringBuffer.length()) {
                selectStart = mStringBuffer.length();
            }
            editText.setSelection(selectStart);
        }
    }

    /**
     * 計算符號的offset
     *
     * @param before
     * @param after
     * @param selectionStart
     *
     * @return
     */
    private int calculateSeparatorOffset(@NonNull CharSequence before, @NonNull CharSequence after, int selectionStart) {
        int offset = 0;
        final int beforeLength = before.length();
        final int afterLength = after.length();
        final int length = afterLength > beforeLength ? beforeLength : afterLength;
        for (int i = 0; i < length; i++) {
            if (i >= selectionStart) {
                break;
            }
            char bc = before.charAt(i);
            char ac = after.charAt(i);
            if (bc == separator && ac != separator) {
                offset--;
            } else if (bc != separator && ac == separator) {
                offset++;
            }
        }
        return offset;
    }

    /**
     * @param s
     * @param rules
     * @param separator
     *
     * @return
     */
    public static String handleText(Editable s, int[] rules, char separator) {
        StringBuffer stringBuffer = new StringBuffer();
        for (int i = 0, length = s.length(); i < length; i++) {
            char c = s.charAt(i);
            if (c != separator) {
                stringBuffer.append(c);
            }
            if (length != stringBuffer.length() && isSeparationPosition(rules, stringBuffer.length())) {
                stringBuffer.append(separator);
            }
        }
        return stringBuffer.toString();
    }

    /**
     * @param RULES
     * @param length
     *
     * @return
     */
    private static boolean isSeparationPosition(int[] RULES, int length) {
        if (RULES == null) {
            return false;
        }
        int standardPos = 0;
        int offset = 0;
        for (int pos : RULES) {
            standardPos += pos;
            if (length == standardPos + offset++) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param editText
     * @param specialSeparator
     *
     * @return
     */
    public static String removeSpecialSeparator(EditText editText, char specialSeparator) {
        if (editText == null) {
            return null;
        }
        Editable text = editText.getText();
        return text == null ? null : text.toString().replace(String.valueOf(specialSeparator), "");
    }
}

這里提供了可以修改分割字符串規(guī)則和分割符的方法碉输,部分處理添加分割符合移除分割符的方法都做成了靜態(tài)方法,方便外面直接調用處理亭珍,可能在構造器傳入EditText對象敷钾,可能會造成內存問題,這個暫時還沒有處理肄梨。

使用TextWatcher來封裝闰非,主要是因為最終還是要通過TextWatcher來實現,那就沒有必要繼承EditText來進行封裝峭范,降低了使用成本财松,添加一個TextWatcher就能實現的事情,為什么要搞得那么復雜呢!

AutoSeparateTextWatcher使用方式也比較簡單

EditText editText = findViewById(R.id.edit_text);
AutoSeparateTextWatcher textWatcher = new AutoSeparateTextWatcher(editText);
textWatcher.setRULES(new int[]{4,4,4,4});
textWatcher.setSeparator('-');
editText.addTextChangedListener(textWatcher);

如果發(fā)現有什么bug還請及時指出

源碼地址:https://github.com/MrTrying/AutoSeparateEditText

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末纱控,一起剝皮案震驚了整個濱河市辆毡,隨后出現的幾起案子,更是在濱河造成了極大的恐慌甜害,老刑警劉巖舶掖,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異尔店,居然都是意外死亡眨攘,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門嚣州,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鲫售,“玉大人,你說我怎么就攤上這事该肴∏橹瘢” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵匀哄,是天一觀的道長秦效。 經常有香客問我,道長涎嚼,這世上最難降的妖魔是什么阱州? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮法梯,結果婚禮上苔货,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好蒲赂,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布阱冶。 她就那樣靜靜地躺著,像睡著了一般滥嘴。 火紅的嫁衣襯著肌膚如雪木蹬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天若皱,我揣著相機與錄音镊叁,去河邊找鬼。 笑死走触,一個胖子當著我的面吹牛晦譬,可吹牛的內容都是我干的。 我是一名探鬼主播互广,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼敛腌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了惫皱?” 一聲冷哼從身側響起像樊,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎旅敷,沒想到半個月后生棍,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡媳谁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年涂滴,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晴音。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡柔纵,死狀恐怖,靈堂內的尸體忽然破棺而出段多,到底是詐尸還是另有隱情首量,我是刑警寧澤壮吩,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布进苍,位于F島的核電站,受9級特大地震影響鸭叙,放射性物質發(fā)生泄漏觉啊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一沈贝、第九天 我趴在偏房一處隱蔽的房頂上張望杠人。 院中可真熱鬧,春花似錦、人聲如沸嗡善。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽罩引。三九已至各吨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間袁铐,已是汗流浹背揭蜒。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留剔桨,地道東北人屉更。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像洒缀,于是被迫代替她去往敵國和親瑰谜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內容