Android TextView的TextWatcher使用案例詳解
TextWatcher是一個文本變化監(jiān)聽接口,定義了三個接口,分別是beforeTextChanged,onTextChanged,afterTextCahnged. TextWatcher通常與TextView結合使用,以便在文本變化的不同時機做響應的處理。TextWatcher中三個回調接口都是使用了InputFilter過濾器過濾之后的文字字符作為新的字符對象。
使用方法
mTextView.addTextChangedListener(new TextWatcher(){ @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { //屏蔽回車 中英文空格 } });
我們可以在beforeTextChanged,onTextChanged,afterTextChanged的回調方法中實現自己的業(yè)務邏輯,這三個參數代表了TextView文本發(fā)生變化的三個階段。
beforeTextChanged(CharSequence s, int start, int count, int after)方法是TextView在文本改變之前調用,并且傳入四個參數。 CharSequence s參數表示當前TextView內部的mText成員變量,實際上就是當前顯示的文本; int start參數表示需要改變的文字區(qū)域的起點,即選中的文本區(qū)域的起始點; int count參數表示需要改變的文字的字符數目,即選中的文本區(qū)域的字符的數目; int after參數表示替換的文字的字符數目。 特別的,當TextView刪除文本的時候,after的值為0,此時TextView使用用空字符串代替需要改變的文字區(qū)域來達到刪除文字的目的。 圖1.1描述了beforeTextChanged的四個參數的含義。
圖1.1 beforeTextChanged的四個參數實例
TextView的setText方法通過調用sendBeforeTextChanged方法通知所有注冊的TextWatcher回調beforeTextChanged方法,此時傳入的四個參數,s是當前的本地變量mText的值,如果該值為null,即之前沒有給TextView設置過需要顯示的文本,那么s的值為"";start的值為0;count的值為當前mText的長度;after的值為需要顯示的新文本的長度。代碼1.1是TextView中setText方法調用sendBeforeTextChanged的源碼。
代碼1.1 TextView中setText方法調用sendBeforeTextChanged的源碼
if ( mText != null) { oldlen = mText.length() ; sendBeforeTextChanged( mText , 0 , oldlen , text.length()) ; } else { sendBeforeTextChanged( "" , 0 , 0 , text.length()) ; }
onTextChanged(CharSequence s, int start, int before, int count)方法是TextView在文本改變的時候調用,此時mText成員變量已經被修改為新的文本,并且傳入四個參數。 CharSequence s參數表示當前TextView內部的mText成員變量,此時的mText已經被修改過了,但此時mText所表示的文本還沒有被顯示到UI組件上; int start參數表示改變的文字區(qū)域的起點; int before參數表示改變的文字區(qū)域在改變前的舊的文本長度,即選中文字區(qū)域的文本長度; int after參數表示改變的文字區(qū)域在修改后的新的文本長度。 特別的,當TextView添加文本的時候,before 的值為0,此時相當于TextView將空的字符區(qū)域用新的文本代替。
afterTextChanged(Editable s)方法是TextView在調用完所有已注冊的TextWatcher的onTextChanged方法之后回調的。此時mText成員變量已經被修改為新的文本,并且傳入s,該參數s實際上就是mText。通過該接口,咱們可以再次修改將要展示的文字。
圖1.2描述了這三個方法在TextView文字變化時的調用流程。
圖1.2 TextView文字變化時的調用流程
afterTextChanged的參數類型是Editable,這是一個可編輯的對象,該對像就Textview的內部變量mText,此時的mText是·可編輯的,實際上是一個SpannableStringBuilder對象。代碼1.1是TextView的setText方法中text的轉換邏輯。從代碼中可以看出,當type == BufferType.EDITABLE || getKeyListener() != null || needEditableForNotification為true的時候,mEditableFactory會調用newEditable的方法創(chuàng)建一個可編輯的對象SpannableStringBuilder。
代碼 1.1 TextView的setText方法中text的轉換邏輯
boolean needEditableForNotification = false; if ( mListeners != null && mListeners.size() != 0) { needEditableForNotification = true; } if (type == BufferType. EDITABLE || getKeyListener() != null || needEditableForNotification) { createEditorIfNeeded() ; Editable t = mEditableFactory.newEditable(text) ; text = t ; setFilters(t , mFilters) ; InputMethodManager imm = InputMethodManager.peekInstance() ; if (imm != null) imm.restartInput( this) ; } else if (type == BufferType. SPANNABLE || mMovement != null) { text = mSpannableFactory.newSpannable(text) ; } else if (!(text instanceof CharWrapper)) { text = TextUtils. stringOrSpannedString(text) ; }
代碼1.2是mEditableFactory的newEditable方法,該方法是一個工廠方法,創(chuàng)建一個 SpannableStringBuilder對象。
代碼 1.2 mEditableFactory的newEditable方法
public Editable newEditable(CharSequence source) { return new SpannableStringBuilder(source) ; }
SpannableStringBuilder實現了CharSequence, GetChars, Spannable, Editable,Appendable, GraphicsOperations的接口,內部的string是可以變化的。不過咱們修改afterTextChanged 中的參數s的時候, 需要注意循環(huán)調用的潛在風險,因為SpannableStringBuilder會在自己內部保存TextView的mChangeWatcher對象,代碼1.3描述了設置過程。如代碼所示,text通過setSpan方法添加了mChangeWatcher對象,以監(jiān)聽整個text的變化,并且做回調。當text的內容發(fā)生變化的時候,會通過span機制調用mChangeWatcher中的相應方法。
代碼1.3 TextView的setText方法給mText添加mChangeWatcher的源碼
Spannable sp = (Spannable) text ; // Remove any ChangeWatchers that might have come from other TextViews. final ChangeWatcher[] watchers = sp.getSpans( 0 , sp.length() , ChangeWatcher. class) ; final int count = watchers. length ; for ( int i = 0 ; i < count ; i++) { sp.removeSpan(watchers[i]) ; } if ( mChangeWatcher == null) mChangeWatcher = new ChangeWatcher() ; sp.setSpan( mChangeWatcher , 0 , textLength , Spanned. SPAN_INCLUSIVE_INCLUSIVE | ( CHANGE_WATCHER_PRIORITY << Spanned. SPAN_PRIORITY_SHIFT)) ;
mChangeWatcher是一個ChangeWatcher對象,ChangeWatcher是TextView的內部類,實現了TextWatcher, SpanWatcher接口偶,代碼1.4描述了ChangeWatcher實現的TextWatcher的三個回調接口。這三個接口會分別調用TextView的相應的方法,通知所有注冊的TextWatcher的調用相應的三個回調接口。其中,TextView的handleTextChanged還會調用invalidate()和checkForResize()方法重繪UI界面.
代碼1.4 ChangeWatcher實現的TextWatcher的三個回調接口
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) ; ...... }
通過SpannableStringBuilder,ChangeWatcher這兩個類再加上span機制,android可以在文本改變的時候通知所有注冊的TextWatcher方法調用相應的三個接口。但是這又會使咱們在TextWatcher的afterTextChanged中修改參數s的時候,再次調用TextWatcher的三個回調接口,這樣如果afterTextChanged不能因為某些條件的判斷,終止對s的修改,那么就會形成無限循環(huán)調用。 類似TextView的setText的方法也會在相應的時機通知所有注冊的TextWatcher調用響應的三個接口,如果咱們在TextWatcher的三個接口中調用TextView的setText方法也會導致無限循環(huán)調用。圖1.3描述了無限調用的示例。
圖1.3 無限調用示例
但是有時候我們可能 需要根據業(yè)務需求更改顯示的文本,比如過濾不必要的字符,過濾非法的文字,添加必要的結束字符等。這個時候我們有時候會給TextView添加一個TextWatcher,然后在某個接口回調中根據傳入的參數修改將要顯示的文本,這可以滿足咱們的業(yè)務需求,不過有可能會導致無限循環(huán)調用。針對這個問題,咱們可以 通過InputFilter來完成修改文本的目的。 InputFilter是一個接口,內部定義了filter方法,這個方法的作用是修改傳入的字符串,如果返回值為null,那么保持原來的字符串。代碼1.5是這個方法的定義。
代碼1.5 InputFilter內部定義了filter接口
public CharSequence filter(CharSequence source , int start , int end , Spanned dest , int dstart , int dend) ;
如代碼所示,filter方法需要傳入六個參數,其中
source參數是即將替換選中字符區(qū)域的字符串對象;
start參數表示source的起始位置;
end參數表示source的終止位置。通過source,start,end這三個參數可以描述出替換選中字符區(qū)域的新的字符串。
dest表示選中文字區(qū)域的文本對象,TextView的setText方法調用filter方法時,傳入的dest為
EMPTY_SPANNED,修改文字時,傳入的
dest為TextView中保存的mText;
dstart表示選中的文字區(qū)域的起始位置;
dend表示選中的文字區(qū)域的終止位置。
該方法的返回值是用來替換source作為新的替換文本。
圖1.4是有一個filter實例,描述了filter的六個參數的含義。
圖1.4 filter實例
InputFilter接口內部提供了兩個子類,分別是AllCaps和LengthFilter。
AllCaps是將文本中的小寫字符全部轉為大寫字符的過濾器,通過該過濾器,TextView能將輸入文本中的小寫字符轉為大寫字符,然后顯示出來;
LengthFilter是刪除掉超過長度maxLength的字符的過濾器。在xml中配置了maxLength之后,TextView在創(chuàng)建實例的時候,會生成一個LengthFilter的過濾器,以便達到限定顯示字符長度的功能。
咱們自己也可以定義滿足咱們業(yè)務需求的inputfilter,以達到在TextWatcher接口回調之前過濾掉無用或者非法字符的功能,代碼1.6是一個InputFilter的實現子類,該類過濾掉無用的空白符。
代碼1.6 過濾掉無用空白符的過濾器
static class NoUsageCharInputFilter implements InputFilter { @Override public CharSequence filter(CharSequence source , int start , int end , Spanned dest , int dstart , int dend) { return source == null ? null : source.toString().replaceAll( " \\ s" , "") ; } }
定義完InputFilter的實現子類之后,咱們就可以將實現了的過濾器添加到TextView的過濾器數組中,代碼1.7是一個添加過濾器的示例,如代碼所示,通過source.setFilters(inputFilters)方法可以給TextView設置InputFilter數組,由于我們的功能是添加過濾器,因此 需要將source原本的過濾器數組中的元素添加到新的過濾器數組中,否則source原本的過濾器數組會被覆蓋掉,那樣即使咱們在xml文件中配置了maxLength,source也不會使用LengthInputFilter來限定文本的長度。
代碼1.7 添加過濾器的示例
public static void addNoUsageCharInputFilter(TextView source) { if (source == null) return; InputFilter[] inputFilters = new InputFilter[source.getFilters() != null ? source.getFilters(). length + 1: 1] ; inputFilters[ 0] = new NoUsageCharInputFilter() ; if (source.getFilters() != null) { for ( int i = 0 ; i < source.getFilters(). length ; i++) inputFilters[i + 1] = source.getFilters()[i] ; } source.setFilters(inputFilters) ; }
TexView在設置完過濾器數組之后,它的setText方法會在調用sendBeforeTextChanged之前先用過濾器數組中的過濾器修改傳入的文本參數,setText方法調用過濾器的實現見代碼1.8。
代碼1.8 TexView中setText調用過濾器的實現代碼
int n = mFilters.length; for (int i = 0; i < n; i++) { CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0); if (out != null) { text = out; } } if (notifyBefore) { if (mText != null) { oldlen = mText.length(); sendBeforeTextChanged(mText, 0, oldlen, text.length()); } else { sendBeforeTextChanged("", 0, 0, text.length()); } }
通過以上的分析:
1.咱們可以使用TextWatcher監(jiān)聽TextView文本變化的三個時機,并在回調函數中做相應的處理;
2.但是在回調函數中處理業(yè)務的時候,需要注意不要調用該TextView的setText方法,否則會發(fā)生無限循環(huán)調用;
3.在TextWatcher 的回調接口afterTextChanged方法中修改參數s的時候,也要注意在修改了s之后,會觸發(fā)文本變化,導致TextView中所有注冊的TextWatcher再次回調自己的的三個回調函數,此時需要預防無限循環(huán)調用的發(fā)生。
4.如果需要修改傳入文本,那么可以實現InputFilter接口,然后給TextView添加符合業(yè)務需求的過濾器;
5.給TextView添加自定義的過濾器的時候,需要注意使用setFilter方法設置過濾器會覆蓋掉TextView原本的過濾器,如果不想舍棄TextView原本的過濾器,那么需要將原本的過濾器添加到新的過濾器數組中。
6.TextView使用InputFilter過濾器數組的時候,是從第一個過濾器到最后一個過濾器依次使用的,因此咱們給TextView設置過濾器數組的時候需要考慮過濾器的順序。
到此這篇關于Android TextView的TextWatcher使用案例詳解的文章就介紹到這了,更多相關Android TextView的TextWatcher使用內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
深入理解Android組件間通信機制對面向對象特性的影響詳解
本篇文章是對Android組件間通信機制對面向對象特性的影響進行了詳細的分析介紹,需要的朋友參考下2013-05-05Android使用MediaPlayer和TextureView實現視頻無縫切換
這篇文章主要為大家詳細介紹了Android使用MediaPlayer和TextureView實現視頻無縫切換,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-10-10Android 開發(fā)使用Activity實現加載等待界面功能示例
這篇文章主要介紹了Android 開發(fā)使用Activity實現加載等待界面功能,結合實例形式詳細分析了Android基于Activity實現加載等待界面布局與功能操作技巧,需要的朋友可以參考下2020-05-05android scrollview 自動滾動到頂部或者底部的實例
這篇文章主要介紹了android scrollview 自動滾動到頂部或者底部的相關資料,需要的朋友可以參考下2017-06-06