用Kotlin封装一个自定义SpannableString

参考自:https://github.com/jaychang0917/SimpleText

使用Kotlin进行了改写,保留了大部分功能,现不支持点击时的文字颜色和背景色(感觉用到的场景不多,所以就没加)

Github上有更详细介绍 :https://github.com/chenyucheng97/SpannableStringBuilder

欢迎star?

先看效果图:

调用方法:

注意事项:SpannableString同时设置了ClickableSpan和ForegroundColorSpan后,发现ForegroundColorSpan不生效。

原因:ClickableSpan将ForegroundColorSpan的颜色覆盖了
解决方式:将ForegroundColorSpan替换为UnderlineSpan,并重写updateDrawState方法,参考:https://blog.csdn.net/Kikitious_Du/article/details/77170087

或者这里可以简化一下,先设置点击事件onClick(即先设置ClickableSpan),再设置textColor (即ForegroundColorSpan)

 val content1 = MySpannableString(this, "前往下一步即表示您已阅读并接受Booking.com之条款及细则和隐私条款")
            .first("条款及细则").size(25).onClick(tvContent) { showToast(this, "条款及细则") }.textColor(R.color.color_main)
            .underline()
            .first("隐私条款").size(25).onClick(tvContent) { showToast(this, "隐私条款") }.textColor(R.color.color_main)
            .underline()
            .first("下一步").strikethrough().bold().scaleSize(2)

        tvContent.text = content1


        val content2 = MySpannableString(this, "Booking.com之条款及细则和隐私条款")
            .bullet(40, R.color.black)
        textView.text = content2

支持的Span:

  • AbsoluteSizeSpan
  • RelativeSizeSpan
  • StyleSpan
  • TypefaceSpan
  • StrikethroughSpan
  • UnderlineSpan
  • BulletSpan
  • ForegroundColorSpan
  • SubscriptSpan
  • SuperscriptSpan
  • ClickableSpan

MySpannableString源码:

/**
 * Created by cyc on 2018/11/5.
 *
 * 常用Span封装,不支持点击时文字颜色和背景色的设置(使用场景少,暂不添加)
 * 支持设置字体颜色、大小、下划线、删除线、点击事件 等,
 * 可以对一个字符串中的多个目标子串进行设置Span
 *
 */
class MySpannableString(private val context: Context, text: CharSequence) : SpannableString(text) {

    private val spanMode = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
    // 初始时,待处理的索引范围为全部字符串
    private val rangeList = mutableListOf(Pair(0, text.length))
    private var textColor: Int = 0


    /**
     *  匹配出现的第一个目标子串[target],并记录开始和结束的index
     */
    fun first(target: String): MySpannableString {
        rangeList.clear()
        val index = toString().indexOf(target)
        val range = Pair(index, index + target.length)
        rangeList.add(range)
        return this
    }

    /**
     *  匹配出现的最后一个目标子串[target],并记录开始和结束的index
     */
    fun last(target: String): MySpannableString {
        rangeList.clear()
        val index = toString().lastIndexOf(target)
        val range = Pair(index, index + target.length)
        rangeList.add(range)
        return this
    }

    /**
     *   匹配出现的所有目标子串[target],并记录开始和结束的index
     */
    fun all(target: String): MySpannableString {
        rangeList.clear()
        val indexes = indexesOf(toString(), target)
        for (index in indexes) {
            val range = Pair(index, index + target.length)
            rangeList.add(range)
        }
        return this
    }

    /**
     *  记录源字符串[src]中目标子串 [target]出现的索引位置
     */
    fun indexesOf(src: String, target: String): MutableList<Int> {
        val positions = mutableListOf<Int>()
        var index = src.indexOf(target)
        while (index >= 0) {
            positions.add(index)
            index = src.indexOf(target, index + 1)
        }
        return positions
    }

    /**
     * 手动输入一个起点索引[from]和终点索引[to]
     */
    fun range(from: Int, to: Int): MySpannableString {
        rangeList.clear()
        val range = Pair(from, to + 1)
        rangeList.add(range)
        return this
    }

    /**
     * 手动输入所有起点和终点的索引范围[ranges]
     */
    fun ranges(ranges: MutableList<Pair<Int, Int>>): MySpannableString {
        rangeList.clear()
        rangeList.addAll(ranges)
        return this
    }

    /**
     * 计算两个字符串[startText] 和 [endText]之间的字符串的索引,加入到待处理的集合中,后续的Span设置都是对该索引范围内的字串进行的
     */
    fun between(startText: String, endText: String): MySpannableString {
        rangeList.clear()
        val startIndex = toString().indexOf(startText) + startText.length + 1
        val endIndex = toString().lastIndexOf(endText) - 1
        val range = Pair(startIndex, endIndex)
        rangeList.add(range)
        return this
    }

    /**
     * 给target字串设置文字绝对大小为[dp]
     */
    fun size(dp: Int): MySpannableString {
        for (range in rangeList) {
            setSpan(AbsoluteSizeSpan(dp, true), range.first, range.second, spanMode)
        }
        return this
    }

    /**
     * 给target字串设置文字相对大小,指相对于文本设定的大小的相对比例为[proportion]
     */
    fun scaleSize(proportion: Int): MySpannableString {
        for (range in rangeList) {
            setSpan(RelativeSizeSpan(proportion.toFloat()), range.first, range.second, spanMode)
        }
        return this
    }

    /**
     * 给target字串设置样式(粗体)
     */
    fun bold(): MySpannableString {
        for (range in rangeList) {
            setSpan(StyleSpan(Typeface.BOLD), range.first, range.second, spanMode)
        }
        return this
    }

    /**
     * 给target字串设置样式(斜体)
     */
    fun italic(): MySpannableString {
        for (range in rangeList) {
            setSpan(StyleSpan(Typeface.ITALIC), range.first, range.second, spanMode)
        }
        return this
    }

    /**
     * 给target字串设置样式(正常)
     */
    fun normal(): MySpannableString {
        for (range in rangeList) {
            setSpan(StyleSpan(Typeface.NORMAL), range.first, range.second, spanMode)
        }
        return this
    }

    /**
     * 给target字串设置样式(粗斜体)
     */
    fun bold_italic(): MySpannableString {
        for (range in rangeList) {
            setSpan(StyleSpan(Typeface.BOLD_ITALIC), range.first, range.second, spanMode)
        }
        return this
    }

    /**
     * 字体样式,可以设置不同的字体,比如系统自带的SANS_SERIF、MONOSPACE和SERIF
     */
    fun font(font: String): MySpannableString {
        for (range in rangeList) {
            setSpan(TypefaceSpan(font), range.first, range.second, spanMode)
        }
        return this
    }

    /**
     * 给target字串添加删除线
     */
    fun strikethrough(): MySpannableString {
        for (range in rangeList) {
            setSpan(StrikethroughSpan(), range.first, range.second, spanMode)
        }
        return this
    }

    /**
     * 给target字串添加下划线
     */
    fun underline(): MySpannableString {
        for (range in rangeList) {
            setSpan(UnderlineSpan(), range.first, range.second, spanMode)
        }
        return this
    }

    /**
     * 类似于HTML中的<li>标签的圆点效果,[dp]表示圆点和字体的间距,[colorRes]表示圆点的颜色
     */
    fun bullet(dp: Int, @ColorRes colorRes: Int?): MySpannableString {
        for (range in rangeList) {
            setSpan(BulletSpan(dp, colorRes ?: textColor), range.first, range.second, spanMode)
        }
        return this
    }

    /**
     * 字体颜色 [colorRes]表示target字串的字体颜色
     */
    fun textColor(@ColorRes colorRes: Int): MySpannableString {
        textColor = ContextCompat.getColor(context, colorRes)
        for (range in rangeList) {
            setSpan(ForegroundColorSpan(textColor), range.first, range.second, spanMode)
        }
        return this
    }

    /**
     * 将target字串作为下标
     */
    fun subscript(): MySpannableString {
        for (range in rangeList) {
            setSpan(SubscriptSpan(), range.first, range.second, spanMode)
        }
        return this
    }

    /**
     * 将target字串作为上标
     */
    fun superscript(): MySpannableString {
        for (range in rangeList) {
            setSpan(SuperscriptSpan(), range.first, range.second, spanMode)
        }
        return this
    }

    /**
     * 给[textView]设置一个点击事件[onTextClickListener]
     */
    fun onClick(textView: TextView, onTextClickListener: () -> Unit): MySpannableString {
        for (range in rangeList) {
            val span = object : ClickableSpan() {
                override fun onClick(widget: View?) {
                    onTextClickListener.invoke()
                }
            }
            setSpan(span, range.first, range.second, spanMode)
        }

        textView.highlightColor = Color.TRANSPARENT
        textView.movementMethod = LinkMovementMethod.getInstance()
        return this
    }

}
发布了82 篇原创文章 · 获赞 86 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/unicorn97/article/details/83756802