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设置过滤器数组的时候需要考虑过滤器的顺序。

到此这篇关于Android TextView的TextWatcher使用案例详解的文章就介绍到这了,更多相关Android TextView的TextWatcher使用内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Android TextView的TextWatcher使用案例详解的更多相关文章

  1. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  4. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  5. Ionic – Splash Screen适用于iOS,但不适用于Android

    我有一个离子应用程序,其中使用CLI命令离子资源生成的启动画面和图标iOS版本与正在渲染的启动画面完美配合,但在Android版本中,只有在加载应用程序时才会显示白屏.我检查了config.xml文件,所有路径看起来都是正确的,生成的图像出现在相应的文件夹中.(我使用了splash.psd模板来生成它们.我错过了什么?这是config.xml文件供参考,我觉得我在这里做错了–解决方法在config.xml中添加以下键:它对我有用!

  6. ios – 无法启动iPhone模拟器

    /Library/Developer/CoreSimulator/Devices/530A44CB-5978-4926-9E91-E9DBD5BFB105/data/Containers/Bundle/Application/07612A5C-659D-4C04-ACD3-D211D2830E17/ProductName.app/ProductName然后,如果您在Xcode构建设置中选择标准体系结构并再次构建和运行,则会产生以下结果:dyld:lazysymbolbindingFailed:Symbol

  7. Xamarin iOS图像在Grid内部重叠

    heyo,所以在Xamarin我有一个使用并在其中包含一对,所有这些都包含在内.这在Xamarin.Android中看起来完全没问题,但是在Xamarin.iOS中,图像与标签重叠.我不确定它的区别是什么–为什么它在Xamarin.Android中看起来不错但在iOS中它的全部都不稳定?

  8. 在iOS上向后播放HTML5视频

    我试图在iPad上反向播放HTML5视频.HTML5元素包括一个名为playbackRate的属性,它允许以更快或更慢的速率或相反的方式播放视频.根据Apple’sdocumentation,iOS不支持此属性.通过每秒多次设置currentTime属性,可以反复播放,而无需使用playbackRate.这种方法适用于桌面Safari,但似乎在iOS设备上的搜索限制为每秒1次更新–在我的情况下太慢了.有没有办法在iOS设备上向后播放HTML5视频?解决方法iOS6Safari现在支持playbackRat

  9. 使用 Swift 语言编写 Android 应用入门

    Swift标准库可以编译安卓armv7的内核,这使得可以在安卓移动设备上执行Swift语句代码。做梦,虽然Swift编译器可以胜任在安卓设备上编译Swift代码并运行。这需要的不仅仅是用Swift标准库编写一个APP,更多的是你需要一些框架来搭建你的应用用户界面,以上这些Swift标准库不能提供。简单来说,构建在安卓设备上使用的Swiftstdlib需要libiconv和libicu。通过命令行执行以下命令:gitclonegit@github.com:SwiftAndroid/libiconv-libi

  10. Android – 调用GONE然后VISIBLE使视图显示在错误的位置

    我有两个视图,A和B,视图A在视图B上方.当我以编程方式将视图A设置为GONE时,它将消失,并且它正下方的视图将转到视图A的位置.但是,当我再次将相同的视图设置为VISIBLE时,它会在视图B上显示.我不希望这样.我希望视图B回到原来的位置,这是我认为会发生的事情.我怎样才能做到这一点?编辑–代码}这里是XML:解决方法您可以尝试将两个视图放在RelativeLayout中并相对于彼此设置它们的位置.

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部