Android TextView的TextWatcher使用案例詳解
TextWatcher是一個文本變化監(jiān)聽接口,定義了三個接口,分別是beforeTextChanged,onTextChanged,afterTextCahnged. TextWatcher通常與TextView結(jié)合使用,以便在文本變化的不同時機(jī)做響應(yīng)的處理。TextWatcher中三個回調(diào)接口都是使用了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的回調(diào)方法中實現(xiàn)自己的業(yè)務(wù)邏輯,這三個參數(shù)代表了TextView文本發(fā)生變化的三個階段。
beforeTextChanged(CharSequence s, int start, int count, int after)方法是TextView在文本改變之前調(diào)用,并且傳入四個參數(shù)。 CharSequence s參數(shù)表示當(dāng)前TextView內(nèi)部的mText成員變量,實際上就是當(dāng)前顯示的文本; int start參數(shù)表示需要改變的文字區(qū)域的起點,即選中的文本區(qū)域的起始點; int count參數(shù)表示需要改變的文字的字符數(shù)目,即選中的文本區(qū)域的字符的數(shù)目; int after參數(shù)表示替換的文字的字符數(shù)目。 特別的,當(dāng)TextView刪除文本的時候,after的值為0,此時TextView使用用空字符串代替需要改變的文字區(qū)域來達(dá)到刪除文字的目的。 圖1.1描述了beforeTextChanged的四個參數(shù)的含義。
圖1.1 beforeTextChanged的四個參數(shù)實例
TextView的setText方法通過調(diào)用sendBeforeTextChanged方法通知所有注冊的TextWatcher回調(diào)beforeTextChanged方法,此時傳入的四個參數(shù),s是當(dāng)前的本地變量mText的值,如果該值為null,即之前沒有給TextView設(shè)置過需要顯示的文本,那么s的值為"";start的值為0;count的值為當(dāng)前mText的長度;after的值為需要顯示的新文本的長度。代碼1.1是TextView中setText方法調(diào)用sendBeforeTextChanged的源碼。
代碼1.1 TextView中setText方法調(diào)用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在文本改變的時候調(diào)用,此時mText成員變量已經(jīng)被修改為新的文本,并且傳入四個參數(shù)。 CharSequence s參數(shù)表示當(dāng)前TextView內(nèi)部的mText成員變量,此時的mText已經(jīng)被修改過了,但此時mText所表示的文本還沒有被顯示到UI組件上; int start參數(shù)表示改變的文字區(qū)域的起點; int before參數(shù)表示改變的文字區(qū)域在改變前的舊的文本長度,即選中文字區(qū)域的文本長度; int after參數(shù)表示改變的文字區(qū)域在修改后的新的文本長度。 特別的,當(dāng)TextView添加文本的時候,before 的值為0,此時相當(dāng)于TextView將空的字符區(qū)域用新的文本代替。
afterTextChanged(Editable s)方法是TextView在調(diào)用完所有已注冊的TextWatcher的onTextChanged方法之后回調(diào)的。此時mText成員變量已經(jīng)被修改為新的文本,并且傳入s,該參數(shù)s實際上就是mText。通過該接口,咱們可以再次修改將要展示的文字。
圖1.2描述了這三個方法在TextView文字變化時的調(diào)用流程。
圖1.2 TextView文字變化時的調(diào)用流程
afterTextChanged的參數(shù)類型是Editable,這是一個可編輯的對象,該對像就Textview的內(nèi)部變量mText,此時的mText是·可編輯的,實際上是一個SpannableStringBuilder對象。代碼1.1是TextView的setText方法中text的轉(zhuǎn)換邏輯。從代碼中可以看出,當(dāng)type == BufferType.EDITABLE || getKeyListener() != null || needEditableForNotification為true的時候,mEditableFactory會調(diào)用newEditable的方法創(chuàng)建一個可編輯的對象SpannableStringBuilder。
代碼 1.1 TextView的setText方法中text的轉(zhuǎn)換邏輯
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實現(xiàn)了CharSequence, GetChars, Spannable, Editable,Appendable, GraphicsOperations的接口,內(nèi)部的string是可以變化的。不過咱們修改afterTextChanged 中的參數(shù)s的時候, 需要注意循環(huán)調(diào)用的潛在風(fēng)險,因為SpannableStringBuilder會在自己內(nèi)部保存TextView的mChangeWatcher對象,代碼1.3描述了設(shè)置過程。如代碼所示,text通過setSpan方法添加了mChangeWatcher對象,以監(jiān)聽整個text的變化,并且做回調(diào)。當(dāng)text的內(nèi)容發(fā)生變化的時候,會通過span機(jī)制調(diào)用mChangeWatcher中的相應(yīng)方法。
代碼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的內(nèi)部類,實現(xiàn)了TextWatcher, SpanWatcher接口偶,代碼1.4描述了ChangeWatcher實現(xiàn)的TextWatcher的三個回調(diào)接口。這三個接口會分別調(diào)用TextView的相應(yīng)的方法,通知所有注冊的TextWatcher的調(diào)用相應(yīng)的三個回調(diào)接口。其中,TextView的handleTextChanged還會調(diào)用invalidate()和checkForResize()方法重繪UI界面.
代碼1.4 ChangeWatcher實現(xiàn)的TextWatcher的三個回調(diào)接口
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機(jī)制,android可以在文本改變的時候通知所有注冊的TextWatcher方法調(diào)用相應(yīng)的三個接口。但是這又會使咱們在TextWatcher的afterTextChanged中修改參數(shù)s的時候,再次調(diào)用TextWatcher的三個回調(diào)接口,這樣如果afterTextChanged不能因為某些條件的判斷,終止對s的修改,那么就會形成無限循環(huán)調(diào)用。 類似TextView的setText的方法也會在相應(yīng)的時機(jī)通知所有注冊的TextWatcher調(diào)用響應(yīng)的三個接口,如果咱們在TextWatcher的三個接口中調(diào)用TextView的setText方法也會導(dǎo)致無限循環(huán)調(diào)用。圖1.3描述了無限調(diào)用的示例。
圖1.3 無限調(diào)用示例
但是有時候我們可能 需要根據(jù)業(yè)務(wù)需求更改顯示的文本,比如過濾不必要的字符,過濾非法的文字,添加必要的結(jié)束字符等。這個時候我們有時候會給TextView添加一個TextWatcher,然后在某個接口回調(diào)中根據(jù)傳入的參數(shù)修改將要顯示的文本,這可以滿足咱們的業(yè)務(wù)需求,不過有可能會導(dǎo)致無限循環(huán)調(diào)用。針對這個問題,咱們可以 通過InputFilter來完成修改文本的目的。 InputFilter是一個接口,內(nèi)部定義了filter方法,這個方法的作用是修改傳入的字符串,如果返回值為null,那么保持原來的字符串。代碼1.5是這個方法的定義。
代碼1.5 InputFilter內(nèi)部定義了filter接口
public CharSequence filter(CharSequence source , int start , int end , Spanned dest , int dstart , int dend) ;
如代碼所示,filter方法需要傳入六個參數(shù),其中
source參數(shù)是即將替換選中字符區(qū)域的字符串對象;
start參數(shù)表示source的起始位置;
end參數(shù)表示source的終止位置。通過source,start,end這三個參數(shù)可以描述出替換選中字符區(qū)域的新的字符串。
dest表示選中文字區(qū)域的文本對象,TextView的setText方法調(diào)用filter方法時,傳入的dest為
EMPTY_SPANNED,修改文字時,傳入的
dest為TextView中保存的mText;
dstart表示選中的文字區(qū)域的起始位置;
dend表示選中的文字區(qū)域的終止位置。
該方法的返回值是用來替換source作為新的替換文本。
圖1.4是有一個filter實例,描述了filter的六個參數(shù)的含義。
圖1.4 filter實例
InputFilter接口內(nèi)部提供了兩個子類,分別是AllCaps和LengthFilter。
AllCaps是將文本中的小寫字符全部轉(zhuǎn)為大寫字符的過濾器,通過該過濾器,TextView能將輸入文本中的小寫字符轉(zhuǎn)為大寫字符,然后顯示出來;
LengthFilter是刪除掉超過長度maxLength的字符的過濾器。在xml中配置了maxLength之后,TextView在創(chuàng)建實例的時候,會生成一個LengthFilter的過濾器,以便達(dá)到限定顯示字符長度的功能。
咱們自己也可以定義滿足咱們業(yè)務(wù)需求的inputfilter,以達(dá)到在TextWatcher接口回調(diào)之前過濾掉無用或者非法字符的功能,代碼1.6是一個InputFilter的實現(xiàn)子類,該類過濾掉無用的空白符。
代碼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的實現(xiàn)子類之后,咱們就可以將實現(xiàn)了的過濾器添加到TextView的過濾器數(shù)組中,代碼1.7是一個添加過濾器的示例,如代碼所示,通過source.setFilters(inputFilters)方法可以給TextView設(shè)置InputFilter數(shù)組,由于我們的功能是添加過濾器,因此 需要將source原本的過濾器數(shù)組中的元素添加到新的過濾器數(shù)組中,否則source原本的過濾器數(shù)組會被覆蓋掉,那樣即使咱們在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在設(shè)置完過濾器數(shù)組之后,它的setText方法會在調(diào)用sendBeforeTextChanged之前先用過濾器數(shù)組中的過濾器修改傳入的文本參數(shù),setText方法調(diào)用過濾器的實現(xiàn)見代碼1.8。
代碼1.8 TexView中setText調(diào)用過濾器的實現(xiàn)代碼
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文本變化的三個時機(jī),并在回調(diào)函數(shù)中做相應(yīng)的處理;
2.但是在回調(diào)函數(shù)中處理業(yè)務(wù)的時候,需要注意不要調(diào)用該TextView的setText方法,否則會發(fā)生無限循環(huán)調(diào)用;
3.在TextWatcher 的回調(diào)接口afterTextChanged方法中修改參數(shù)s的時候,也要注意在修改了s之后,會觸發(fā)文本變化,導(dǎo)致TextView中所有注冊的TextWatcher再次回調(diào)自己的的三個回調(diào)函數(shù),此時需要預(yù)防無限循環(huán)調(diào)用的發(fā)生。
4.如果需要修改傳入文本,那么可以實現(xiàn)InputFilter接口,然后給TextView添加符合業(yè)務(wù)需求的過濾器;
5.給TextView添加自定義的過濾器的時候,需要注意使用setFilter方法設(shè)置過濾器會覆蓋掉TextView原本的過濾器,如果不想舍棄TextView原本的過濾器,那么需要將原本的過濾器添加到新的過濾器數(shù)組中。
6.TextView使用InputFilter過濾器數(shù)組的時候,是從第一個過濾器到最后一個過濾器依次使用的,因此咱們給TextView設(shè)置過濾器數(shù)組的時候需要考慮過濾器的順序。
到此這篇關(guān)于Android TextView的TextWatcher使用案例詳解的文章就介紹到這了,更多相關(guān)Android TextView的TextWatcher使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

Android 實現(xiàn)無網(wǎng)絡(luò)傳輸文件的示例代碼

Android實現(xiàn)讀寫JSON數(shù)據(jù)的方法

Android使用MediaPlayer和TextureView實現(xiàn)視頻無縫切換

Android 開發(fā)使用Activity實現(xiàn)加載等待界面功能示例

android scrollview 自動滾動到頂部或者底部的實例