富文本开发那些事(三): 特殊文本如何不计入富文本最大限制长度

1,592 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第21天,点击查看活动详情

本篇文章将是介绍Android富文本开发系列的第三篇文章,本篇文章主要介绍的内容为:特殊文本如何不计入富文本最大限制长度。

历史文章

富文本开发那些事:如何一次性删除/跳过一段特殊文本,比如@xxx?

富文本开发那些事(二):实现特殊文本的点击和长按监听

问题场景

不知道大家在平常富文本开发中,是否面临过这种问题:

富文本有最大字符输入限制,但是图片、右上右下的角标等特殊文本要求不能计入富文本的最大字符限制长度中。

这个我们就得拿InputFilter动刀了,标准的限制最大长度的过滤器代码如下:

class MaxLengthFilterSpecialText: InputFilter {

    companion object {
        const val MAX_LENGTH = 1000
    }

    override fun filter(
        source: CharSequence,
        start: Int,
        end: Int,
        dest: Spanned,
        dstart: Int,
        dend: Int
    ): CharSequence {

        val allLength = source.length + dest.length
        var result = source

        if (allLength > MAX_LENGTH) {
            result = result.substring(0, MAX_LENGTH - allLength)
        }

        return result
    }
}

核心就是filter()方法,这里我们主要介绍两个最关键的参数sourcedest:

假如当前输入框中已存在的文本为Hello,现在我要输入World内容,此时source的内容就是Worlddest的内容就是Hello

假如过滤ImageSpan对应特殊文本长度

EditText中标识富文本的就是各种Span了,当然我们也可以自定义Span,假如我们当前过滤掉图片不计入当前最大字符限制的长度中,图片的Span就是ImageSpan了,所以做法如下:

我们只需要在filter()方法中判断sourcedest是否包含ImageSpan,并从最大长度中减去对应ImageSpan的渲染的起始区间即可。

override fun filter(
    source: CharSequence, ...
    dest: Spanned, ...
): CharSequence {

    var allLength = source.length + dest.length
    var result = source
    //关键代码,过滤掉ImageSpan
    for (content in arrayOf(source, dest)) {
        if (content !is Spanned || content.isEmpty()) {
            continue
        }
        val spans = content.getSpans<ImageSpan>()
        spans.forEach {
            allLength -= content.getSpanEnd(it) - content.getSpanStart(it)
        }
    }
    
    if (allLength > MAX_LENGTH) {
        result = result.substring(0, MAX_LENGTH - allLength)
    }
    return result
}

上面代码一目了然,对于sourcedest参数对应的内容分别判断是否包含ImageSpan,包含的话就去掉该ImageSpan渲染的区间范围。

超出限制长度提示语

一般而言,当输入的文本长度超过设置的富文本最大字符限制时,一般都会给弹个toast给个提示,同样也可以在过滤器中进行处理。

提示语可以通过类的构造参数传递过来:

class MaxLengthFilterSpecialText(vararg args: Class<*>, private val mTip: String)

当然也可以直接给mTip一个默认通用的提示语。

接下来在上面的重写的filter方法最后,判断到if (allLength > MAX_LENGTH)时,直接弹出传入的提示语即可。

if (allLength > MAX_LENGTH) {
    result = result.substring(0, MAX_LENGTH - allLength)
    Toast.make(mApplication, mTip, Toast.LENGTH_SHORT).show()
}

这个地方可以对弹toast提示进行一个建议封装,比如:

fun String.toast() {
    Toast.makeText(this@MainApplication, this, Toast.LENGTH_SHORT).show()
}

这样直接在上面代码中直接调用mTip.toast()弹出提示语即可。

富文本错位问题

富文本开发中,碰到过这样一个问题,比如我在富文本内容的中间插入了一个ImageSpan,假设这个图片的大小为 1080*100,比如发生了下面两种情况:

  • 手动从富文本中删除该ImageSpan以及对应的渲染文本;

  • 或者移除原来的ImageSpan替换成另一个ImageSpan,新的图片大小为1080*50

这就很容易导致文本错位或者被其他图片覆盖遮挡的问题,具体的原因不太清楚,但是解决的方法很简单:

fun EditText.resetTextAndSelectPos() {
    val pos = selectionStart
    this.text = this.text
    setSelection(pos)
}

重设下富文本内容即可。还请注意一下,在重新设置文本内容前,先保存光标所在的位置,等重设完文本后再重新将光标定位到之前的位置,这样就是为了防止重设文本后丢失之前光标的位置。