前段时间项目中有个需求是要求只能输入汉字,并且不能输入偏旁部首,于是总结了下EditText限制输入的几种方式,但是对于语音输入的还没找到好的解决方案:

通过设置EditText的inputType来限制,可以在xml或者java代码中设置:

在xml中设置:android:inputType="textPassword"

在java代码中设置: mEditText.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);

可以通过设置不同属性来限制输入内容。

通过设置EditText的android:digits 属性来限制可输入的内容,但需要把允许输入的内容全都罗列出来,只适合允许输入少数限制的情况,如只允许输入数字,像这种只能输入汉字的情况明显不适合,总不能把几千个汉字全都罗列出来吧。

通过InputFilter来限制。

/**
* EditText限制只能输入汉字
*/
public InputFilter getInputFilter() {
InputFilter filter = new InputFilter() {
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
if (TextUtils.isEmpty(source)){
return "";
}
for (int i = start; i < end; i++) {
if (stringFilterChinese(source) && !source.toString().contains("。") && !source.toString ().contains(",")) {
return "";
} else if (CHINESE_RADICAL_DIGISTS.contains(source)) {
return "";
}
}
return null;
}
};
return filter;
}
/**
* 限制只能输入汉字,过滤非汉字
*
* @param str 输入值
* @return true 非汉字;false 汉字
*/
public boolean stringFilterChinese(CharSequence str) {
//只允许汉字,正则表达式匹配出所有非汉字
String regEx = "[^\u4E00-\u9FA5]";
Pattern p = Pattern.compile(regEx);
Matcher m = p.matcher(str);
if (m.find()) {
return true;
} else {
return false;
}
}
mEdtAddDictation.setFilters(new InputFilter[]{getInputFilter()});

查看TextView的源码,在setText中通过调用filter()过滤了相关内容:

private void setText(CharSequence text, BufferType type,boolean notifyBefore, int oldlen) {
...
int n = mFilters.length;
for (int i = 0; i < n; i++) {
CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
if (out != null) {
text = out;
}
}
...
}

通过TextWatch来限制输入。

mEdtAddDictation.addTextChangedListener(mTextWatcher);
private TextWatcher mTextWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
this.temp = s;
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable editable) {
String words = editable.toString().trim();
if (TextUtils.isEmpty(words)) {
mBtnAddSure.setEnabled(false);
} else {
mBtnAddSure.setEnabled(true);
}
if (TextUtils.isEmpty(words)) {
return;
}
String newWords = words;
newWords = StringUtils.clearLimitStr(StringUtils.DEFAULT_REGEX_LIMIT_CHINESE,newWords);
newWords = StringUtils.clearLimitStr(StringUtils.CHINESE_RADICAL_DIGISTS,newWords);
boolean isChange = false;
if (!TextUtils.equals(words,newWords)){
isChange = true;
words = newWords;
}
if (words.length() > MAX_INPUT_LIMIT) {
ToastUtils.getInstance(AddDictationWordsActivity.this).s(R.string.dictation_add_word_limit);
words = words.substring(0, MAX_INPUT_LIMIT);
isChange = true;
}
if (isChange) {
mEdtAddDictation.removeTextChangedListener(this);
// et.setText方法可能会引起键盘变化,所以用editable.replace来显示内容
editable.replace(0, editable.length(), words.trim());
mEdtAddDictation.addTextChangedListener(this);
}
}
};
/**
* 清除不符合条件的内容
*
* @param regex
* @return
*/
public static String clearLimitStr(String regex, String str) {
return str.replaceAll(regex, "");
}
/**
* 默认的筛选条件(正则:只能输入中文)
*/
public static String DEFAULT_REGEX_LIMIT_CHINESE = "[^\u4E00-\u9FA5]";
/**
* 偏旁部首
*/
public static final String CHINESE_RADICAL_DIGISTS = "[犭凵巛冖氵廴纟讠礻亻钅宀亠忄辶弋饣刂阝冫卩疒艹疋豸冂匸扌丬屮衤勹彳彡]";

通过自定义InputConnectionWrapper来限制输入。

步骤:

(1)自定义EditText,重载onCreateInputConnection方法,它需要返回一个InputConnection对象;

(2)继承于InputConnectionWrapper, 实现自己的InputConnection 并且在onCreateInputConnection中返回。

(3)在自定义的InputConnectionWrapper类中,实现输入法输入和按键事件的拦截。

由于InputConnection是在文本显示之前进行调用,因此可以通过重写其中的方法修改要显示的内容。

/**
* @author zhangshao
* @desc 只能输入汉字的输入框
* @time 2018/11/8 18:09
*/
@SuppressLint("AppCompatCustomView")
public class ChineseLimitEditText extends EditText {
public ChineseLimitEditText(Context context) {
super(context);
}
public ChineseLimitEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ChineseLimitEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 输入法
*
* @param outAttrs
* @return
*/
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
return new InnerInputConnecttion(super.onCreateInputConnection(outAttrs),
false);
}
class InnerInputConnecttion extends InputConnectionWrapper implements InputConnection {
public InnerInputConnecttion(InputConnection target, boolean mutable) {
super(target, mutable);
}
/**
* 对输入的内容进行拦截
*
* @param text
* @param newCursorPosition
* @return
*/
@Override
public boolean commitText(CharSequence text, int newCursorPosition) {
// 只能输入汉字
if (!TextUtils.isEmpty(text) && (!StringUtils.isContainChinese(text.toString()) ||
StringUtils.isContainRadical(text.toString()))) {
return false;
}
return super.commitText(text, newCursorPosition);
}
@Override
public boolean sendKeyEvent(KeyEvent event) {
// 拦截换行键
return event.getKeyCode() != KeyEvent.KEYCODE_ENTER && super.sendKeyEvent(event);
}
@Override
public boolean setSelection(int start, int end) {
return super.setSelection(start, end);
}
}
}
/**
* 字符串是否包含中文
* */
public static boolean isContainChinese(String str) {
Pattern p = Pattern.compile("[\u4e00-\u9fa5]");
Matcher m = p.matcher(str);
if (m.find()) {
return true;
}
return false;
}
/**
* 字符串是否包含偏旁部首
* */
public static boolean isContainRadical(String str) {
Pattern p = Pattern.compile(CHINESE_RADICAL_DIGISTS);
Matcher m = p.matcher(str);
if (m.find()) {
return true;
}
return false;
}

拦截条件:在commitText方法中,如果执行父类的 commitText(即super.commitText(text, newCursorPosition))那么表示不拦截,如果返回false则表示拦截,

输入法的字符串则无法传送到EditText。在sendKeyEvent中,如果执行父类的sendKeyEvent(即super.sendKeyEvent(event))那么表示不拦截,如果返回false表示拦截。

不同的需求可以通过不同的限制方法组合使用,不用局限于一种。

以上几种方法都可以解决软键盘输入时只显示中文的问题,但是搜狗输入法的语音输入无法过滤,一旦在InputFilter或者TextWatch中屏蔽,那么语音输入内容会重复,目前分析的原因是:语音输入是持续输入,如果去掉相应的标点,那么输入法会检测到输入内容与缓存的不对应,会把之前的文本拿出来重新拼接在一起返回。如果有朋友有好的解决方案,还望不吝赐教!