SpannableStringBuiler封装Kotlin

前言

SpannableStringBuilder和SpannableString功能基本一样,不过SpannableStringBuilder可以拼接,主要是通过setSpan来实现各种效果,主要的方法如下:

start: 指定Span的开始位置
end: 指定Span的结束位置,并不包括这个位置。
flags:取值有如下四个
Spannable. SPAN_INCLUSIVE_EXCLUSIVE:前面包括,后面不包括,即在文本前插入新的文本会应用该样式,而在文本后插入新文本不会应用该样式
Spannable. SPAN_INCLUSIVE_INCLUSIVE:前面包括,后面包括,即在文本前插入新的文本会应用该样式,而在文本后插入新文本也会应用该样式
Spannable. SPAN_EXCLUSIVE_EXCLUSIVE:前面不包括,后面不包括
Spannable. SPAN_EXCLUSIVE_INCLUSIVE:前面不包括,后面包括
what: 对应的各种Span,不同的Span对应不同的样式。已知的可用类有:
BackgroundColorSpan : 文本背景色
ForegroundColorSpan : 文本颜色
MaskFilterSpan : 修饰效果,如模糊(BlurMaskFilter)浮雕
RasterizerSpan : 光栅效果
StrikethroughSpan : 删除线
SuggestionSpan : 相当于占位符
UnderlineSpan : 下划线
AbsoluteSizeSpan : 文本字体(绝对大小)
DynamicDrawableSpan : 设置图片,基于文本基线或底部对齐。
ImageSpan : 图片
RelativeSizeSpan : 相对大小(文本字体)
ScaleXSpan : 基于x轴缩放
StyleSpan : 字体样式:粗体、斜体等
SubscriptSpan : 下标(数学公式会用到)
SuperscriptSpan : 上标(数学公式会用到)
TextAppearanceSpan : 文本外貌(包括字体、大小、样式和颜色)
TypefaceSpan : 文本字体
URLSpan : 文本超链接
ClickableSpan : 点击事件

简单使用示例

初始化SpannableString或SpannableStringBuilder,然后设置对应的setPan就可以实现对应的效果。

SpannableString spannableString = new SpannableString("要设置的内容");
        ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.parseColor("#009ad6"));
        spannableString.setSpan(colorSpan, 0, 8, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
        ((TextView)findViewById(R.id.mode1)).setText(spannableString);

具体使用详情可以参考:强大的SpannableStringBuilder

封装使用

对很多功能都可以封装,简化使用,这里使用了扩展函数,更方便在Kotlin中使用,不过在Java中也可以使用,使用方法如下:

第一种情况,要设置的内容已经是一段完整的内容

注意:链式调用时,只需要初始化第一个src就可以了,后续都会默认使用第一个,如果后续继续初始化src, 会导致前面的设置无效,只有最后一个生效。target和range都是为了确定要改变的文字的范围,两个初始化一个即可。

  1. 对整个字符串设置效果

    src 和target默认等于TextView的text

    //对整个 text 设置方式一,textView已经设置过内容,可以不用初始化src
    tvTvOne.sizeSpan(textSize = 20f)
    //对整个 text 设置方式二
    tvTvOne2.typeSpan(src = "全部文字加粗",target = "全部文字加粗",
      type = SsbKtx.type_bold)
    
  2. 设置部分文字效果

    type 有3个,对应加粗,倾斜,加粗倾斜

    //设置部分文字效果
    //tvTv2.typeSpan(range = 2..4,type = SsbKtx.type_bold)
     tvTv2.typeSpan(target = "部分",type = SsbKtx.type_bold)
    //设置加粗倾斜效果
     tvTv3.typeSpan(range = 0..4,type = SsbKtx.type_bold_italic)
    
  3. 对同一个文字设置多个效果

    对同一个部分做多种效果,只能第一个设置 src, 后续设置会导致前面的无效。

    //        tvTv4.typeSpan(range = 0..4,type = SsbKtx.type_bold_italic)
    //            .foregroundColorIntSpan(range = 0..4,color = Color.GREEN)
    //            .strikethroughSpan(range = 0..4)
            tvTv4.typeSpan(src = "只能这个可以设置 src,后面的再设置会导致前面效果无效",
                range = 0..4,type = SsbKtx.type_bold_italic)
                .foregroundColorIntSpan(range = 0..4,color = Color.GREEN)
                .strikethroughSpan(range = 0..4)
    
  4. 对多个不同的文字分别设置不同的效果

     tvTv5.typeSpan(range = 0..4,type = SsbKtx.type_bold_italic)
                .foregroundColorIntSpan(range = 7..11,color = Color.BLUE)
    
  5. 设置部分点击

    tvTv6.clickIntSpan(range = 0..4){
                Toast.makeText(this, "hello", Toast.LENGTH_SHORT).show()
            }
    
  6. 设置部分超链接

    tvTv7.urlSpan(range = 0..4,url = "https://www.baidu.com")
    

第二种情况,拼接成一个完整的字符串

  1. 拼接成完整的内容

     tvTv8.text = "拼接一段文字"
            tvTv8.appendTypeSpan("加粗",SsbKtx.type_bold)
                .strikethroughSpan(target = "加粗")//对同一部分文字做多个效果
                .appendForegroundColorIntSpan("改变字体颜色",Color.RED)
    

    如果想对拼接的内容做多个效果,可以在其后面调用对应的方法,只要traget或是range正确即可。

完整代码

object SsbKtx {
    const val flag = SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE
    const val type_bold = Typeface.BOLD
    const val type_italic = Typeface.ITALIC
    const val type_bold_italic = Typeface.BOLD_ITALIC

}
//-------------------CharSequence相关扩展-----------------------
/**
 *CharSequence不为 null 或者 empty
 */
fun CharSequence?.isNotNullOrEmpty() = !isNullOrEmpty()

/**
 *获取一段文字在文字中的范围
 * @param target
 * @return
 */
fun CharSequence.range(target: CharSequence): IntRange {
    val start = this.indexOf(target.toString())
    return start..(start + target.length)
}

/**
 *将一段指定的文字改变大小
 * @return
 */
fun CharSequence.sizeSpan(range: IntRange, textSize: Int): CharSequence {
    return SpannableStringBuilder(this).apply {
        setSpan(AbsoluteSizeSpan(textSize), range.first, range.last, SsbKtx.flag)
    }
}

/**
 *将一段指定的文字,设置类型,是否加粗,倾斜
 * @return
 */
fun CharSequence.typeSpan(range: IntRange, type: Int): CharSequence {
    return SpannableStringBuilder(this).apply {
        setSpan(StyleSpan(type), range.first, range.last, SsbKtx.flag)
    }
}

/**
 *设置下划线
 * @param range
 * @return
 */
fun CharSequence.underlineSpan(range: IntRange): CharSequence {
    return SpannableStringBuilder(this).apply {
        setSpan(UnderlineSpan(), range.first, range.last, SsbKtx.flag)
    }
}

/**
 *设置删除线
 * @param range
 * @return
 */
fun CharSequence.strikethroughSpan(range: IntRange): CharSequence {
    return SpannableStringBuilder(this).apply {
        setSpan(StrikethroughSpan(), range.first, range.last, SsbKtx.flag)
    }
}

/**
 *设置文字颜色
 * @param range
 * @return
 */
fun CharSequence.foregroundColorSpan(range: IntRange, color: Int = Color.RED): CharSequence {
    return SpannableStringBuilder(this).apply {
        setSpan(ForegroundColorSpan(color), range.first, range.last, SsbKtx.flag)
    }
}

/**
 *设置文字背景色
 * @param range
 * @return
 */
fun CharSequence.backgroundColorSpan(range: IntRange, color: Int = Color.RED): CharSequence {
    return SpannableStringBuilder(this).apply {
        setSpan(BackgroundColorSpan(color), range.first, range.last, SsbKtx.flag)
    }
}

/**
 *设置引用线颜色
 * @param range
 * @return
 */
fun CharSequence.quoteColorSpan(range: IntRange, color: Int = Color.RED): CharSequence {
    return SpannableStringBuilder(this).apply {
        setSpan(QuoteSpan(color), range.first, range.last, SsbKtx.flag)
    }
}

/**
 *设置字体大小比例
 * @param range
 * @return
 */
fun CharSequence.proportionSpan(range: IntRange, proportion: Float): CharSequence {
    return SpannableStringBuilder(this).apply {
        setSpan(RelativeSizeSpan(proportion), range.first, range.last, SsbKtx.flag)
    }
}

/**
 *设置横向字体大小比例
 * @param range
 * @return
 */
fun CharSequence.proportionXSpan(range: IntRange, proportion: Float): CharSequence {
    return SpannableStringBuilder(this).apply {
        setSpan(ScaleXSpan(proportion), range.first, range.last, SsbKtx.flag)
    }
}

/**
 *设置上标
 * @param range
 * @return
 */
fun CharSequence.superscriptSpan(range: IntRange): CharSequence {
    return SpannableStringBuilder(this).apply {
        setSpan(SuperscriptSpan(), range.first, range.last, SsbKtx.flag)
    }
}

/**
 *设置下标
 * @param range
 * @return
 */
fun CharSequence.subscriptSpan(range: IntRange): CharSequence {
    return SpannableStringBuilder(this).apply {
        setSpan(SubscriptSpan(), range.first, range.last, SsbKtx.flag)
    }
}

/**
 *设置字体
 * @param range
 * @return
 */
fun CharSequence.fontSpan(range: IntRange, font: String): CharSequence {
    return SpannableStringBuilder(this).apply {
        setSpan(TypefaceSpan(font), range.first, range.last, SsbKtx.flag)
    }
}

@RequiresApi(Build.VERSION_CODES.P)
fun CharSequence.fontSpan(range: IntRange, font: Typeface): CharSequence {
    return SpannableStringBuilder(this).apply {
        setSpan(TypefaceSpan(font), range.first, range.last, SsbKtx.flag)
    }
}

/**
 *设置对齐方式
 * @param range
 * @return
 */
fun CharSequence.alignSpan(range: IntRange, align: Layout.Alignment): CharSequence {
    return SpannableStringBuilder(this).apply {
        setSpan(AlignmentSpan.Standard(align), range.first, range.last, SsbKtx.flag)
    }
}

/**
 *设置url,超链接
 * @param range
 * @return
 */
fun CharSequence.urlSpan(range: IntRange, url: String): CharSequence {
    return SpannableStringBuilder(this).apply {
        setSpan(URLSpan(url), range.first, range.last, SsbKtx.flag)
    }
}

/**
 *设置click,将一段文字中指定range的文字添加颜色和点击事件
 * @param range
 * @return
 */
fun CharSequence.clickSpan(
    range: IntRange,
    color: Int = Color.RED,
    isUnderlineText: Boolean = false,
    clickAction: () -> Unit
): CharSequence {
    return SpannableString(this).apply {
        val clickableSpan = object : ClickableSpan() {
            override fun onClick(widget: View) {
                clickAction()
            }

            override fun updateDrawState(ds: TextPaint) {
                ds.color = color
                ds.isUnderlineText = isUnderlineText
            }
        }
        setSpan(clickableSpan, range.first, range.last, SsbKtx.flag)
    }
}


//-------------------TextView相关扩展--------------------------
/**
 *设置目标文字大小, src,target 为空时,默认设置整个 text
 * @return
 */
fun TextView?.sizeSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null,
    @DimenRes textSize: Int
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        textSize == 0 -> this
        range != null -> {
            text = src.sizeSpan(range, ResUtils.getDimensionPixelSize(textSize))
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.sizeSpan(src.range(target!!), ResUtils.getDimensionPixelSize(textSize))
            this
        }
        else -> this
    }
}

/**
 *设置目标文字大小, src,target 为空时,默认设置整个 text
 * @return
 */
fun TextView?.sizeSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null,
    textSize: Float
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        textSize == 0f -> this
        range != null -> {
            text = src.sizeSpan(range, DensityUtils.dp2px(textSize))
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.sizeSpan(src.range(target!!), DensityUtils.dp2px(textSize))
            this
        }
        else -> this
    }
}

/**
 *追加内容设置字体大小
 * @param str
 * @param textSize
 * @return
 */
fun TextView?.appendSizeSpan(str: String?, textSize: Float): TextView? {
    str?.let {
        this?.append(it.sizeSpan(0..it.length, DensityUtils.dp2px(textSize)))
    }
    return this
}

fun TextView?.appendSizeSpan(str: String?, @DimenRes textSize: Int): TextView? {
    str?.let {
        this?.append(it.sizeSpan(0..it.length, ResUtils.getDimensionPixelSize(textSize)))
    }
    return this
}

/**
 *设置目标文字类型(加粗,倾斜,加粗倾斜),src,target 为空时,默认设置整个 text
 * @return
 */
fun TextView?.typeSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null,
    type: Int
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            text = src.typeSpan(range, type)
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.typeSpan(src.range(target!!), type)
            this
        }
        else -> this
    }
}

fun TextView?.appendTypeSpan(str: String?, type: Int): TextView? {
    str?.let {
        this?.append(it.typeSpan(0..it.length, type))
    }
    return this
}

/**
 *设置目标文字下划线
 * @return
 */
fun TextView?.underlineSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            text = src.underlineSpan(range)
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.underlineSpan(src.range(target!!))
            this
        }
        else -> this
    }
}

fun TextView?.appendUnderlineSpan(str: String?): TextView? {
    str?.let {
        this?.append(it.underlineSpan(0..it.length))
    }
    return this
}

/**
 *设置目标文字删除线
 * @return
 */
fun TextView?.strikethroughSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            text = src.strikethroughSpan(range)
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.strikethroughSpan(src.range(target!!))
            this
        }
        else -> this
    }
}

fun TextView?.appendStrikethroughSpan(str: String?): TextView? {
    str?.let {
        this?.append(it.strikethroughSpan(0..it.length))
    }
    return this
}

/**
 *设置目标文字颜色
 * @return
 */
fun TextView?.foregroundColorIntSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null,
    color: Int
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            text = src.foregroundColorSpan(range, color)
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.foregroundColorSpan(src.range(target!!), color)
            this
        }
        else -> this
    }
}

fun TextView?.appendForegroundColorIntSpan(str: String?, color: Int): TextView? {
    str?.let {
        this?.append(it.foregroundColorSpan(0..it.length, color))
    }
    return this
}

/**
 *设置目标文字颜色
 * @return
 */
fun TextView?.foregroundColorSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null,
    @ColorRes color: Int
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            text = src.foregroundColorSpan(range, ResUtils.getColor(color))
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.foregroundColorSpan(src.range(target!!), ResUtils.getColor(color))
            this
        }
        else -> this
    }
}

fun TextView?.appendForegroundColorSpan(str: String?, @ColorRes color: Int): TextView? {
    str?.let {
        this?.append(it.foregroundColorSpan(0..it.length, ResUtils.getColor(color)))
    }
    return this
}

/**
 *设置目标文字背景颜色
 * @return
 */
fun TextView?.backgroundColorIntSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null,
    color: Int
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            text = src.backgroundColorSpan(range, color)
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.backgroundColorSpan(src.range(target!!), color)
            this
        }
        else -> this
    }
}

fun TextView?.appendBackgroundColorIntSpan(str: String?, color: Int): TextView? {
    str?.let {
        this?.append(it.backgroundColorSpan(0..it.length, color))
    }
    return this
}

/**
 *设置目标文字背景颜色
 * @return
 */
fun TextView?.backgroundColorSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null,
    @ColorRes color: Int
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            text = src.backgroundColorSpan(range, ResUtils.getColor(color))
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.backgroundColorSpan(src.range(target!!), ResUtils.getColor(color))
            this
        }
        else -> this
    }
}

fun TextView?.appendBackgroundColorSpan(str: String?, @ColorRes color: Int): TextView? {
    str?.let {
        this?.append(it.backgroundColorSpan(0..it.length, ResUtils.getColor(color)))
    }
    return this
}

/**
 *设置目标文字引用线颜色
 * @return
 */
fun TextView?.quoteColorIntSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null,
    color: Int
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            text = src.quoteColorSpan(range, color)
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.quoteColorSpan(src.range(target!!), color)
            this
        }
        else -> this
    }
}

fun TextView?.appendQuoteColorIntSpan(str: String?, color: Int): TextView? {
    str?.let {
        this?.append(it.quoteColorSpan(0..it.length, color))
    }
    return this
}

/**
 *设置目标文字引用线颜色
 * @return
 */
fun TextView?.quoteColorSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null,
    @ColorRes color: Int
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            text = src.quoteColorSpan(range, ResUtils.getColor(color))
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.quoteColorSpan(src.range(target!!), ResUtils.getColor(color))
            this
        }
        else -> this
    }
}

fun TextView?.appendQuoteColorSpan(str: String?, @ColorRes color: Int): TextView? {
    str?.let {
        this?.append(it.quoteColorSpan(0..it.length, ResUtils.getColor(color)))
    }
    return this
}

/**
 *设置目标文字字体大小比例
 * @return
 */
fun TextView?.proportionSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null,
    proportion: Float
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            text = src.proportionSpan(range, proportion)
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.proportionSpan(src.range(target!!), proportion)
            this
        }
        else -> this
    }
}

fun TextView?.appendProportionSpan(str: String?, proportion: Float): TextView? {
    str?.let {
        this?.append(it.proportionSpan(0..it.length, proportion))
    }
    return this
}

/**
 *设置目标文字字体横向大小比例
 * @return
 */
fun TextView?.proportionXSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null,
    proportion: Float
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            text = src.proportionXSpan(range, proportion)
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.proportionXSpan(src.range(target!!), proportion)
            this
        }
        else -> this
    }
}

fun TextView?.appendProportionXSpan(str: String?, proportion: Float): TextView? {
    str?.let {
        this?.append(it.proportionXSpan(0..it.length, proportion))
    }
    return this
}

/**
 *设置目标文字字体上标
 * @return
 */
fun TextView?.superscriptSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            text = src.superscriptSpan(range)
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.superscriptSpan(src.range(target!!))
            this
        }
        else -> this
    }
}

fun TextView?.appendSuperscriptSpan(str: String?): TextView? {
    str?.let {
        this?.append(it.superscriptSpan(0..it.length))
    }
    return this
}

/**
 *设置目标文字字体下标
 * @return
 */
fun TextView?.subscriptSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            text = src.subscriptSpan(range)
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.subscriptSpan(src.range(target!!))
            this
        }
        else -> this
    }
}

fun TextView?.appendSubscriptSpan(str: String?): TextView? {
    str?.let {
        this?.append(it.subscriptSpan(0..it.length))
    }
    return this
}

/**
 *设置目标文字字体
 * @return
 */
fun TextView?.fontSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null,
    font: String
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            text = src.fontSpan(range, font)
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.fontSpan(src.range(target!!), font)
            this
        }
        else -> this
    }
}

fun TextView?.appendFontSpan(str: String?, font: String): TextView? {
    str?.let {
        this?.append(it.fontSpan(0..it.length, font))
    }
    return this
}

/**
 *设置目标文字字体
 * @return
 */
@RequiresApi(Build.VERSION_CODES.P)
fun TextView?.fontSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null,
    font: Typeface
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            text = src.fontSpan(range, font)
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.fontSpan(src.range(target!!), font)
            this
        }
        else -> this
    }
}

@RequiresApi(Build.VERSION_CODES.P)
fun TextView?.appendFontSpan(str: String?, font: Typeface): TextView? {
    str?.let {
        this?.append(it.fontSpan(0..it.length, font))
    }
    return this
}

/**
 *设置目标文字对齐方式
 * @return
 */
fun TextView?.alignSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null,
    align: Layout.Alignment
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            text = src.alignSpan(range, align)
            this
        }
        target.isNotNullOrEmpty() -> {
            text = src.alignSpan(src.range(target!!), align)
            this
        }
        else -> this
    }
}

fun TextView?.appendAlignSpan(str: String?, align: Layout.Alignment): TextView? {
    str?.let {
        this?.append(it.alignSpan(0..it.length, align))
    }
    return this
}

/**
 *设置目标文字超链接
 * @return
 */
fun TextView?.urlSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null,
    url: String
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            movementMethod = LinkMovementMethod.getInstance()
            text = src.urlSpan(range, url)
            this
        }
        target.isNotNullOrEmpty() -> {
            movementMethod = LinkMovementMethod.getInstance()
            text = src.urlSpan(src.range(target!!), url)
            this
        }
        else -> this
    }
}

fun TextView?.appendUrlSpan(str: String?, url: String): TextView? {
    str?.let {
        this?.append(it.urlSpan(0..it.length, url))
    }
    return this
}

/**
 *设置目标文字点击
 * @return
 */
fun TextView?.clickIntSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null,
    color: Int = Color.RED,
    isUnderlineText: Boolean = false,
    clickAction: () -> Unit
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            movementMethod = LinkMovementMethod.getInstance()
            highlightColor = Color.TRANSPARENT  // remove click bg color
            text = src.clickSpan(range, color, isUnderlineText, clickAction)
            this
        }
        target.isNotNullOrEmpty() -> {
            movementMethod = LinkMovementMethod.getInstance()
            highlightColor = Color.TRANSPARENT  // remove click bg color
            text = src.clickSpan(src.range(target!!), color, isUnderlineText, clickAction)
            this
        }
        else -> this
    }
}

fun TextView?.appendClickIntSpan(
    str: String?, color: Int = Color.RED,
    isUnderlineText: Boolean = false,
    clickAction: () -> Unit
): TextView? {
    str?.let {
        this?.append(it.clickSpan(0..it.length, color, isUnderlineText, clickAction))
    }
    return this
}

/**
 *设置目标文字点击
 * @return
 */
fun TextView?.clickSpan(
    src: CharSequence? = this?.text,
    target: CharSequence? = this?.text,
    range: IntRange? = null,
    @ColorRes color: Int,
    isUnderlineText: Boolean = false,
    clickAction: () -> Unit
): TextView? {
    return when {
        this == null -> this
        src.isNullOrEmpty() -> this
        target.isNullOrEmpty() && range == null -> this
        range != null -> {
            movementMethod = LinkMovementMethod.getInstance()
            highlightColor = Color.TRANSPARENT  // remove click bg color
            text = src.clickSpan(range, ResUtils.getColor(color), isUnderlineText, clickAction)
            this
        }
        target.isNotNullOrEmpty() -> {
            movementMethod = LinkMovementMethod.getInstance()
            highlightColor = Color.TRANSPARENT  // remove click bg color
            text = src.clickSpan(
                src.range(target!!),
                ResUtils.getColor(color),
                isUnderlineText,
                clickAction
            )
            this
        }
        else -> this
    }
}

fun TextView?.appendClickSpan(
    str: String?,
    @ColorRes color: Int,
    isUnderlineText: Boolean = false,
    clickAction: () -> Unit
): TextView? {
    str?.let {
        this?.append(
            it.clickSpan(
                0..it.length,
                ResUtils.getColor(color),
                isUnderlineText,
                clickAction
            )
        )
    }
    return this
}

里面的ResUtils只是简单的获取资源文件,如果想直接引入,可以参考Github直接使用gradle依赖。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容

  • 探索 Android 中的 Span 在 Android 中,使用 Span 定义文本的样式. 通过 Span 改...
    TonnyL阅读 6,100评论 2 14
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML标准。 注意:讲述HT...
    kismetajun阅读 27,485评论 1 45
  • 以下是常用html+相关css全部汇总: # 01html标签 ``` html标签 是组成网页的骨架 区域划分作...
    Qingelin阅读 583评论 0 0
  • 前端 VScode 自动换行(word wrap) ctrl + s 保存 ctrl + a 全选 ctrl + ...
    东方寂明阅读 979评论 0 1
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 125,019评论 2 7