需求
输入框输入小写字母自动转为大写字母
上面是个EditText 作为输入框。最下方view 记录的是原始输入内容。
实现
1.获取 android.text.method.ReplacementTransformationMethod 类的实例对象
private ReplacementTransformationMethod transformationMethod = new ReplacementTransformationMethod() {
@Override
protected char[] getOriginal() {
return new char[]{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
}
@Override
protected char[] getReplacement() {
return new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
}
};
2.为EditText添加 TransformationMethod 监听
editText.setTransformationMethod(transformationMethod);
注意:使用这种方法两个问题
- 通过 editText.getText().toString() 获取到的是用户操作的原始输入,不是转换后的。
- 在添加的 TextWatcher 监听中,同样获取到的是输入的原始值。
原理分析
android.text.method.ReplacementTransformationMethod
/**
* 该类会将{@link #getOriginal}中的字符数组替换为{@link #getReplacement}数组。
*/
public abstract class ReplacementTransformationMethod
implements TransformationMethod
{
/**
* 返回显示时将被其他字符替换的字符列表。
*/
protected abstract char[] getOriginal();
/**
* 返回替换的字符列表
*/
protected abstract char[] getReplacement();
/**
* 处理替换的逻辑,getOriginal() 和 getReplacement() 方法在此方法内被调用以完成字符替换
*/
public CharSequence getTransformation(CharSequence source, View v) {
...省略...
}
}
android.text.method.TransformationMethod
/**
* TextView使用TransformationMethods进行字符替换操作,例如用点替换密码字符,或防止换行符在单行文本字段中引起换行。
*/
public interface TransformationMethod
{
/**
* 返回源文本sourceText转换后的数据。例如,用密码字段中的点替换每个字符。 需要主要的是返回的文本
* 必须与源文本长度完全相同,并且如果源文本是可编辑的,则返回的文本必须动态镜像它,而不是进行一次复制。
*/
public CharSequence getTransformation(CharSequence source, View view);
/**
* 使用该 TransformationMethod 的TextView 在获得/失去 焦点时调用
*/
public void onFocusChanged(View view, CharSequence sourceText,
boolean focused, int direction,
Rect previouslyFocusedRect);
}
android.widget.TextView#setTransformationMethod
TextView 内部有两个字段来控制源文本和变换后的文本
@ViewDebug.ExportedProperty(category = "text")
private @Nullable CharSequence mText; //源文本
private CharSequence mTransformed; //变换的文本
TextView 的setTransformationMethod 内部会调用 setText(java.lang.CharSequence) 方法,最终会执行到 setText(java.lang.CharSequence, android.widget.TextView.BufferType, boolean, int) 方法内。在该方法内调用 mTransformation.getTransformation(text, this) 方法对源数据进行变换,并把返回的值赋给 mTransformed 字段。
private void setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen) {
...省略其他代码
if (mTransformation == null) {
mTransformed = text;
} else {
mTransformed = mTransformation.getTransformation(text, this);
}
... 省略其他代码
sendOnTextChanged(text, 0, oldlen, textLength);
... 省略其他代码
sendAfterTextChanged((Editable) text);
... 省略其他代码
}
在 sendOnTextChanged 和 sendAfterTextChanged 方法中会执行文本改变监听的调用,而传入的text 为 mText ,非mTransformed,这也就解释了为啥在给TextView 添加 TextWatcher 监听时,获得的值仍然为源文本,而非转换后的文本
/**
* Not private so it can be called from an inner class without going
* through a thunk.
*/
void sendOnTextChanged(CharSequence text, int start, int before, int after) {
if (mListeners != null) {
final ArrayList<TextWatcher> list = mListeners;
final int count = list.size();
for (int i = 0; i < count; i++) {
list.get(i).onTextChanged(text, start, before, after);
}
}
if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
}
/**
* Not private so it can be called from an inner class without going
* through a thunk.
*/
void sendAfterTextChanged(Editable text) {
if (mListeners != null) {
final ArrayList<TextWatcher> list = mListeners;
final int count = list.size();
for (int i = 0; i < count; i++) {
list.get(i).afterTextChanged(text);
}
}
// Always notify AutoFillManager - it will return right away if autofill is disabled.
notifyAutoFillManagerAfterTextChangedIfNeeded();
hideErrorIfUnchanged();
}