基于SpannableString匹配单词

本文介绍了如何在一段英文中根据给定的关键词高亮显示单词,重点在于确保关键词作为独立单词出现时才进行高亮,避免在其他单词内部误匹配。作者封装了一个MySpanTextView,实现了这一功能,且给出了详细的需求和效果示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近接到个需求,在一段英文中,将给定的单词高亮。写了近1天,考虑了各种情况,终于写完了对应算法。我将其封装成一个:MySpanTextView

源码在下面,直接复制,拿到项目中用就行。继承自普通TextView,仅仅对文字匹配做了修改,其他属性不变。复制代码,可以直接用

需求:给定一段英文,和某些关键字,在英文中,将关键字高亮。

要求:将给定的关键字视为独立的单词,当其出现在其他单词内时,不可以高亮。

如:
关键字中有单独字母 a ,则 and、teacher等单词中的 a 不可以高亮
英文中有一个单词是 haha ,关键字中有一个是 ha,则 haha 中,ha 不可以高亮

效果图如下:
在这里插入图片描述
相关的注释,全部在代码中了

/**
 * 展示高亮关键字的TextView
 */
class MySpanTextView : TextView {

    constructor(context: Context?) : this(context, null)
    constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    /**
     * 设置文字前景色
     *
     * content 要展示的文字
     * highlight 要处理为高亮的所有关键字
     */
    fun setTextForegColor(content: String?, highlight: String?, tip: String?) {

        try {

            //如果文本为空,就 setText=""
            if (content.isNullOrEmpty()) {
                text = ""
                return
            }

            //如果文本为空,就 setText=content
            if (highlight.isNullOrEmpty()) {
                text = content
                return
            }

            /**
             * 整体思路
             *
             * 1、将全部的高亮关键字(keys),进行切分,拿到单个的关键字(key)
             * 2、用每个key,去原始文本中进行遍历,找到出现的位置(每次出现的位置)
             * 3、对该位置的前后进行判断,是否是有效的单词分隔符,如:空格、逗号、句号等
             * 4、如果符合条件,就是最终,要高亮的位置(有效位),这个位置上的单词,就是要高亮的单词
             *
             * 如:
             * 原始文本:abc is a Teacher.
             * 高亮单词:a
             * 结果:abc、Teacher 中的"a",不能高亮,只有单独的那个"a",才能高亮,单独的"a"出现的位置,才是真正的有效位
             * 理由:abc中,虽然可以找到a,但是a后面,是"b",不是单词分隔符,所有不能算有效位
             *
             * 关于位置的处理思路:
             * 目前,找到后,先存起来(封装成一个bean(index:出现位置,key:key的内容),最后整体遍历一遍;
             * 也可以找到有效位,就高亮变色
             *
             */

            //所有要高亮的单词
            var keys = highlight.trim().split(";", ";")

            if (keys.isNullOrEmpty().not()) {
                //高亮关键字集合不为空

                //创建一个 spannableString
                var spannableString: SpannableString = SpannableString(content)

                //最后要集中处理的高亮位置的单词
                var keyBeanList: MutableList<HighKeyBean> = mutableListOf()

                //视为分隔符的 字符 的集合
                var separatedChars: MutableList<Char> = mutableListOf(
                    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
                    'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                    'u', 'v', 'w', 'x', 'y', 'z'
                )

                //遍历keys中,每个高亮关键字,并对其进行处理
                keys.forEach {

                    var index = 0

                    //截取原始文本的开始位置
                    var subStartIndex = 0

                    var subContent = content!!

                    while (subContent.contains(it)) {

                        //找到当前的这个关键字,在剩余文本中第一次出现的位置(不一定是有效位)
                        var indexOf = subContent.indexOf(it)

                        //设置、记录下一次,要截取的位置的开始
                        subStartIndex = indexOf + it.length

                        index = index + indexOf

                        //剩余文本中,找到了这个单词,判断其有效位
                        if (indexOf == 0) {
                            //单词出现的位置,是剩余文本的起点处

                            /**
                             * 特别说明
                             *
                             * 这里要检查一下标记的前一位,是否是有效位
                             *
                             * 因为会出现特殊情况
                             *
                             * 如:abc haha def
                             * key = ha
                             *
                             * 后续处理时
                             * 第一次:
                             * abc haha def
                             *
                             * indexOf = 4
                             * subStartIndex = indexOf + it.length = 4 + ha.length = 4+2 = 6
                             * index = 0 + 4 = 4
                             *
                             * 会走到 indexOf != 0 情况中的一种,判定结果为:haha中,前半部分的"ha",不能高亮
                             *
                             * 第二次:从 haha 的第二个 h 处开始截取
                             * 子串为:ha def
                             * key = ha
                             * 这个时候,这个 ha 还是不能高亮,因为它前面还紧跟字母,只是因为截取看不到
                             *
                             * 所以要对它的前一位做判断,同时,为了避免繁琐的判断,直接用 try...catch...
                             */
                            var checkIndex = true
                            try {

                                if (separatedChars.contains(content[index - 1].toLowerCase())) {
                                    checkIndex = false
                                }

                            } catch (e: Exception) {

                            }

                            if (checkIndex) {


                                if (it.length == subContent.length) {
                                    /**
                                     * 当前剩余文本,都是关键字
                                     * 如:剩余文本:abc
                                     * key=abc
                                     */

                                    //是有效位

                                    keyBeanList.add(
                                        HighKeyBean(
                                            index,
                                            it
                                        )
                                    )

                                } else if (!separatedChars.contains(subContent[it.length].toLowerCase())) {

                                    /**
                                     * 单词的下一个位置的字符,是分隔类型字符
                                     *
                                     * 如:剩余文本:ab cd
                                     * key = ab
                                     * 则:
                                     * indexOf = 0
                                     * keyLength = 2
                                     * indexOf + it.length = 2
                                     *
                                     * subContent[it.length] = subContent[2] = ' '(空格)
                                     *
                                     * separatedChars中包含空格
                                     */
                                    //是有效位
                                    keyBeanList.add(
                                        HighKeyBean(
                                            index,
                                            it
                                        )
                                    )
                                }
                            }

                        } else {
                            //出现位置,不再文本开始处

                            if (indexOf + it.length == subContent.length && !separatedChars.contains(subContent[indexOf - 1].toLowerCase())) {
                                /**
                                 * 如:剩余文本为:ab cde
                                 * key = cde
                                 *
                                 * indexOf = 3
                                 * keyLength = 3
                                 * indexOf + it.length =6
                                 * subContent.length = 6
                                 *
                                 * subContent[indexOf -1] = subContent[2] = ' '(空格)
                                 */
                                //是有效位
                                keyBeanList.add(
                                    HighKeyBean(
                                        index,
                                        it
                                    )
                                )

                            } else {

                                if (!separatedChars.contains(subContent[indexOf - 1].toLowerCase())
                                    && !separatedChars.contains(subContent[indexOf + it.length].toLowerCase()
                                    )
                                ) {
                                    keyBeanList.add(
                                        HighKeyBean(
                                            index,
                                            it
                                        )
                                    )
                                }

                            }
                        }

                        //下一次要处理的子串
                        subContent = subContent.substring(subStartIndex)

                        //避免错位,要进行对应的移位
                        index += it.length

                    }
                }

                Log.e("keyBeanList = ", "$keyBeanList")

                //集中处理需要高亮的单词
                if (keyBeanList.isNotEmpty()) {

                    keyBeanList.forEach {

                        spannableString.setSpan(
                            ForegroundColorSpan(Color.parseColor("#ffd234")),
                            it.index,
                            it.index + it.key.length,
                            Spanned.SPAN_INCLUSIVE_EXCLUSIVE
                        )

                    }

                    if (tip.isNullOrEmpty().not()) {

                        var t= tip!!.trim()

                        val tipSpan = ForegroundColorSpan(Color.parseColor("#ff999999"))
                        spannableString.setSpan(
                            tipSpan,
                            content.indexOf(t),
                            content.indexOf(t) + t.length,
                            Spanned.SPAN_INCLUSIVE_EXCLUSIVE
                        )
                    }

                    text = spannableString


                } else {
                    text = content
                }

            } else {
                //高亮关键字集合为空。直接展示原始文本
                text = content
            }

        } catch (e: Exception) {

            if (content.isNullOrEmpty()) {
                text = ""
            }else{
                text = content
            }

        }

    }

}

其中 HighKeyBean 为

data class HighKeyBean(
    var index: Int = 0,
    var key: String = ""
) {
}

使用:
布局文件中:

    <com.demo.newkotlindemo.MySpanTextView
            android:id="@+id/my_tv"
            android:layout_marginStart="20dp"
            android:layout_marginEnd="20dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
//文本内容
var content: String? =
            "If you procrastinate when faced with a big e e haha difficult if the problem... break and the problem if into ha parts, and handle one part at a time time."

//需要高亮的单词
var highlight: String? = "If;you;a;the;and;time;if;e;ha"

my_tv.setTextForegColor(content,highlight)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值