TextWatcher 定義
官方: 當文字發生變化時會觸發介面回撥. 一般應用場景為自定義輸入框用於對使用者的輸入進行限制,比如只能輸入英文,數字等等
TextWatcher使用
TextWatcher 為介面,通常配合 TextView
或 EditText
進行使用,需要我們自行實現介面方法並且通過textview.addTextChangedListener(textWatcher)
為對應的view新增監聽
方法解讀
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
持有的字串進行操作.
原始碼分析
新增監聽
textview.addTextChangedListener(textWatcher)
// TextView.java
public void addTextChangedListener(TextWatcher watcher) {
if (mListeners == null) {
mListeners = new ArrayList<TextWatcher>();
}
mListeners.add(watcher);
}
複製程式碼
通過原始碼可知新增監聽是將所有的已新增監聽通過 List
進行儲存,由此可以得知可以對同一 TextView
設定多個 TextWatcher
擴充套件: removeTextChangedListener(TextWatcher watcher)
可以移除指定 TextWatcher
, 沒有類似 removeAllXXX
這種移除全部 TextWatcher
的方法.
setText(newStr)
setText(newStr) 是一種特殊的改變文字的方式,無論 newStr
是什麼,都會完全覆蓋原值
// 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
之後迴圈呼叫
Editable
前面已經說過觸發回撥的方式的其中一個方式: 直接對 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
的監聽器,觸發回撥