Android EditText输入手机号自带分隔符

图片发自简书App

输入手机号时,为了看着更加方便可能会显示成xxx xxxx xxxx,如图

为了这个需求,自己简单的研究了一下,写了个这个小东西自己练练思维

简单来看就是按照3-4-4来分割,通过添加addTextChangedListener()就能获得文本修改的值,可以在afterTextChanged(Editable s)方法中处理就行了,主要就是在对应的位置添加分隔符就OK了

private StringBuffer mStringBuffer = new StringBuffer();
/** 分割符 */
private char separator = ' ';
/** 分割符插入位置规则 */
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. ……

后续再处理这些问题的过程中进行了一些优化,还扩展了一些使用方式,最终是以实现TextWatcher的方式来封装的,不说废话,上代码

public class AutoSeparateTextWatcher implements TextWatcher {
    /***/
    private StringBuffer mStringBuffer = new StringBuffer();
    /** 分割符 */
    private char separator = ' ';
    /** 分割符插入位置规则 */
    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();
    }

    /**
     * 设置分割规则
     * @param RULES 分割规则数组
     *              例如: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), "");
    }
}

这里提供了可以修改分割字符串规则和分割符的方法,部分处理添加分割符合移除分割符的方法都做成了静态方法,方便外面直接调用处理,可能在构造器传入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);

如果发现有什么bug还请及时指出

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容