由于项目的需求,需要在卡号输入时,每四位用空间分隔,于是就写了个控件。

该控件支持中间删除,中间增加,粘贴,末尾输入等,光标的位置显示正确。

主要的思想就是:对于添加TextWatcher监听Text的改变,text改变后,拿到该text,将text中的所有空格去掉。然后重新排列。记下来是对光标的位置处理。

1.在末尾删除或者增加的时候,光标一直处于末端。
2.在中间删除的时候,检测光标是否在空格的后面,如果在后面,就让光标前进(往左)一位,如果不是,则光标的位置不变(根据start,count,before这几个参数来算)。
3.在中间插入的时候,检测光标是否在空格的前面,如果在前面,则让光标前进(往右)一位,如果不是,则光标的位置不变(根据start,count,before这几个参数来算)。

读者要先懂得start,count,before的意义。

直接上代码

/**
 * Created by hzlinxuanxuan on 2015/12/10.
 */
public class CardInputEditText extends CleanUpEditText {

    public CardInputEditText(Context ctx) {
        this(ctx, null);
    }

    public CardInputEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        addTextChangedListener(watcher);
    }

    public CardInputEditText(Context context, AttributeSet attrs, int defStyleAttr){
        super(context, attrs,defStyleAttr);
        addTextChangedListener(watcher);
    }

    private TextWatcher watcher = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            LogUtil.d("s:" + s + ",start:" + start + ",before:" + before + ",count:" + count);
            if (s == null) {
                return;
            }
            //判断是否是在中间输入,需要重新计算
            boolean isMiddle = (start + count) < (s.length());
            //在末尾输入时,是否需要加入空格
            boolean isNeedSpace = false;
            if (!isMiddle && s.length() > 0 && s.length() % 5 == 0) {
                isNeedSpace = true;
            }
            if (isMiddle || isNeedSpace) {
                String newStr = s.toString();
                newStr = newStr.replace(" ", "");
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < newStr.length(); i += 4) {
                    if (i > 0) {
                        sb.append(" ");
                    }
                    if (i + 4 <= newStr.length()) {
                        sb.append(newStr.substring(i, i + 4));
                    } else {
                        sb.append(newStr.substring(i, newStr.length()));
                    }
                }
                removeTextChangedListener(watcher);
                setText(sb);
                //如果是在末尾的话,或者加入的字符个数大于零的话(输入或者粘贴)
                if(!isMiddle || count > 1){
                    setSelection(sb.length());
                } else if (isMiddle) {
                    //如果是删除
                    if (count == 0) {
                        //如果删除时,光标停留在空格的前面,光标则要往前移一位
                        if ((start - before + 1) % 5 == 0) {
                            setSelection((start - before) > 0 ? start - before : 0);
                        } else {
                            setSelection((start - before + 1) > sb.length() ? sb.length() : (start - before + 1));
                        }
                    }
                    //如果是增加
                    else {
                        if ((start - before + count) % 5 == 0) {
                            setSelection((start + count - before + 1) < sb.length() ? (start + count - before + 1) : sb.length());
                        } else {
                            setSelection(start + count - before);
                        }
                    }
                }
                addTextChangedListener(watcher);
            }
        }

        @Override
        public void afterTextChanged(Editable s) {
        }
    };

    public String getTextWithoutSpace(){
        return super.getText().toString().replace(" ","");
    }
}




这是效果

Android 输入框默认字 android输入框控件_Android 输入框默认字

看了该效果,如果现在让你再写个电话号码输入框,应该会写了吧?

/**
 * Created by hzlinxuanxuan on 2015/12/22.
 */
public class PhoneInputEditText extends EditText implements ITextWithoutSpaceInter{
    public PhoneInputEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        addTextChangedListener(watcher);
        setFilters(new InputFilter[]{new InputFilter.LengthFilter(13)});
    }

    public PhoneInputEditText(Context context) {
        this(context, null);
    }

    public PhoneInputEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        addTextChangedListener(watcher);
        setFilters(new InputFilter[]{new InputFilter.LengthFilter(13)});
    }

    private TextWatcher watcher = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            if (s == null) {
                return;
            }
            //判断是否是在中间输入,需要重新计算
            boolean isMiddle = (start + count) < (s.length());
            //在末尾输入时,是否需要加入空格
            boolean isNeedSpace = false;
            if (!isMiddle && isNeedSpace(s.length())) {
                isNeedSpace = true;
            }
            if (isMiddle || isNeedSpace) {
                String newStr = s.toString();
                newStr = newStr.replace(" ", "");
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < newStr.length(); ) {
                    if (i == 0) {
                        sb.append(newStr.length() > 3 ? newStr.substring(0, 3) : newStr);
                        i += 3;
                        continue;
                    } else if (i > 0) {
                        sb.append(" ");
                        if (i + 4 <= newStr.length()) {
                            sb.append(newStr.substring(i, i + 4));
                        } else {
                            sb.append(newStr.substring(i, newStr.length()));
                        }
                        i += 4;
                    }
                }
                removeTextChangedListener(watcher);
                setText(sb);
                //如果是在末尾的话,或者加入的字符个数大于零的话(输入或者粘贴)
                if (!isMiddle || count > 1) {
                    setSelection(sb.length());
                } else if (isMiddle) {
                    //如果是删除
                    if (count == 0) {
                        //如果删除时,光标停留在空格的前面,光标则要往前移一位
                        if (isNeedSpace(start - before + 1)) {
                            setSelection((start - before) > 0 ? start - before : 0);
                        } else {
                            setSelection((start - before + 1) > sb.length() ? sb.length() : (start - before + 1));
                        }
                    }
                    //如果是增加
                    else {
                        if (isNeedSpace(start - before + count)) {
                            setSelection((start + count - before + 1) < sb.length() ? (start + count - before + 1) : sb.length());
                        } else {
                            setSelection(start + count - before);
                        }
                    }
                }
                addTextChangedListener(watcher);
            }
        }

        @Override
        public void afterTextChanged(Editable s) {
        }
    };

    @Override
    public String getTextWithoutSpace() {
        return super.getText().toString().replace(" ", "");
    }

    private boolean isNeedSpace(int length) {
        if (length < 4) {
            return false;
        } else if (length == 4) {
            return true;
        } else return (length + 1) % 5 == 0;
    }
}


这是效果

Android 输入框默认字 android输入框控件_Text_02


现在新需求来了,又来了一个身份证号码输入框,格式是:330304 1995 0406 1456

如果按照上面的方法再写个控件的话是可以解决问题,但是比较麻烦,所以将上述三种的输入框合并成一种,然后自定义属性,可以在xml中指定是哪一种类型,或者使用代码指定。直接上代码:

/**
 * Created by hzlinxuanxuan on 2015/12/22.
 * 该控件是支持输入时自带控件的,目前支持xml属性指定,也支持代码指定该输入卡所属的类型
 * 现在支持:电话、卡号、身份证号,或者无类型(正常输入)
 */
public class ContentWithSpaceEditText extends EditText{

    private int contentType;
    public static final int TYPE_PHONE = 0;
    public static final int TYPE_CARD = 1;
    public static final int TYPE_IDCARD = 2;

    public ContentWithSpaceEditText(Context context) {
        this(context, null);
    }

    public ContentWithSpaceEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        parseAttributeSet(context, attrs);
    }

    public ContentWithSpaceEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        parseAttributeSet(context, attrs);
    }

    private void parseAttributeSet(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ContentWithSpaceEditText, 0, 0);
        contentType = ta.getInt(R.styleable.ContentWithSpaceEditText_epaysdk_type, 0);
        ta.recycle();
        initType();
        setSingleLine();
        addTextChangedListener(watcher);
    }

    private void initType(){
        if (contentType == TYPE_PHONE) {
            setInputType(InputType.TYPE_CLASS_NUMBER);
            setFilters(new InputFilter[]{new InputFilter.LengthFilter(13)});
        } else if (contentType == TYPE_CARD) {
            setInputType(InputType.TYPE_CLASS_NUMBER);
            setFilters(new InputFilter[]{new InputFilter.LengthFilter(31)});
        } else if (contentType == TYPE_IDCARD) {
            setFilters(new InputFilter[]{new InputFilter.LengthFilter(21)});
        }
    }

    public void setContentType(int contentType) {
        this.contentType = contentType;
        initType();
    }

    private TextWatcher watcher = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            if (s == null) {
                return;
            }
            //判断是否是在中间输入,需要重新计算
            boolean isMiddle = (start + count) < (s.length());
            //在末尾输入时,是否需要加入空格
            boolean isNeedSpace = false;
            if (!isMiddle && isSpace(s.length())) {
                isNeedSpace = true;
            }
            if (isMiddle || isNeedSpace) {
                String newStr = s.toString();
                newStr = newStr.replace(" ", "");
                StringBuilder sb = new StringBuilder();
                int spaceCount = 0;
                for (int i = 0; i < newStr.length(); i++) {
                    sb.append(newStr.substring(i, i+1));
                    //如果当前输入的字符下一位为空格(i+1+1+spaceCount),因为i是从0开始计算的,所以一开始的时候需要先加1
                    if(isSpace(i + 2 + spaceCount)){
                        sb.append(" ");
                        spaceCount += 1;
                    }
                }
                removeTextChangedListener(watcher);
                setText(sb);
                //如果是在末尾的话,或者加入的字符个数大于零的话(输入或者粘贴)
                if (!isMiddle || count > 1) {
                    setSelection(sb.length());
                } else if (isMiddle) {
                    //如果是删除
                    if (count == 0) {
                        //如果删除时,光标停留在空格的前面,光标则要往前移一位
                        if (isSpace(start - before + 1)) {
                            setSelection((start - before) > 0 ? start - before : 0);
                        } else {
                            setSelection((start - before + 1) > sb.length() ? sb.length() : (start - before + 1));
                        }
                    }
                    //如果是增加
                    else {
                        if (isSpace(start - before + count)) {
                            setSelection((start + count - before + 1) < sb.length() ? (start + count - before + 1) : sb.length());
                        } else {
                            setSelection(start + count - before);
                        }
                    }
                }
                addTextChangedListener(watcher);
            }
        }

        @Override
        public void afterTextChanged(Editable s) {
        }
    };

    public String getTextWithoutSpace() {
        return super.getText().toString().replace(" ", "");
    }

    public boolean checkTextRight(){
        String text = getTextWithoutSpace();
        //这里做个简单的内容判断
        if (contentType == TYPE_PHONE) {
            if (TextUtils.isEmpty(text)) {
                ToastUtil.show(getContext(), "手机号不能为空,请输入正确的手机号");
            } else if (text.length() < 11) {
                ToastUtil.show(getContext(), "手机号不足11位,请输入正确的手机号");
            } else {
                return true;
            }
        } else if (contentType == TYPE_CARD) {
            if (TextUtils.isEmpty(text)) {
                ToastUtil.show(getContext(), "银行卡号不能为空,请输入正确的银行卡号");
            } else if (text.length() < 14) {
                ToastUtil.show(getContext(), "银行卡号位数不正确,请输入正确的银行卡号");
            } else {
                return true;
            }
        } else if (contentType == TYPE_IDCARD) {
            if (TextUtils.isEmpty(text)) {
                ToastUtil.show(getContext(), "身份证号不能为空,请输入正确的身份证号");
            } else if (text.length() < 18) {
                ToastUtil.show(getContext(), "身份证号不正确,请输入正确的身份证号");
            } else {
                return true;
            }
        }
        return false;
    }

    private boolean isSpace(int length) {
        if (contentType == TYPE_PHONE) {
            return isSpacePhone(length);
        } else if (contentType == TYPE_CARD) {
            return isSpaceCard(length);
        } else if (contentType == TYPE_IDCARD) {
            return isSpaceIDCard(length);
        }
        return false;
    }

    private boolean isSpacePhone(int length) {
        if (length < 4) {
            return false;
        } else if (length == 4) {
            return true;
        } else return (length + 1) % 5 == 0;
    }

    private boolean isSpaceCard(int length) {
        return length % 5 == 0;
    }

    private boolean isSpaceIDCard(int length) {
        if (length <= 6) {
            return false;
        } else if (length == 7) {
            return true;
        } else return (length - 2) % 5 == 0;
    }

}



style.xml:

<declare-styleable name="ContentWithSpaceEditText">
        <attr name="epaysdk_type" format="enum">
            <enum name="phone" value="0" />
            <enum name="card" value="1" />
            <enum name="IDCard" value="2" />
        </attr>
    </declare-styleable>



代码不详述,比较下上述的代码就懂了。


具体的用法:

<com.think.example.ContentWithSpaceEditText
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:hint="请输入银行卡号"
            android:textSize="14dp"
            app:epaysdk_type="card"/>




QA测了几天,发现在身份证情况下,输入一会儿会导致输入法的跳转。bug重现步骤:

1.在身份证情况下,因为允许输入数字和字母,所以未对InputType进行设置。

2.身份证的格式为****** **** **** ****,如果我先使用数字键盘输入,输入123456,此时再输入的时候(此时输入框会自动加一个空格),输入法会自动跳转到英文字母键盘,这会让用户很奇怪,为什么输入法突然跳转了。事实上在每次重排输入框内容的时候(例如添加空格,或者中间插入),就会出现该问题。


后来定位到setText该方法会导致重新唤起输入法,具体解决方法请看另外一篇文章。Android中EditText.setText(String)方法导致输入法跳转