博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
TextWatcher的使用及源码解析
阅读量:7197 次
发布时间:2019-06-29

本文共 6340 字,大约阅读时间需要 21 分钟。

TextWatcher 定义

官方: 当文本发生变化时会触发接口回调. 一般应用场景为自定义输入框用于对用户的输入进行限制,比如只能输入英文,数字等等

TextWatcher使用

TextWatcher 为接口,通常配合 TextViewEditText进行使用,需要我们自行实现接口方法并且通过textview.addTextChangedListener(textWatcher) 为对应的view添加监听

方法解读

public interface TextWatcher extends NoCopySpan {    /**      * 文本改变前回调方法     * 改变完成前的字符串 s 的 start 位置开始的 count 个字符将会被 after 个字符替换     * index 为 start 的 after 个字符被删除后,新的字符串插入到 start 位置     */    public void beforeTextChanged(CharSequence s, int start, int count, int after);    /**      * 文本改变完成回调方法     * 改变完成后的字符串 s 的 start 位置开始的 before 个字符已经被 count 个字符替换     */       public void onTextChanged(CharSequence s, int start, int before, int count);    /**      * 文本改变后的回调方法,在 onTextChanged 方法后调用     * s为改变完成的字符串     */    public void afterTextChanged(Editable s);}复制代码

触发条件

  1. 键盘文本输入 键盘输入直接对文本进行修改,包括且不限于文本粘贴,文本修改,新增文本等等.

  2. setText(newStr)TextView 进行文本设置,不局限于 setText() 方法,包括 replace() , apeend() 等等直接对文本进行修改操作的方法,都会触发 TextWatcher 的回调.

  3. 修改回调方法中的 Editable 对象 Editable 属于可变字符串,对其进行字符串操作不会产生新的对象,等同于对 TextView 持有的字符串进行操作.

源码分析

添加监听

textview.addTextChangedListener(textWatcher)

// TextView.java    public void addTextChangedListener(TextWatcher watcher) {        if (mListeners == null) {            mListeners = new ArrayList
(); } mListeners.add(watcher); }复制代码

通过源码可知添加监听是将所有的已添加监听通过 List 进行存储,由此可以得知可以对同一 TextView 设置多个 TextWatcher

扩展: removeTextChangedListener(TextWatcher watcher) 可以移除指定 TextWatcher, 没有类似 removeAllXXX 这种移除全部 TextWatcher 的方法.

setText(newStr)

setText(newStr) 是一种特殊的改变文本的方式,无论 newStr 是什么,都会完全覆盖原值

// TextView.javaprivate void setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen) {    ...    if (mText != null) {        oldlen = mText.length();        sendBeforeTextChanged(mText, 0, oldlen, text.length());    } else {        sendBeforeTextChanged("", 0, 0, text.length());    }    ...}private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {    if (mListeners != null) {        final ArrayList
list = mListeners; final int count = list.size(); for (int i = 0; i < count; i++) { list.get(i).beforeTextChanged(text, start, before, after); } } ...}复制代码

通过源码可知如果 newStr 不为null,那么 beforeTextChanged的第一个参数 text 始终为原字符串.第二个参数 start 始终为0, setText() 的本质是完全替换现有字符串,从起始位置0进行替换.第三个参数 before 为现有的字符串长度,因为要完全替换所有的字符.第四个参数 after 为新字符串的长度.

// TextView.javaprivate void setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen) {    ...    sendOnTextChanged(text, 0, oldlen, textLength);    ...    if (needEditableForNotification) {        sendAfterTextChanged((Editable) text);    }    ...}复制代码

在对持有字符串进行新的赋值后会调用剩余2个回调方法,如果设置了 TextWatcher ,那么needEditableForNotification 的值就为true,意味着只要对 TextView 设置了输入监听,那么 afterTextChanged() 一定能够被回调. 调用回调方法的详细操作就不列出,与 beforeTextChanged 一样,遍历所有已设置的 TextWatcher 之后循环调用

Editable

前面已经说过触发回调的方式的其中一个方式: 直接对 afterTextChanged(Editable s) 回调方法返回的Editable 对象进行操作

Editable是一种特殊的字符串,对其做任何操作,其内存地址不会发生变化,始终指向原内存地址,不同于常规 String 类型每次赋值都会在内存中开辟新的内存进行存储.

下面以append()方法为例简要概括修改Editable触发监听的流程

  1. 调用 Editable 对象的 append() 方法进行字符串拼接.
  2. 获取自身绑定的 TextWatcher
  3. 类比 setText() 方法的回调触发流程,在对应的时机触发回调

当然关键在于第二步 >>>>>> 获取自身绑定的 TextWatcher

  1. setText() 时获取 TextView 的内部类 ChangeWatcher 对象,并绑定给自身持有的字符串对象上
  2. ChangeWatcher 实现了 TextWatcher 的回调方法,并在回调方法中调用 TextViewsendBeforeTextChanged 等方法进行遍历调用.

下面通过源码进行流程分析

Editableappend() 方法解析

//TextView.java...if (mListeners != null && mListeners.size() != 0) {    needEditableForNotification = true;}if (type == BufferType.EDITABLE || getKeyListener() != null        || needEditableForNotification) {    createEditorIfNeeded();    mEditor.forgetUndoRedo();    Editable t = mEditableFactory.newEditable(text);    text = t;    setFilters(t, mFilters);    InputMethodManager imm = InputMethodManager.peekInstance();    if (imm != null) imm.restartInput(this);}...复制代码

这部分代码在 TextViewsetText() 方法中, 当我们需要监听的控件是 EditText 时,在其构造方法内直接把type设置成了BufferType.EDITABLE ,当我们为其设置了监听时, needEditableForNotification 的值也会被设置成true.

函数体内,我们重点关注Editable t = mEditableFactory.newEditable(text)

// Editable.Factorypublic Editable newEditable(CharSequence source) {     return new SpannableStringBuilder(source);}复制代码

通过工厂方法返回一个实现了Editable接口的对象SpannableStringBuilder,并且当我们进行append()操作时,经历以下流程

// SpannableStringBuilder.javapublic SpannableStringBuilder append(CharSequence text) {    int length = length();    return replace(length, length, text, 0, text.length());}...public SpannableStringBuilder replace(final int start, final int end,            CharSequence tb, int tbstart, int tbend) {    ...    TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);    sendBeforeTextChanged(textWatchers, start, origLen, newLen);    ...    sendTextChanged(textWatchers, start, origLen, newLen);    sendAfterTextChanged(textWatchers);    ...}复制代码

是不是很眼熟? 在setText()时的流程也十分相似, 获取绑定的TextWatcher -> sendBeforeTextChanged() -> 字符串修改 -> sendTextChanged() -> sendAfterTextChanged()

在对Editable对象进行调用相关方法修改而不是直接赋值时,将获取与其绑定的TextWatcher并遍历调用相关方法,那么TextWatcher是何时与其绑定的呢?

Editable绑定TextWatcher

// TextView.setTextif (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE        | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));复制代码

ChangeWatcherTextView的内部类,在 setText方法中获取内部类实例并且设置给Editable对象

此时我们发现 Editable里获取的TextWatcher并不是 TextView里面我们设置的监听,而是获取到了TextView的内部类实例,下面我们再看ChangeWatcher

private class ChangeWatcher implements TextWatcher, SpanWatcher {    private CharSequence mBeforeText;    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);    }}复制代码

我们可以看出,ChangeWatcher 是实现了TextWatcher的三个方法,并且在方法体内分别调用了TextView的相关方法,而TextView中的方法又会遍历TextWatcher 的list去分别调用这3个回调

那么Editable调用append()方法进行修改时的流程可以简单理解为

  1. 调用replace()
  2. 获取绑定TextWatcher
  3. 调用TextWatcher实例方法
  4. 其中的ChangeWatcher对象的方法会去调用TextView的方法
  5. 遍历TextView的监听器,触发回调

转载地址:http://twakm.baihongyu.com/

你可能感兴趣的文章
Deis发布1.4版本,支持Microsoft Azure
查看>>
别了MongoDB?
查看>>
Druid 在有赞的使用场景及应用实践
查看>>
四种方式主导你的第一个敏捷项目
查看>>
为什么区块链永远不会干掉数据库
查看>>
微软超过苹果 成为全球第一大市值公司
查看>>
linux 内核移植(六)——C语言启动部分分析(一)
查看>>
Unix高级环境编程
查看>>
2017 Transformation Days |国际数据处理大咖SNP首次来华
查看>>
揭秘天猫未来小店背后的NEC the WISE技术群
查看>>
CentOS 6.5上安装并配置FastDFS
查看>>
流程DEMO-借款申请
查看>>
算法——分支限界法
查看>>
静态方法中不能new内部类的实例对象的总结
查看>>
PHP源码包编译安装错误及解决方法汇总
查看>>
RIP 路由汇总实验
查看>>
Git版本恢复命令reset(转载)
查看>>
运维第八单元
查看>>
实战 MDT 2012(二)---制作模板机
查看>>
学会这些, 让你的服务器远离攻击影响
查看>>