官方: 当文本发生变化时会触发接口回调. 通常应用场景为自定义输入框用于对用户的输入进行限制,好比只能输入英文,数字等等java
TextWatcher 为接口,一般配合 TextView
或 EditText
进行使用,须要咱们自行实现接口方法而且经过textview.addTextChangedListener(textWatcher)
为对应的view添加监听app
public interface TextWatcher extends NoCopySpan {
/** * 文本改变前回调方法 * 改变完成前的字符串 s 的 start 位置开始的 count 个字符将会被 after 个字符替换 * index 为 start 的 after 个字符被删除后,新的字符串插入到 start 位置 */
public void beforeTextChanged(CharSequence s, int start, int count, int after);
/** * 文本改变完成回调方法 * 改变完成后的字符串 s 的 start 位置开始的 before 个字符已经被 count 个字符替换 */
public void onTextChanged(CharSequence s, int start, int before, int count);
/** * 文本改变后的回调方法,在 onTextChanged 方法后调用 * s为改变完成的字符串 */
public void afterTextChanged(Editable s);
}
复制代码
键盘文本输入 键盘输入直接对文本进行修改,包括且不限于文本粘贴,文本修改,新增文本等等.函数
setText(newStr) 对 TextView 进行文本设置,不局限于 setText()
方法,包括 replace()
, apeend()
等等直接对文本进行修改操做的方法,都会触发 TextWatcher 的回调.源码分析
修改回调方法中的 Editable
对象 Editable
属于可变字符串,对其进行字符串操做不会产生新的对象,等同于对 TextView
持有的字符串进行操做.ui
textview.addTextChangedListener(textWatcher)this
// TextView.java
public void addTextChangedListener(TextWatcher watcher) {
if (mListeners == null) {
mListeners = new ArrayList<TextWatcher>();
}
mListeners.add(watcher);
}
复制代码
经过源码可知添加监听是将全部的已添加监听经过 List
进行存储,由此能够得知能够对同一 TextView
设置多个 TextWatcher
spa
扩展: removeTextChangedListener(TextWatcher watcher)
能够移除指定 TextWatcher
, 没有相似 removeAllXXX
这种移除所有 TextWatcher
的方法.rest
setText(newStr) 是一种特殊的改变文本的方式,不管 newStr
是什么,都会彻底覆盖原值code
// TextView.java
private void setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen) {
...
if (mText != null) {
oldlen = mText.length();
sendBeforeTextChanged(mText, 0, oldlen, text.length());
} else {
sendBeforeTextChanged("", 0, 0, text.length());
}
...
}
private void sendBeforeTextChanged(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).beforeTextChanged(text, start, before, after);
}
}
...
}
复制代码
经过源码可知若是 newStr
不为null,那么 beforeTextChanged
的第一个参数 text
始终为原字符串.第二个参数 start
始终为0, setText()
的本质是彻底替换现有字符串,从起始位置0进行替换.第三个参数 before
为现有的字符串长度,由于要彻底替换全部的字符.第四个参数 after
为新字符串的长度.对象
// TextView.java
private void setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen) {
...
sendOnTextChanged(text, 0, oldlen, textLength);
...
if (needEditableForNotification) {
sendAfterTextChanged((Editable) text);
}
...
}
复制代码
在对持有字符串进行新的赋值后会调用剩余2个回调方法,若是设置了 TextWatcher
,那么needEditableForNotification
的值就为true,意味着只要对 TextView
设置了输入监听,那么 afterTextChanged()
必定可以被回调. 调用回调方法的详细操做就不列出,与 beforeTextChanged
同样,遍历全部已设置的 TextWatcher
以后循环调用
前面已经说过触发回调的方式的其中一个方式: 直接对 afterTextChanged(Editable s)
回调方法返回的Editable
对象进行操做
Editable
是一种特殊的字符串,对其作任何操做,其内存地址不会发生变化,始终指向原内存地址,不一样于常规 String
类型每次赋值都会在内存中开辟新的内存进行存储.
下面以append()
方法为例简要归纳修改Editable触发监听的流程
Editable
对象的 append()
方法进行字符串拼接.TextWatcher
setText()
方法的回调触发流程,在对应的时机触发回调固然关键在于第二步 >>>>>> 获取自身绑定的 TextWatcher
setText()
时获取 TextView
的内部类 ChangeWatcher
对象,并绑定给自身持有的字符串对象上ChangeWatcher
实现了 TextWatcher
的回调方法,并在回调方法中调用 TextView
的 sendBeforeTextChanged
等方法进行遍历调用.下面经过源码进行流程分析
Editable
的 append()
方法解析//TextView.java
...
if (mListeners != null && mListeners.size() != 0) {
needEditableForNotification = true;
}
if (type == BufferType.EDITABLE || getKeyListener() != null
|| needEditableForNotification) {
createEditorIfNeeded();
mEditor.forgetUndoRedo();
Editable t = mEditableFactory.newEditable(text);
text = t;
setFilters(t, mFilters);
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) imm.restartInput(this);
}
...
复制代码
这部分代码在 TextView
的 setText()
方法中, 当咱们须要监听的控件是 EditText
时,在其构造方法内直接把type
设置成了BufferType.EDITABLE
,当咱们为其设置了监听时, needEditableForNotification
的值也会被设置成true
.
函数体内,咱们重点关注Editable t = mEditableFactory.newEditable(text)
// Editable.Factory
public Editable newEditable(CharSequence source) {
return new SpannableStringBuilder(source);
}
复制代码
经过工厂方法返回一个实现了Editable
接口的对象SpannableStringBuilder
,而且当咱们进行append()
操做时,经历如下流程
// SpannableStringBuilder.java
public SpannableStringBuilder append(CharSequence text) {
int length = length();
return replace(length, length, text, 0, text.length());
}
...
public SpannableStringBuilder replace(final int start, final int end, CharSequence tb, int tbstart, int tbend) {
...
TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);
sendBeforeTextChanged(textWatchers, start, origLen, newLen);
...
sendTextChanged(textWatchers, start, origLen, newLen);
sendAfterTextChanged(textWatchers);
...
}
复制代码
是否是很眼熟? 在setText()
时的流程也十分类似, 获取绑定的TextWatcher
-> sendBeforeTextChanged() -> 字符串修改 -> sendTextChanged() -> sendAfterTextChanged()
在对Editable
对象进行调用相关方法修改而不是直接赋值时,将获取与其绑定的TextWatcher
并遍历调用相关方法,那么TextWatcher
是什么时候与其绑定的呢?
Editable
绑定TextWatcher
// TextView.setText
if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
| (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
复制代码
ChangeWatcher
是 TextView
的内部类,在 setText
方法中获取内部类实例而且设置给Editable对象
此时咱们发现 Editable
里获取的TextWatcher
并非 TextView
里面咱们设置的监听,而是获取到了TextView
的内部类实例,下面咱们再看ChangeWatcher
类
private class ChangeWatcher implements TextWatcher, SpanWatcher {
private CharSequence mBeforeText;
public void beforeTextChanged(CharSequence buffer, int start, int before, int after) {
...
TextView.this.sendBeforeTextChanged(buffer, start, before, after);
}
public void onTextChanged(CharSequence buffer, int start, int before, int after) {
...
TextView.this.handleTextChanged(buffer, start, before, after);
}
public void afterTextChanged(Editable buffer) {
...
TextView.this.sendAfterTextChanged(buffer);
}
}
复制代码
咱们能够看出,ChangeWatcher
是实现了TextWatcher
的三个方法,而且在方法体内分别调用了TextView
的相关方法,而TextView
中的方法又会遍历TextWatcher
的list去分别调用这3个回调
那么Editable
调用append()
方法进行修改时的流程能够简单理解为
replace()
TextWatcher
TextWatcher
实例方法ChangeWatcher
对象的方法会去调用TextView
的方法TextView
的监听器,触发回调