android的TextView的TextWatcher使用

article/2025/10/28 7:48:24
TextWatcher是一个文本变化监听接口,定义了三个接口,分别是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的回调方法中实现自己的业务逻辑,这三个参数代表了TextView文本发生变化的三个阶段。

beforeTextChanged(CharSequence s, int start, int count, int after)方法是TextView在文本改变之前调用,并且传入四个参数。
CharSequence s参数表示当前TextView内部的mText成员变量,实际上就是当前显示的文本;
int start参数表示需要改变的文字区域的起点,即选中的文本区域的起始点;
int count参数表示需要改变的文字的字符数目,即选中的文本区域的字符的数目;
int after参数表示替换的文字的字符数目。
特别的,当TextView删除文本的时候,after的值为0,此时TextView使用用空字符串代替需要改变的文字区域来达到删除文字的目的。
图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参数表示改变的文字区域的起点;
int before参数表示改变的文字区域在改变前的旧的文本长度,即选中文字区域的文本长度;
int after参数表示改变的文字区域在修改后的新的文本长度。
特别的,当TextView添加文本的时候,before 的值为0,此时相当于TextView将空的字符区域用新的文本代替。

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的方法创建一个可编辑的对象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方法,该方法是一个工厂方法,创建一个 SpannableStringBuilder对象。
代码  1.2  mEditableFactory的newEditable方法
public Editable  newEditable(CharSequence source) {
    return new SpannableStringBuilder(source) ;
}

SpannableStringBuilder实现了CharSequence, GetChars, Spannable, Editable,Appendable, GraphicsOperations的接口,内部的string是可以变化的。不过咱们修改afterTextChanged 中的参数s的时候, 需要注意循环调用的潜在风险,因为SpannableStringBuilder会在自己内部保存TextView的mChangeWatcher对象,代码1.3描述了设置过程。如代码所示,text通过setSpan方法添加了mChangeWatcher对象,以监听整个text的变化,并且做回调。当text的内容发生变化的时候,会通过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 ==  nullmChangeWatcher 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的修改,那么就会形成无限循环调用。
类似TextView的setText的方法也会在相应的时机通知所有注册的TextWatcher调用响应的三个接口,如果咱们在TextWatcher的三个接口中调用TextView的setText方法也会导致无限循环调用。图1.3描述了无限调用的示例。

图1.3 无限调用示例


但是有时候我们可能 需要根据业务需求更改显示的文本,比如过滤不必要的字符,过滤非法的文字,添加必要的结束字符等。这个时候我们有时候会给TextView添加一个TextWatcher,然后在某个接口回调中根据传入的参数修改将要显示的文本,这可以满足咱们的业务需求,不过有可能会导致无限循环调用。针对这个问题,咱们可以 通过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参数是即将替换选中字符区域的字符串对象;
start参数表示source的起始位置;
end参数表示source的终止位置。通过source,start,end这三个参数可以描述出替换选中字符区域的新的字符串。
dest表示选中文字区域的文本对象,TextView的setText方法调用filter方法时,传入的dest为EMPTY_SPANNED,修改文字时,传入的
dest为TextView中保存的mText;
dstart表示选中的文字区域的起始位置;
dend表示选中的文字区域的终止位置。
该方法的返回值是用来替换source作为新的替换文本。
图1.4是有一个filter实例,描述了filter的六个参数的含义。
图1.4 filter实例
 
InputFilter接口内部提供了两个子类,分别是AllCaps和LengthFilter。
AllCaps是将文本中的小写字符全部转为大写字符的过滤器,通过该过滤器,TextView能将输入文本中的小写字符转为大写字符,然后显示出来;
LengthFilter是删除掉超过长度maxLength的字符的过滤器。在xml中配置了maxLength之后,TextView在创建实例的时候,会生成一个LengthFilter的过滤器,以便达到限定显示字符长度的功能。

咱们自己也可以定义满足咱们业务需求的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 11] ;
    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监听TextView文本变化的三个时机,并在回调函数中做相应的处理;
2.但是在回调函数中处理业务的时候,需要注意不要调用该TextView的setText方法,否则会发生无限循环调用;
3.在TextWatcher 的回调接口afterTextChanged方法中修改参数s的时候,也要注意在修改了s之后,会触发文本变化,导致TextView中所有注册的TextWatcher再次回调自己的的三个回调函数,此时需要预防无限循环调用的发生。
4.如果需要修改传入文本,那么可以实现InputFilter接口,然后给TextView添加符合业务需求的过滤器;
5.给TextView添加自定义的过滤器的时候,需要注意使用setFilter方法设置过滤器会覆盖掉TextView原本的过滤器,如果不想舍弃TextView原本的过滤器,那么需要将原本的过滤器添加到新的过滤器数组中。
6.TextView使用InputFilter过滤器数组的时候,是从第一个过滤器到最后一个过滤器依次使用的,因此咱们给TextView设置过滤器数组的时候需要考虑过滤器的顺序。






http://chatgpt.dhexx.cn/article/wbJWXfBV.shtml

相关文章

Android 文本监听接口TextWatcher详解

TextWatcher是一个用来监听文本变化的接口&#xff0c;使用该接口可以很方便的对可显示文本控件和可编辑文本控件中的文字进行监听和修改 TextWatcher接口中定义了三个方法&#xff1a; public void beforeTextChanged(CharSequence s, int start, int count, int after) {} 该…

makefile中的两个函数(wildcard和patsubst)

(1) wildcard函数 作用是查找指定目录下指定类型的文件&#xff0c;并最终返回一个环境变量&#xff0c;需要用$取值赋值给另一个环境变量&#xff01;该函数只有一个参数&#xff0c;如取出当前目录下的所有.c文件&#xff0c;并赋值给allc普通变量&#xff1a; allc$(wildc…

linux_makefile文件编写,基本规则、工作原理、模式规则,wildcard函数、patsubst函数

接上一篇&#xff1a;linux_GDB调试学习(调试运行、多文件设置断点)_C/C程序调试 本次来分享linux下的makefile文件的编写&#xff0c;开始上菜&#xff1a; 目录 1.makefile文件的命名规则2.用途3.基本规则3.1.用例一4.工作原理4.1.用例二 5.makefile的执行5.1.用例三 6.make…

makefile wildcard patsubst使用小结

这是当前的目录&#xff1a; objs main.o g_a.o g_b. target : $(objs)gcc -o target $(objs) main.o : g_a.h g_b.hgcc -c main.c g_a.o : g_a.hgcc -c g_a.c g_b.o : g_b.hgcc -c g_b.c.PHONY : cleansrc $(wildcard *.c) obj $(patsubst %.c,%.o,$(src)) #obj $(patsu…

Makefile中patsubst函数使用方法

Makefile中patsubst函数使用方法 patsubst函数用于将文件模式进行替换。 一、作用 替换文件后缀。 二、格式 $(patsubst 原模式&#xff0c; 目标模式&#xff0c; 文件列表) 三、实例 图1 源文件结构 图2 patsubst实例

从零开始学习makefile(5)makefile中patsubst的作用

目录 介绍 text pattern与replacement 返回值 通配符% 示例1 例子2 介绍 patsubst是pattern substitute的缩写。其用法是&#xff1a; $(patsubst pattern,replacement,text) text text是将要被处理的字符串。首先&#xff0c;patsubst以空格为分隔符&#xff0c;将te…

list_for_each_entry和list_for_each_entry_safe

看内核代码都会发现&#xff0c;内核链表的操作常用的二个宏list_for_each_entry和list_for_each_entry_safe 循序渐进&#xff0c;先从最底层的函数container_of函数说起&#xff0c;其内核定义如下&#xff1a; 先看offsetof宏&#xff0c;根据优先级的顺序&#xff0c;最里面…

Map中的entry,entrySet,keySet的区别和用法

Map中的元素是以键值对的形式存在的&#xff0c;即key-value。key是唯一的不能重复&#xff0c;但value可以重复。 Map.keySet(): 这个方法返回的就是map集合中所有键Key的一个Set集合。如Map<Integer&#xff0c;String> 中put(1, “张三”)&#xff0c;put(2, “李四”…

Map.Entry与entrySet与entry,getKey()与entry.getValue()的用法

直接上代码 实体类 Data AllArgsConstructor NoArgsConstructor public class SinglePressureResultDTO {private Integer Times; private Integer SCU_number; private Boolean Intervention; private Long startTime_low; private Long low_time; private Long start…

Map集合的entrySet()方法

之前学习集合的时候要通过迭代器来迭代的时候最难得就是map集合得迭代&#xff0c;一直也不太明白&#xff0c;今天总算搞懂了 首先我们看什么容器才能迭代 根据API我们得知是对所有collection迭代的集合&#xff0c;那么已知的collection容器有哪些 我把常用的标出了&#xf…

keySet()和entrySet()

一、描述 keySet()和entrySet()&#xff0c;是Map集合中的两种取值方法。 与get(Object key)相比&#xff0c;get(Object key)只能返回到指定键所映射的值&#xff0c;不能一次全部取出。而keySet()和entrySet()可以。 Map集合中没有迭代器&#xff0c;Map集合取出键值的原理…

Java高级之HashMap中的entrySet()方法

基本使用 entrySet()方法得到HashMap中各个键值对映射关系的集合。 然后Map.Entry中包含了getKey()和getValue()方法获取键和值。 示例&#xff1a; public class Demo {public static void main(String[] args) {Map<String, String> map new HashMap<>();ma…

KeySet和EntrySet区别

场景&#xff1a; keySet()和entrySet()&#xff0c;是Map集合中的两种取值方法。 与get(Object key)相比&#xff0c;get(Object key)只能返回到指定键所映射的值&#xff0c;不能一次全部取出。而keySet()和entrySet()可以。 Map集合中没有迭代器&#xff0c;Map集合取出键…

Map中entrySet()方法使用

public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。 就是返回一个集合&#xff0c;集合里存放的是对象&#xff0c;创建对象的类有两个属性&#xff0c;分别是 键和值 也即键值对。 其中Entry是属于Map的静态内部类&#x…

glPushMatrix 和glPopMatrix图解 ----求别笑

猜想&#xff1a; openGL在绘制场景时的一般用法是&#xff1a; 首先在函数的开始处用glLoadIdentity()设置当前的矩阵为单位矩阵。 然后在函数中用glPushMatrix()和glPopMatrix()函数进行操作&#xff1a; 根据实践判断&#xff1a; 即这两者是分开的&#xff0c;并不是当前…

OpenGL的glPushMatrix和glPopMatrix矩阵栈顶操作函数详解

OpenGL中图形绘制后&#xff0c;往往需要一系列的变换来达到用户的目的&#xff0c;而这种变换实现的原理是又通过矩阵进行操作的。opengl中的变换一般包括视图变换、模型变换、投影变换等&#xff0c;在每次变换后&#xff0c;opengl将会呈现一种新的状态&#xff08;这也就是…

为什么调用glPushMatrix()和glPopMatrix()

2019独角兽企业重金招聘Python工程师标准>>> 今天忽然感悟到为什么在进行变换之前要用glPushMatrix();这个函数&#xff0c;而在变换完毕后有用glPopMatrix()这两个函数了,赶紧记下来&#xff1a; 我们在变换坐标的时候&#xff0c;使用的是glTranslatef(),glRotaef…

opengl入门记录--glPushMatrix和glPopMatrix原理

glPushMatrix、glPopMatrix操作事实上就相当于栈里的入栈和出栈。 很多人不明确的可能是入的是什么&#xff0c;出的又是什么。 比如你当前的坐标系原点在你电脑屏幕的左上方。如今你调用glPushMatrix&#xff0c;然后再调用一堆平移、旋转代码等等&#xff0c;然后再绘图。那…

OpenGL编程指南9:裁剪平面+glPushMatrix和glPopMatrix矩阵栈顶操作

1.任意裁剪平面 Opengl中&#xff0c;除了视景体的立方体裁剪平面之外&#xff0c;另外还可以额外指定多达6个裁剪平面&#xff0c;对视景体做进一步限制。每一个平面都由平面公式定义&#xff1a;AxByCzD 0.裁剪平面的指定通过函数&#xff1a;glClipPlane(GLenum plane,cons…

使用glPushMatrix和glPopMatrix的原因

转自 百度百科 glPushMatrix 函数将当前矩阵堆栈推送&#xff0c;通过一个&#xff0c;复制当前矩阵。 这就是后 glPushMatrix 的调用堆栈的顶部矩阵是它下面的相同的。 1. 原理讲解 终于明白为什么使用glPushMatrix()和glPopMatrix()的原因了。将本次需要执行的缩放、平移等操…