由于项目的需求,需要在卡号输入时,每四位用空间分隔,于是就写了个控件。
该控件支持中间删除,中间增加,粘贴,末尾输入等,光标的位置显示正确。
主要的思想就是:对于添加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(" ",""); } }
看了该效果,如果现在让你再写个电话号码输入框,应该会写了吧?
/** * 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; } }
现在新需求来了,又来了一个身份证号码输入框,格式是: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; } }
<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)方法导致输入法跳转