TextWatcher的使用及源碼解析

TextWatcher 定義

官方: 當文本發生變化時會觸發接口回調. 通常應用場景爲自定義輸入框用於對用戶的輸入進行限制,好比只能輸入英文,數字等等java

TextWatcher使用

TextWatcher 爲接口,一般配合 TextViewEditText進行使用,須要咱們自行實現接口方法而且經過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);
}
複製代碼

觸發條件

  1. 鍵盤文本輸入 鍵盤輸入直接對文本進行修改,包括且不限於文本粘貼,文本修改,新增文本等等.函數

  2. setText(newStr)TextView 進行文本設置,不侷限於 setText() 方法,包括 replace() , apeend() 等等直接對文本進行修改操做的方法,都會觸發 TextWatcher 的回調.源碼分析

  3. 修改回調方法中的 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 設置多個 TextWatcherspa

擴展: removeTextChangedListener(TextWatcher watcher) 能夠移除指定 TextWatcher, 沒有相似 removeAllXXX 這種移除所有 TextWatcher 的方法.rest

setText(newStr)

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 以後循環調用

Editable

前面已經說過觸發回調的方式的其中一個方式: 直接對 afterTextChanged(Editable s) 回調方法返回的Editable 對象進行操做

Editable是一種特殊的字符串,對其作任何操做,其內存地址不會發生變化,始終指向原內存地址,不一樣於常規 String 類型每次賦值都會在內存中開闢新的內存進行存儲.

下面以append()方法爲例簡要歸納修改Editable觸發監聽的流程

  1. 調用 Editable 對象的 append() 方法進行字符串拼接.
  2. 獲取自身綁定的 TextWatcher
  3. 類比 setText() 方法的回調觸發流程,在對應的時機觸發回調

固然關鍵在於第二步 >>>>>> 獲取自身綁定的 TextWatcher

  1. setText() 時獲取 TextView 的內部類 ChangeWatcher 對象,並綁定給自身持有的字符串對象上
  2. ChangeWatcher 實現了 TextWatcher 的回調方法,並在回調方法中調用 TextViewsendBeforeTextChanged 等方法進行遍歷調用.

下面經過源碼進行流程分析

Editableappend() 方法解析

//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);
}
...
複製代碼

這部分代碼在 TextViewsetText() 方法中, 當咱們須要監聽的控件是 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));
複製代碼

ChangeWatcherTextView的內部類,在 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()方法進行修改時的流程能夠簡單理解爲

  1. 調用replace()
  2. 獲取綁定TextWatcher
  3. 調用TextWatcher實例方法
  4. 其中的ChangeWatcher對象的方法會去調用TextView的方法
  5. 遍歷TextView的監聽器,觸發回調
相關文章
相關標籤/搜索