在EditText里面输入,限制输入长度并弹出提示,原本,这是最初级和普遍的需求了,但在细微处却有点小不方便。
方法之一,是在xml中添加限制:maxLength="10"。但是,当用户输入达到或超过限制时没有提示。方法之二,给EditText添加TextWatcher监控,在输入超过限制时给予提示,并恢复为之前的状态。试了一下,没法把方法一和方法二结合起来,用maxLength限制后,超过限制时,只是不能输入了,但触发不了TextWatcher,找不到机会触发弹框提示。
给予超出限制的提示很容易,但怎么恢复为本次输入之前状态却需要小心。注意点包括,用户可能键入单个字符,也可能拷贝一个字符串进去,输入光标不一定在字符串尾巴处,也可能在中间或开头。此外,用户可能已经标记高亮了部分字符串。无论哪种,只要输入后会超过限制,那么我们都要恢复成输入之前的状态。
使用TextWatcher做输入恢复的步骤是,在TextWatcher的beforeTextChanged方法中保存本次输入之前的字符串及光标位置,接着在afterTextChanged方法中对输入后的字符串长度做检查,如果超过限制,那么在这一步恢复之前的字符串及光标位置。
Android的问题在于,TextWatcher接口中afterTextChanged方法的内部设计比较不友好,存在一个递归嵌套。也即,TextWatcher的正常流程是“beforeTextChanged-->onTextChanged-->afterTextChanged”,但是,当我们在afterTextChanged方法中设法恢复或说修改字符串时,在afterTextChanged方法还没返回的情况下,流程又被触发进入新一个“beforeTextChanged-->onTextChanged-->afterTextChanged”流程,这是因为TextWatcher的设定是,如果字符串被修改,不论在哪,包括我们在第三步afterTextChanged里面修改字符串,都会立即触发并进入新一轮的“beforeTextChanged-->onTextChanged-->afterTextChanged”流程,稍不注意,这里就递归不已,马上耗完内存并stackoverflow了。
如下是限制输入长度,并弹出toast的代码,设法避免了嵌套递归:
public class LimitTextWatcher implements TextWatcher {
public interface IF_callback{
void callback(int left);
}
public IF_callback if_callback;
EditText editText;
int maxLength;
int cursorPositionLast;
String textLast;
boolean bypass;
public LimitTextWatcher(EditText editText, int maxLength, IF_callback if_callback) {
this.editText = editText;
this.maxLength = maxLength;
this.if_callback = if_callback;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (bypass) {
bypass = false;
} else {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(s);
textLast = stringBuilder.toString();
this.cursorPositionLast = editText.getSelectionStart();
}
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (s.toString().length() > maxLength) {
int left = maxLength - s.toString().length();
bypass = true;
s.clear();
bypass = true;
s.append(textLast);
editText.setSelection(this.cursorPositionLast);
if (if_callback != null) {
if_callback.callback(left);
}
}
}
}
edit_text.addTextChangedListener(new LimitTextWatcher(edit_text, MAX_LENGTH, new LimitTextWatcher.IF_callback() {
@Override
public void callback(int left) {
if(left <= 0) {
Toast.makeText(MainActivity.this, "input is full", Toast.LENGTH_SHORT).show();
}
}
}));
如上代码有个小缺陷:如果在输入之前,用户已经标记高亮了一段子字符串,那么无法恢复这段高亮。android提供设置高亮的头尾光标的方法不起作用,不知为何。