由于项目的需求,需要在卡号输入时,每四位用空间分隔,于是就写了个控件。
该控件支持中间删除,中间增加,粘贴,末尾输入等,光标的位置显示正确。
主要的思想就是:对于添加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;
}
}
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)方法导致输入法跳转