日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

InputFilter 和 TextWatcher

發(fā)布時(shí)間:2023/12/9 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 InputFilter 和 TextWatcher 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Android 成長(zhǎng)在于積累和分享

原文:https://www.jianshu.com/p/223dcbac3e4d

版權(quán)聲明:本文為參考博主編寫(xiě)的原創(chuàng)文章,參考原文: https://blog.csdn.net/u014606081/article/details/53101629

文章目錄

    • UCS 移動(dòng)端技術(shù)分享
    • 導(dǎo)讀
    • 前言
    • InputFilter
    • TextWatcher
      • 情況一:setText()
      • 情況二:鍵盤(pán)輸入
    • 總結(jié)
    • 鳴謝

UCS 移動(dòng)端技術(shù)分享

技術(shù)形式的一種分享,發(fā)現(xiàn)在寫(xiě)過(guò)程中參考別人博客的同時(shí)發(fā)現(xiàn)確實(shí)他寫(xiě)的更完善點(diǎn) ,所以在他博客的基礎(chǔ)上添加了一部分自己理解的東西。也是給大家看一下,如果有不對(duì)的地方,希望我們一起修正和學(xué)習(xí)。

導(dǎo)讀

InputFilter源碼解析、TextWatcher源碼解析

前言

Android中 InputFilterTextWatcher 的功能和作用非常相似,都可以做到對(duì) EditText 輸入內(nèi)容的監(jiān)聽(tīng)及控制。那兩者具體有什么區(qū)別,又是如何實(shí)現(xiàn)對(duì)輸入內(nèi)容進(jìn)行監(jiān)聽(tīng)的。下面我們就從源碼的角度一起分析一下。

分析源碼之前先打一下基礎(chǔ),EditText是繼承自TextView,90%的功能跟TextView是一致的,只有4個(gè)私有方法,剩下8個(gè)是重寫(xiě)TextView的方法。所以EditText的大部分功能都是在TextView中完成的,具體邏輯也都是在TextView中。

InputFilter

InputFilter里面只有一個(gè)方法filter(),返回值為CharSequence,用于過(guò)濾或者輸入/插入的字符串, 當(dāng)返回值不為null時(shí),使用返回結(jié)果替換原有輸入/插入的字符串

package android.text;/*** InputFilters can be attached to {@link Editable}s to constrain the* changes that can be made to them.*/ public interface InputFilter {/*** @param source 將要插入的字符串,來(lái)自鍵盤(pán)輸入、粘貼* @param start source的起始位置,為0(暫時(shí)沒(méi)有發(fā)現(xiàn)其它值的情況)輸入-0,刪除-0* @param end source的長(zhǎng)度: 輸入-文字的長(zhǎng)度,刪除-0* @param dest EditText中已經(jīng)存在的字符串,原先顯示的內(nèi)容* @param dstart 插入點(diǎn)的位置:輸入-原光標(biāo)位置,刪除-光標(biāo)刪除結(jié)束位置* @param dend 輸入-原光標(biāo)位置,刪除-光標(biāo)刪除開(kāi)始位置*/public CharSequence filter(CharSequence source, int start, int end,Spanned dest, int dstart, int dend);...略... }

TextView類中的setText()方法,會(huì)調(diào)用filter()方法,得到過(guò)濾后的字符串,部分源碼如下:

:*setText()*方法有很多重載方法,但是最終都會(huì)調(diào)用下面這個(gè)。這個(gè)方法很重要,只需要看關(guān)鍵邏輯處的注釋。

/*** @param text 將要設(shè)置的新內(nèi)容* @param type 內(nèi)容的設(shè)置類型(static text, styleable/spannable text, or editable text)* @param notifyBefore 是否需要觸發(fā)TextWacther的before* @param oldlen 新增加內(nèi)容的長(zhǎng)度 */ private void setText(CharSequence text, BufferType type,boolean notifyBefore, int oldlen) {mTextFromResource = false;if (text == null) {text = "";}...略...// 使用InputFilter處理textint 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;}}...略...// 如果是EditText,就new一個(gè)Editableif (type == BufferType.EDITABLE || getKeyListener() != null ||needEditableForNotification) {createEditorIfNeeded();Editable t = mEditableFactory.newEditable(text);text = t;setFilters(t, mFilters); // filter的另外一處過(guò)濾調(diào)用處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);}... 略 ...}

mFilters是一個(gè)InputFilter數(shù)組,因?yàn)橛幸粋€(gè)或多個(gè)過(guò)濾器。通過(guò)for循環(huán),把text按照所有過(guò)濾條件全部過(guò)濾一遍,最終得到**“合格”**的text。

除了setText方法,在鍵盤(pán)鍵入的過(guò)程中同樣會(huì)過(guò)濾,TextViewsetFilters方法給Editable設(shè)置了filters,并在Editablereplace方法中進(jìn)行過(guò)濾。

private void setFilters(Editable e, InputFilter[] filters) {if (mEditor != null) {final boolean undoFilter = mEditor.mUndoInputFilter != null;final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;int num = 0;if (undoFilter) num++;if (keyFilter) num++;if (num > 0) {InputFilter[] nf = new InputFilter[filters.length + num];System.arraycopy(filters, 0, nf, 0, filters.length);num = 0;if (undoFilter) {nf[filters.length] = mEditor.mUndoInputFilter;num++;}if (keyFilter) {nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;}e.setFilters(nf);return;}}e.setFilters(filters);

舉例SpannableStringBuilderreplace方法的實(shí)現(xiàn)。

// Documentation from interfacepublic SpannableStringBuilder replace(final int start, final int end,CharSequence tb, int tbstart, int tbend) {checkRange("replace", start, end);int filtercount = mFilters.length;for (int i = 0; i < filtercount; i++) {CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);if (repl != null) {tb = repl;tbstart = 0;tbend = repl.length();}}... 略... }

知道了InputFilter是如何起作用的,那么剩下的就是搞清楚**filter()**方法中的各個(gè)參數(shù)的含義,寫(xiě)出自己需要的InputFilter。

SDK提供了兩個(gè)實(shí)現(xiàn):AllCapsLengthFilter,下面以LengthFilter解讀InputFilter的用法,源碼片段如下:

public static class LengthFilter implements InputFilter {private final int mMax;public LengthFilter(int max) {mMax = max;}//參數(shù)source:將要插入的字符串,來(lái)自鍵盤(pán)輸入、粘貼//參數(shù)start:source的起始位置,為0(暫時(shí)沒(méi)有發(fā)現(xiàn)其它值的情況)輸入-0,刪除-0//參數(shù)end:source的長(zhǎng)度: 輸入-文字的長(zhǎng)度,刪除-0//參數(shù)dest:EditText中已經(jīng)存在的字符串,原先顯示的內(nèi)容//參數(shù)dstart:插入點(diǎn)的位置:輸入-原光標(biāo)位置,刪除-光標(biāo)刪除結(jié)束位置//參數(shù)dend:輸入-原光標(biāo)位置,刪除-光標(biāo)刪除開(kāi)始位置public CharSequence filter(CharSequence source, int start, int end, Spanned dest,int dstart, int dend) {int keep = mMax - (dest.length() - (dend - dstart));if (keep <= 0) {// 如果超出字?jǐn)?shù)限制,就返回“”return "";} else if (keep >= end - start) {// 如果完全滿足限制,就返回null(如果返回值為null,TextView中就會(huì)使用原始source)return null; // keep original} else {keep += start;if (Character.isHighSurrogate(source.charAt(keep - 1))) {// 如果最后一位字符是HighSurrogate(高編碼,占2個(gè)字符位),就把kepp減1,保證不超出字?jǐn)?shù)限制--keep;if (keep == start) {return "";}}return source.subSequence(start, keep);}}/*** @return the maximum length enforced by this input filter*/public int getMax() {return mMax;}}

TextWatcher

類如其名,用于觀察Text的輸入刪除等變化。

package android.text;/*** When an object of a type is attached to an Editable, its methods will* be called when the text is changed.*/ public interface TextWatcher extends NoCopySpan {/*** @param s原內(nèi)容* @param start 被替換內(nèi)容起點(diǎn)坐標(biāo)* @param count 被替換內(nèi)容的長(zhǎng)度* @param after 新增加內(nèi)容的長(zhǎng)度*/public void beforeTextChanged(CharSequence s, int start,int count, int after);/*** @param s 發(fā)生改變后的內(nèi)容* @param start 被替換內(nèi)容的起點(diǎn)坐標(biāo)* @param before 被替換內(nèi)容的長(zhǎng)度* @param count 新增加的內(nèi)容的長(zhǎng)度*/public void onTextChanged(CharSequence s, int start, int before, int count);/*** @param s 發(fā)生改變后的內(nèi)容(對(duì)s編輯同樣會(huì)觸發(fā)TextWatcher)*/public void afterTextChanged(Editable s); }

兩種情況會(huì)使TextView里面的內(nèi)容發(fā)生變化,從而通知監(jiān)聽(tīng)器,第一種就是setText()方法,第二種就是從鍵盤(pán)輸入。兩種都會(huì)調(diào)用sendBeforeTextChanged方法發(fā)送通知,如下(舉例beforeTextChanged):

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);}}// The spans that are inside or intersect the modified region no longer make senseremoveIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);}

首先,給大家介紹下Google的設(shè)計(jì)理念,說(shuō)實(shí)話,我不知道原文出自哪,這個(gè)是從某個(gè)大佬博客上摘過(guò)來(lái)的。


Google對(duì)于**“改變字符串”的設(shè)計(jì)理念就是“替換”**。如果是刪內(nèi)容,就是用空字符串替換需要?jiǎng)h除的字符串;如果是增加內(nèi)容,就是用新字符串替換空字符串。所以要先搞清楚下面幾個(gè)概念:

  • 原內(nèi)容:發(fā)生改變前TextView中的內(nèi)容;
  • 被替換內(nèi)容起點(diǎn)坐標(biāo):編輯一段內(nèi)容時(shí),有可能是直接添加新內(nèi)容,也有可能是選中一段原有內(nèi)容,用新內(nèi)容把它替換掉;
  • 被替換內(nèi)容的長(zhǎng)度:如果是直接添加新內(nèi)容,被替換內(nèi)容的長(zhǎng)度就是0;
  • 新增加的內(nèi)容:對(duì)于setText()來(lái)說(shuō),就是方法中的參數(shù),對(duì)于鍵盤(pán)輸入來(lái)說(shuō),就是鍵盤(pán)輸入的內(nèi)容
  • 再來(lái)分析這兩種情況。

    情況一:setText()

    同樣的,先看下TextView中**setText()**方法對(duì)相關(guān)事件的處理

    /*** @param text 將要設(shè)置的新內(nèi)容* @param type 內(nèi)容的設(shè)置類型(static text, styleable/spannable text, or editable text)* @param notifyBefore 是否需要觸發(fā)TextWacther的before* @param oldlen 新增加內(nèi)容的長(zhǎng)度 */ private void setText(CharSequence text, BufferType type,boolean notifyBefore, int oldlen) {mTextFromResource = false;if (text == null) {text = "";}...略...// 先過(guò)濾,再確認(rèn)是否發(fā)送TextChangeint 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;}}// char[]類型時(shí)會(huì)提前發(fā)送sendBeforeTextChanged,此處的notifyBefore即為falseif (notifyBefore) {// 通知調(diào)用TextWatcher的beforeTextChanged()方法if (mText != null) {oldlen = mText.length();sendBeforeTextChanged(mText, 0, oldlen, text.length());} else {sendBeforeTextChanged("", 0, 0, text.length());}}boolean needEditableForNotification = false;if (mListeners != null && mListeners.size() != 0) {needEditableForNotification = true;}if (type == BufferType.EDITABLE || getKeyListener() != null|| needEditableForNotification) {createEditorIfNeeded();mEditor.forgetUndoRedo();Editable t = mEditableFactory.newEditable(text); // 創(chuàng)建Editable,后面鍵盤(pán)輸入會(huì)用到相關(guān)監(jiān)聽(tīng)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);}...略...if (text instanceof Spannable && !mAllowTransformationLengthChange) {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();// 設(shè)置鍵盤(pán)輸入TextWatcher監(jiān)聽(tīng)(可以看一下ChangeWatcher源碼)sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE| (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));if (mEditor != null) mEditor.addSpanWatchers(sp);if (mTransformation != null) {sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);}...略...}...略...// 通知調(diào)用TextWatcher的onTextChanged()方法sendOnTextChanged(text, 0, oldlen, textLength);onTextChanged(text, 0, oldlen, textLength);// 通知view刷新notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);// 通知調(diào)用TextWatcher的afterTextChanged()方法if (needEditableForNotification) {sendAfterTextChanged((Editable) text);} else {// Always notify AutoFillManager - it will return right away if autofill is disabled.notifyAutoFillManagerAfterTextChangedIfNeeded();}// SelectionModifierCursorController depends on textCanBeSelected, which depends on textif (mEditor != null) mEditor.prepareCursorControllers();}

    這里可以看到,如果調(diào)用setText方法是一定會(huì)觸發(fā)TextWatcher相關(guān)事件的,所以盡量不要TextWatcher的**onTextChanged()方法中對(duì)文字進(jìn)行過(guò)濾,然后再調(diào)用setText()方法重置字符串,
    如果一定要在TextWatcher的
    onTextChanged()**方法中調(diào)用setText()方法(某些很難受的需求),注意防止死循環(huán)。因?yàn)閟etText()方法又會(huì)回調(diào)onTextChanged()方法,會(huì)形成死循環(huán)。

    情況二:鍵盤(pán)輸入

    TextView的構(gòu)造方法中,會(huì)獲取android:text屬性的值,調(diào)用setText()方法設(shè)置初始內(nèi)容。其中,就會(huì)判斷BufferType的類型,如果是EditText,就會(huì)創(chuàng)建Editable(此段邏輯見(jiàn)上面setText()源碼)。

    最終new出SpannableStringBuilder對(duì)象,SpannableStringBuilder實(shí)現(xiàn)了Editable、Appendable接口。Appendable提供了一個(gè)接口(有三個(gè)重載的):append(),用來(lái)把新內(nèi)容(來(lái)自鍵盤(pán)輸入)添加到原內(nèi)容中。所以我們?nèi)pannableStringBuilder里看看append()方法的具體實(shí)現(xiàn)。

    三個(gè)重載的接口,就有三個(gè)具體實(shí)現(xiàn),但原理都一樣,最終都會(huì)調(diào)用replace()方法。下面以其中一個(gè)append()實(shí)現(xiàn)來(lái)分析:

    // 鍵盤(pán)輸入有兩種:一種是正常輸入;另一種是先選中一段內(nèi)容,再?gòu)逆I盤(pán)輸入,新內(nèi)容會(huì)替換掉選中的內(nèi)容; // 這個(gè)方法是正常輸入時(shí)調(diào)用 public SpannableStringBuilder append(CharSequence text, int start, int end) {// length就是插入點(diǎn)的位置int length = length();// 最終都會(huì)調(diào)用replace()方法來(lái)“增加”內(nèi)容。從命名可以看出,Google對(duì)于字符串改變的設(shè)計(jì)思路就是“替換”,如果是刪內(nèi)容,就是用空內(nèi)容替換原內(nèi)容,如果是增加內(nèi)容,就是用新內(nèi)容替換某個(gè)內(nèi)容return replace(length, length, text, start, end); }public SpannableStringBuilder replace(final int start, final int end,CharSequence tb, int tbstart, int tbend) {checkRange("replace", start, end);// 與setText()一樣,都會(huì)對(duì)新增內(nèi)容進(jìn)行過(guò)濾int filtercount = mFilters.length;for (int i = 0; i < filtercount; i++) {CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);if (repl != null) {tb = repl;tbstart = 0;tbend = repl.length();}}// 由于是正常鍵盤(pán)輸入,end等于start,所以origLen等于0final int origLen = end - start;// 新增內(nèi)容的長(zhǎng)度f(wàn)inal int newLen = tbend - tbstart;if (origLen == 0 && newLen == 0 && !hasNonExclusiveExclusiveSpanAt(tb, tbstart)) {return this;}TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);// 通知TextWatcher調(diào)用beforeTextChanged()方法,邏輯跟TextView中的一樣,就不再貼代碼了sendBeforeTextChanged(textWatchers, start, origLen, newLen);boolean adjustSelection = origLen != 0 && newLen != 0;int selectionStart = 0;int selectionEnd = 0;if (adjustSelection) {selectionStart = Selection.getSelectionStart(this);selectionEnd = Selection.getSelectionEnd(this);}change(start, end, tb, tbstart, tbend);if (adjustSelection) {if (selectionStart > start && selectionStart < end) {final int offset = (selectionStart - start) * newLen / origLen;selectionStart = start + offset;setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,Spanned.SPAN_POINT_POINT);}if (selectionEnd > start && selectionEnd < end) {final int offset = (selectionEnd - start) * newLen / origLen;selectionEnd = start + offset;setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,Spanned.SPAN_POINT_POINT);}}// 通知TextWatcher調(diào)用onTextChanged()、afterTextChanged()方法。可以看到,這兩個(gè)方法是一起調(diào)用的,這點(diǎn)跟setText()有點(diǎn)細(xì)微差別,總體來(lái)說(shuō)是一樣的sendTextChanged(textWatchers, start, origLen, newLen);sendAfterTextChanged(textWatchers);sendToSpanWatchers(start, end, newLen - origLen);return this; }

    通過(guò)分析,大概可以得出如下結(jié)論:(通過(guò)鍵盤(pán)輸入的源碼分析可以確認(rèn)該結(jié)論)

    // s:原內(nèi)容 // start:被替換內(nèi)容起點(diǎn)坐標(biāo),因?yàn)閟etText()是將原內(nèi)容全部替換掉,所以起點(diǎn)是0 // count:被替換內(nèi)容的長(zhǎng)度,因?yàn)閟etText()是將原內(nèi)容全部替換掉,所以就是mText.length() // after:新增加內(nèi)容的長(zhǎng)度 public void beforeTextChanged(CharSequence s, int start, int count, int after) { }// s:發(fā)生改變后的內(nèi)容 // start:被替換內(nèi)容的起點(diǎn)坐標(biāo) // before:被替換內(nèi)容的長(zhǎng)度 // count:新增加的內(nèi)容的長(zhǎng)度 public void onTextChanged(CharSequence s, int start, int before, int count) { }// s:發(fā)生改變后的內(nèi)容 public void afterTextChanged(Editable s) { }

    總結(jié)

    • 最好使用InputFilter對(duì)字符串進(jìn)行控制、過(guò)濾。

    • 盡量不要在TextWatcher的**onTextChanged()方法中對(duì)文字進(jìn)行過(guò)濾,然后再調(diào)用setText()**方法重置字符串,效率明顯比InputFilter低

    • 如果一定要在TextWatcher的**onTextChanged()方法中調(diào)用setText()方法,注意防止死循環(huán)。因?yàn)?/strong>setText()方法又會(huì)回調(diào)onTextChanged()**方法,會(huì)形成死循環(huán)。

    • TextWatcher主要功能是進(jìn)行監(jiān)聽(tīng),從Google對(duì)該類的命名就可以看出來(lái)。。


    鳴謝

    thinkreduce
    原文鏈接: https://blog.csdn.net/u014606081/article/details/53101629

    總結(jié)

    以上是生活随笔為你收集整理的InputFilter 和 TextWatcher的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。