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 == 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的修改,那么就会形成无限循环调用。

类似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 + 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监听TextView文本变化的三个时机,并在回调函数中做相应的处理;

2.但是在回调函数中处理业务的时候,需要注意不要调用该TextView的setText方法,否则会发生无限循环调用;

3.在TextWatcher 的回调接口afterTextChanged方法中修改参数s的时候,也要注意在修改了s之后,会触发文本变化,导致TextView中所有注册的TextWatcher再次回调自己的的三个回调函数,此时需要预防无限循环调用的发生。

4.如果需要修改传入文本,那么可以实现InputFilter接口,然后给TextView添加符合业务需求的过滤器;

5.给TextView添加自定义的过滤器的时候,需要注意使用setFilter方法设置过滤器会覆盖掉TextView原本的过滤器,如果不想舍弃TextView原本的过滤器,那么需要将原本的过滤器添加到新的过滤器数组中。

6.TextView使用InputFilter过滤器数组的时候,是从第一个过滤器到最后一个过滤器依次使用的,因此咱们给TextView设置过滤器数组的时候需要考虑过滤器的顺序。

转载于:https://my.oschina.net/JiangTun/blog/918113

android的TextView的TextWatcher使用相关推荐

  1. Android自动调整TextView的大小

    In this tutorial, we'll look at how we can implement TextView such that it auto resizes itself based ...

  2. android 保存textview,为什么没有TextView(带ID)会自动保存它的状态?

    我的理解是所有具有ID的标准视图都应该自动保存它们的状态,并且在尝试这个例子时我发现它非常令人困惑. 我只有1个活动和主要布局如下所示. 当我通过单击按钮更改TextView的文本,然后旋转屏幕时,T ...

  3. android 自定义view文字不齐,Android 解决TextView排版参差不齐的问题

    Android 解决TextView排版参差不齐的问题 在app中,展示数据时,里面有汉字.数字.特殊字符时,由于全角.半角问题导致TextView参差不齐.在网上找了许多,半角转全角并没什么用,还有 ...

  4. Android 解决Android的TextView和EditText换行问题

    Android 解决Android的TextView和EditText换行问题 参考文章: (1)Android 解决Android的TextView和EditText换行问题 (2)https:// ...

  5. 让android的TextView可以滚动

    让android的TextView可以滚动 android 我想要在一个文本视图里显示一段文本,但是文字太多,一屏显示不下.我需要让我的TextView可以滚动.我应该怎么做,这是我的代码. fina ...

  6. android 字符串,textview

    Android - CharSequence和String的比较和转换 Android自定义TextView边框颜色(动态改变边框颜色以及字体颜色) Android TextView加中划线,下划线 ...

  7. Android 设置TextView字体加粗

    今天,简单讲讲Android里如何设置TextView字体加粗. 不废话了,用过多次,还是没记住.直接上代码. 1.布局文件中这样设置即可: XML/HTML代码 android:textStyle= ...

  8. android 决TextView中MaxLines与ellipsize=end冲突问题

    今天,讲讲如何在多行文本后显示省略号. TextView控件有一个属性是ellipsize,指的是当文字内容长度超过TextView大小时显示问题,一般情况下我们都是用省略号表示,常用的情况有以下四种 ...

  9. Android的TextView在显示文字的时候,如果有段中文有英文,有中文,有中文标点符号,你会发现,当要换行的时候遇到中文标点, 这一行就会空出很多空格出来...

    一.问题描述: Android的TextView在显示文字的时候,如果有段中文有英文,有中文,有中文标点符号,你会发现,当要换行的时候遇到中文标点, 这一行就会空出很多空格出来.原因是: 1) Tex ...

最新文章

  1. Android地图 总于实现了!
  2. CentOS安装高版本gcc
  3. win7html文件,教你win7浏览器打不开本地html文件格式的解决方法
  4. 服务器监控系统的介绍,客户服务系统服务器监控系统
  5. 开机未发现nvidia控制面板_Nvidia控制面板打不开,怎么办?
  6. HTML 调用打印机打印指定区域
  7. HUAWEI nova 青春版发布会现场探秘 堪称圈内大惊喜
  8. java后端和web_从java和web角度分析前端好还是后端好
  9. 计算机室在初中英语教学中的应用,信息技术在初中英语教学中的应用探析
  10. 蜂鸣器、风扇、震动马达
  11. mysql 8.0 导入文件_MySQL8.0实验-从本地文件导入数据
  12. 【调剂】东北石油大学计算机科学与技术专业接收调剂
  13. 项目03--当当网源码解读
  14. jdbc之oracle
  15. 关于maven仓库存在jar包,工程引用却找不到
  16. C语言 数组插入 – 插入法排序(顺序版)
  17. VSphere系列教程(一):ESXI 6.5 的安装和使用
  18. 使用阿里云部署Web项目后无法通过浏览器访问
  19. 【C语言入门】已知10个学生的5门课程的成绩,将其存入一个二维数组,求每个学生的总成绩和平均成绩。
  20. Centos 6/7安装Torque(单节点)

热门文章

  1. Mirai QQ机器人(开源)
  2. 开源夏令营《基于HackRF开发GPS信号仿真模拟器》开题报告
  3. Linux文件打开函数open()
  4. 解决IDEA JavaWeb项目启动运行后出现404错误
  5. 计算 1+2(2次方)+3(3次方)+...+n(n次方)的值
  6. 3.MySQL数据类型
  7. python上机试题
  8. python科学计算和可视化编程
  9. Python学习手册--第六部分(类)
  10. 微信小程序直播如何开发