android TextWatcher 学习

1.简介

主要用来监听用户输入,然后剪裁输入。

比如输入框只能输入8个字节的内容,就可以用TextWatcher来实现。

public interface TextWatcher extends NoCopySpan {
    /**
     * This method is called to notify you that, within <code>s</code>,
     * the <code>count</code> characters beginning at <code>start</code>
     * are about to be replaced by new text with length <code>after</code>.
     * It is an error to attempt to make changes to <code>s</code> from
     * this callback.
     */
    public void beforeTextChanged(CharSequence s, int start,
                                  int count, int after);
    /**
     * This method is called to notify you that, within <code>s</code>,
     * the <code>count</code> characters beginning at <code>start</code>
     * have just replaced old text that had length <code>before</code>.
     * It is an error to attempt to make changes to <code>s</code> from
     * this callback.
     */
    public void onTextChanged(CharSequence s, int start, int before, int count);

    /**
     * This method is called to notify you that, somewhere within
     * <code>s</code>, the text has been changed.
     * It is legitimate to make further changes to <code>s</code> from
     * this callback, but be careful not to get yourself into an infinite
     * loop, because any changes you make will cause this method to be
     * called again recursively.
     * (You are not told where the change took place because other
     * afterTextChanged() methods may already have made other changes
     * and invalidated the offsets.  But if you need to know here,
     * you can use {@link Spannable#setSpan} in {@link #onTextChanged}
     * to mark your place and then look up from here where the span
     * ended up.
     */
    public void afterTextChanged(Editable s);
}

想要了解方法的参数什么意思,把注释看一下,然后把参数全部打印一遍就行。

这三个方法很简单,值得注意的地方:

每当输入一次,就会调用一次 before-on-after。每当调用setText("xxx")也会如此,那么在on方法中调用setText("xxx")就会形成递归,然后就可能死循环。

但毕竟监听这个就是要修改内容的,根据源码注释,可以在after中修改字符。

2.使用

val input1 = findViewById<EditText>(R.id.input1)
input1.addTextChangedListener(Watcher1(input1, 8, TAG))

先简单尝试,限制8个数字或字母

class Watcher1(private val editText: EditText, private val limit: Int, private val TAG: String): TextWatcher {
    private var suitable = true
    private val what = "luo"
    private val editable = editText.editableText
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        Log.d(TAG, "beforeTextChanged")
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        Log.d(
            MainActivity.TAG,
            "onTextChanged: s = $s, start = $start, before = $before, count = $count"
        )
        s?.let {
            suitable = s.length <= limit
            if (!suitable) {
                editable.setSpan(what, start, start + count, 1)
            }
        }
    }

    override fun afterTextChanged(s: Editable?) {
        Log.d(TAG, "afterTextChanged")
        if (!suitable) {
            s?.let {
                // 注意substring的参数范围
                Log.d(TAG, "afterTextChanged: ${editable.getSpanStart(what)}")
                val start = s.substring(0, editable.getSpanStart(what))
                Log.d(TAG, "afterTextChanged: start =  $start")
                val end = s.substring(editable.getSpanEnd(what), s.length)
                Log.d(TAG, "afterTextChanged: end = $end")
                val text = start + end
                // 会继续调用before、on、after
                editText.setText(text)
                editText.setSelection(editable.getSpanStart(what))
            }
        }
    }
}

思路:在on中,字符串是已经被修改了的,所以在on中判断,如果字符串不符合要求,就记录这次的输入详情,然后在after中截取字符串。

值得注意的是,如果你使用了after回调的参数 "s",也许会有bug,因为after中调用setText(),就会形成递归:因为s是第一次的,还未修改,调用setText()修改后会有第二次after,第二次的参数才是真正想要的。所以最好是不用这个参数,而是使用editText的get方法。

混合输入时,限制长度

由于数字、汉字、表情的byte各不相同。如果我们想要保证输入的byte总数不超过定值,那就需要监听输入,然后剪裁。

class Watcher2(private val editText: EditText, private val limit: Int, private val TAG: String) :
    TextWatcher {
    private val change = IntArray(2)
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        Log.d(TAG, "beforeTextChanged")
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        Log.d(TAG, "onTextChanged s = $s, start = $start, before = $before, count = $count")
        if (!suitable(s.toString())) {
            change[0] = start
            change[1] = start + count
        }
    }

    override fun afterTextChanged(s: Editable?) {
        Log.d(TAG, "afterTextChanged")
        if (!suitable(s.toString())) {
            s?.let {
                // 注意substring的参数范围
                val start = s.substring(0, change[0])
                Log.d(TAG, "afterTextChanged: start =  $start")
                val end = s.substring(change[1], s.length)
                Log.d(TAG, "afterTextChanged: end = $end")
                val text = start + end
                // 会继续调用before、on、after
                editText.setText(text)
                editText.setSelection(change[0])
                handler.removeCallbacks(runnable)
                handler.postDelayed(runnable, 100)
            }
        }
        Log.d(TAG, "afterTextChanged: s = $s")
    }

    /**
     * Determine whether the current input is legal
     */
    private fun suitable(str: String): Boolean {
        val size = str.toByteArray().size
        Log.d(TAG, "suitable: size = $size")
        return size <= limit
    }

    private val handler = object : Handler(Looper.getMainLooper()) {}
    private val runnable =
        Runnable {
            Toast.makeText(editText.context, "限制 $limit byte!", Toast.LENGTH_SHORT).show() }
}

在第一个案例中,我使用setSpan来记录输入详情,在这个案例中有点不好使,主要原因是长度不同的。

在after中截取了字符串后记得把输入光标挪个位置。

3.总结

1.弄清楚方法里的参数

2.根据需求,在on中记录输入,在after中截取字符串

3.避免无限递归

代码icon-default.png?t=M4ADhttps://gitee.com/luoccxyz/text-watcher-test

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值