Android中EditText.setText(String)方法导致输入法跳转

这篇文章的起因请先看我的另外一篇文章(有点长,都是代码惹的祸)
http://blog.csdn.net/lintcgirl/article/details/50358421

简要说明就是为EditText设置了一个TextWatcher,在onTextChanged方法中监听新的text,然后根据我想要的效果计算出一个Stringbuilder,然后调用setText方法。
如果我使用数字键盘输入,当EditText调用了setTex方法的话,会导致输入法的跳转(从数字键盘跳到了字母键盘)。
于是我参照了另外一篇博客http://blog.csdn.net/junjun071308/article/details/47305435

他的做法就是在afterTextChanged方法中不去调用setText方法,直接修改该方法中的形参,因为对该形参的修改会直接影响到EditText中的内容,所以其实并不用调用setText。
于是我尝试用这种方法去解决。将onTextChanged方法中的内容移到了afterTextChanged方法中,代码不变,只是在onTextChanged方法中记录了start、count、before参数,用在afterTextChanged方法中。
看代码:

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) {
        ContentWithSpaceEditText.this.start = start;
        ContentWithSpaceEditText.this.before = before;
        ContentWithSpaceEditText.this.count = count;
    }

    @Override
    public void afterTextChanged(Editable s) {
        if (s == null) {
            return;
        }
        //判断是否是在中间输入,需要重新计算
        boolean isMiddle = (start + count) < (s.length());
        //在末尾输入时,是否需要加入空格
        boolean isNeedSpace = false;
        if (!isMiddle && isSpace(s.length())) {
            isNeedSpace = true;
        }
        if (isMiddle || isNeedSpace || count > 1) {
            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)。
             * 但是如果调用该语句的话,在身份证输入框的情况下(允许字母和数字),当调用setText时,会导致输入法的跳转
             * 参照网上解决方法,将该句话替换成s.replace(...)
             * 该种方法不会导致输入法的跳转。
             * 造成输入法跳转的原因可能是setText会重新唤起输入法控件*/
            s.replace(0, s.length(),sb);
            //如果是在末尾的话,或者加入的字符个数大于零的话(输入或者粘贴)
            if (!isMiddle || count > 1) {
                setSelection(s.length() <= maxLength ? s.length() : maxLength);
            } else if (isMiddle) {
                //如果是删除
                if (count == 0) {
                    //如果删除时,光标停留在空格的前面,光标则要往前移一位
                    if (isSpace(start - before + 1)) {
                        setSelection((start - before) > 0 ? start - before : 0);
                    } else {
                        setSelection((start - before + 1) > s.length() ? s.length() : (start - before + 1));
                    }
                }
                //如果是增加
                else {
                    if (isSpace(start - before + count)) {
                        setSelection((start + count - before + 1) < s.length() ? (start + count - before + 1) : s.length());
                    } else {
                        setSelection(start + count - before);
                    }
                }
            }
            addTextChangedListener(watcher);
        }
    }
};

没多大变,就是把setText(sb)改成了s.replace(0, s.length(),sb);
本以为万事大吉,但是发现这样一改,身份证输入控件是可以了,但是对于电话号码和卡号就不行了,原因是在执行s.replace(0, s.length(),sb)时,因为在电话号码和卡号中设置了InputType类型为Number,在replace的时候,会自动帮你把空格给删去。举个例子:
在输入电话号码时,格式为### #### ####,在输入123时,并不会触发replace函数。如果此时再输入4,s变量的值传进来为”1234”,计算出的sb为”123 4”,然后去执行replace函数,因为设置InputType,在replace的时候,将空格删去了,所以s还是等于”1234”,虽然这句执行不会出错,但是在下面setSelection的时候,会抛出IndexOutOfRangeException,因为setSelection函数的参数是sb.length(),s的长度为4,sb的长度为5,当然会出错了。。。。

于是我为卡号和电话号码添加了输入字符的限制,可输入的字符包括”0123456789 “,重点是有空格哦,所以就可以了。
设置输入字符的代码:
setKeyListener(DigitsKeyListener.getInstance(“0123456789 “));
这里需要注意的是,setKeyListener需要在setInputType方法之后调用,否则无效。。。

这里读者会疑问,为什么原来电话和卡号输入的时候,setText(带有空格的字符串)就可以生效,为什么replace的时候就无效。首先这是一个好问题,带读者做个例子就知道了。
首先写一个界面,上面有一个输入框,在布局文件中设置inputType为number,然后我再MainActivity中为该输入框setText(“hello world”),运行该应用,结果是该输入框显示hello world。这是什么鬼,不是已经设置了InputType么。
带你看下setText源码
Android中EditText.setText(String)方法导致输入法跳转_第1张图片
该代码是private void setText(CharSequence text, BufferType type,boolean notifyBefore, int oldlen);方法中的片段。在EditText下,第二个参数type的值为BufferType.EDITABLE。
看代码,如果type为BufferType.EDITABLE的话,会根据传进来的text重新生成一个Editable,生成后才为他设置Filter,但是Filter只有在改变的时候,才会调用Filter的方法,所以Filter对初始的值也没有什么办法,所以setText的时候会成功。

接下来给大家看看整理后的源码:

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

    private int contentType;
    public static final int TYPE_PHONE = 0;
    public static final int TYPE_CARD = 1;
    public static final int TYPE_IDCARD = 2;
    private int maxLength = 100;
    private int start, count,before;
    private String digits;

    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) {
            maxLength = 13;
            digits = "0123456789 ";
            setInputType(InputType.TYPE_CLASS_NUMBER);
        } else if (contentType == TYPE_CARD) {
            maxLength = 31;
            digits = "0123456789 ";
            setInputType(InputType.TYPE_CLASS_NUMBER);
        } else if (contentType == TYPE_IDCARD) {
            maxLength = 21;
            digits = null;
            setInputType(InputType.TYPE_CLASS_TEXT);
        }
        setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)});
    }

    @Override
    public void setInputType(int type) {
        if (contentType == TYPE_PHONE || contentType == TYPE_CARD) {
            type = InputType.TYPE_CLASS_NUMBER;
        }else if(contentType == TYPE_IDCARD){
            type = InputType.TYPE_CLASS_TEXT;
        }
        super.setInputType(type);
        /* 非常重要:setKeyListener要在setInputType后面调用,否则无效。*/
        if(!TextUtils.isEmpty(digits)) {
            setKeyListener(DigitsKeyListener.getInstance(digits));
        }
    }

    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) {
        ContentWithSpaceEditText.this.start = start;
        ContentWithSpaceEditText.this.before = before;
        ContentWithSpaceEditText.this.count = count;
    }

    @Override
    public void afterTextChanged(Editable s) {
        if (s == null) {
            return;
        }
        //判断是否是在中间输入,需要重新计算
        boolean isMiddle = (start + count) < (s.length());
        //在末尾输入时,是否需要加入空格
        boolean isNeedSpace = false;
        if (!isMiddle && isSpace(s.length())) {
            isNeedSpace = true;
        }
        if (isMiddle || isNeedSpace || count > 1) {
            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)。
             * 但是如果调用该语句的话,在身份证输入框的情况下(允许字母和数字),当调用setText时,会导致输入法的跳转
             * 参照网上解决方法,将该句话替换成s.replace(...)
             * 该种方法不会导致输入法的跳转。
             * 造成输入法跳转的原因可能是setText会重新唤起输入法控件*/
            s.replace(0, s.length(),sb);
            //如果是在末尾的话,或者加入的字符个数大于零的话(输入或者粘贴)
            if (!isMiddle || count > 1) {
                setSelection(s.length() <= maxLength ? s.length() : maxLength);
            } else if (isMiddle) {
                //如果是删除
                if (count == 0) {
                    //如果删除时,光标停留在空格的前面,光标则要往前移一位
                    if (isSpace(start - before + 1)) {
                        setSelection((start - before) > 0 ? start - before : 0);
                    } else {
                        setSelection((start - before + 1) > s.length() ? s.length() : (start - before + 1));
                    }
                }
                //如果是增加
                else {
                    if (isSpace(start - before + count)) {
                        setSelection((start + count - before + 1) < s.length() ? (start + count - before + 1) : s.length());
                    } else {
                        setSelection(start + count - before);
                    }
                }
            }
            addTextChangedListener(watcher);
        }
    }
};

    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) {
        return length >= 4 && (length == 4 || (length + 1) % 5 == 0);
    }

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

    private boolean isSpaceIDCard(int length) {
        return length > 6 && (length == 7 || (length - 2) % 5 == 0);
    }

}

你可能感兴趣的:(android)