一、前言

基本包含了常见的设置加粗,倾斜,设置部分文字大小颜色等,除了没有加入替换图片功能,感觉替换图片对效果控制不够好,就没有加入。可以设置单个效果,也可以自由组合,同时设置多个效果,非常灵活。

二、使用效果和详解

    1. 部分文字加粗

image.png
使用方法,推荐使用方法一:

  1. val src = "普通样式的文字测试富文本部分加粗"
  2. val target1 = "样式"
  3. val target2 = "测试"
  4. //使用方法一
  5. val ss1 = SpannableStringUtils.Builder(src,target1)
  6. .isBold(true).build()
  7. //使用方法二
  8. val ss11 = SpannableStringUtils.ssb(src)
  9. SpannableStringUtils.isBold(ss11,target2)
  10. tv_textview_test.text = ss11
    1. 全部文字加粗

image.png

  1. val src2 = "普通样式的文字测试富文本全部加粗"
  2. val ss2 = SpannableStringUtils.Builder(src2)
  3. .isBold(true).build()
  4. tv_textview_blod.text = ss2
    1. 设置同一部分文字多种效果

image.png 加粗倾斜更换字体颜色

  1. val src3 = "普通样式的文字测试富文本多种效果"
  2. val target3 = "普通样式的文字"
  3. val ss3 = SpannableStringUtils.Builder(src3,target3)
  4. .isBoldItalic(true)
  5. .foregroundColor(R.color.red)
  6. .build()
  7. tv_textview_blodItalic.text = ss3
    1. 设置多个部分效果

image.png

  1. val src4 = "普通样式的文字测试富文本多个部分设置效果"
  2. val target4 = "样式的"
  3. val target44 = "测试富文本"
  4. //设置 target4 部分
  5. val ss4 = SpannableStringUtils.Builder(src4,target4)
  6. .foregroundColor(R.color.red)
  7. .build()
  8. //再设置 target44 部分
  9. SpannableStringUtils.foregroundColor(ss4,target44,R.color.blue_74D3FF)
  10. tv_textview_three.text = ss4
    1. 设置部分文字点击,设置超链接同理,不过有些问题

image.png

  1. val src5 = "普通样式的文字测试富文本部分文字可点击效果"
  2. val target5 = "可点击效果"
  3. val ss5 = SpannableStringUtils.Builder(src5,target5)
  4. //.foregroundColor(R.color.red)
  5. .clickSpan(object : ClickableSpan(){
  6. override fun onClick(widget: View) {
  7. //设置点击处理
  8. Toast.makeText(this@TextViewActivity, "hello", Toast.LENGTH_SHORT).show()
  9. }
  10. })
  11. .build()
  12. //设置点击需要先添加view.setMovementMethod(LinkMovementMethod.getInstance())
  13. tv_textview_4.movementMethod = LinkMovementMethod.getInstance()
  14. tv_textview_4.text = ss5

基本上所有的方法都可以通过以上方式组合使用。

三、所有方法详解

    1. 使用 build 初始化
      1. textSize : 设置字体大小
      2. clickSpan : 设置点击
      3. url : 设置超链接
      4. align : 设置对齐方式
      5. fontFamily : 设置字体
      6. isSubscript : 设置下标
      7. isSuperscript : 设置上标
      8. xProportion : 横向字体比例
      9. proportion : 字体比例
      10. start : 起始点,初始化时不想设置目标文字时使用
      11. end : 结束点,初始化时不想设置目标文字时使用
      12. isBold : 加粗
      13. isItalic : 倾斜
      14. isBoldItalic : 加粗和倾斜
      15. isUnderline : 下划线
      16. isStrikethrough : 删除线
      17. foregroundColor : 文字颜色
      18. backgroundColor : 背景色
      19. quoteColor : 引用线颜色
    1. 直接使用SpannableStringUtils时

      1. isBold : 加粗
      2. isItalic : 倾斜
      3. isBoldItalic : 加粗和倾斜
      4. isUnderline : 下划线
      5. isStrikethrough : 删除线
      6. foregroundColor : 文字颜色
      7. backgroundColor : 背景色
      8. textSize : 设置字体大小
      9. clickSpan : 设置点击
      10. url : 设置超链接
      11. ssb : 获取一个SpannableStringBuilder

      四、工具类

      ``` class SsbUtils private constructor() {

      //针对多个部分调用设置使用呢 companion object { private const val flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE

      private fun getStart(ssb: SpannableStringBuilder, target: String?): Int {

      1. if (target.isNullOrEmpty()) {
      2. return 0
      3. }
      4. return ssb.indexOf(target)

      }

      private fun getEnd(ssb: SpannableStringBuilder, target: String?): Int {

      1. if (target.isNullOrEmpty()) {
      2. return 0
      3. }
      4. val pos = ssb.indexOf(target)
      5. var end = pos + target.length
      6. if (end > ssb.length) {
      7. end = ssb.length
      8. }
      9. return end

      }

      private fun getPosStart(ssb: SpannableStringBuilder, start: Int): Int {

      1. var posStart = start
      2. if (start < 0) {
      3. posStart = 0
      4. } else if (start > ssb.length) {
      5. posStart = ssb.length
      6. }
      7. return posStart

      }

      private fun getPosEnd(ssb: SpannableStringBuilder, end: Int): Int {

      1. var posEnd = end
      2. if (end > ssb.length) {
      3. posEnd = ssb.length
      4. }
      5. return posEnd

      }

      fun isBold(ssb: SpannableStringBuilder, target: String?): SpannableStringBuilder {

      1. if (!target.isNullOrEmpty()) {
      2. ssb.setSpan(
      3. StyleSpan(Typeface.BOLD),
      4. getStart(ssb, target),
      5. getEnd(ssb, target),
      6. flag
      7. )
      8. }
      9. return ssb

      }

      fun isBold(ssb: SpannableStringBuilder, start: Int, end: Int): SpannableStringBuilder {

      1. ssb.setSpan(
      2. StyleSpan(Typeface.BOLD),
      3. getPosStart(ssb, start),
      4. getPosEnd(ssb, end),
      5. flag
      6. )
      7. return ssb

      }

      fun isItalic(ssb: SpannableStringBuilder, target: String?): SpannableStringBuilder {

      1. if (!target.isNullOrEmpty()) {
      2. ssb.setSpan(
      3. StyleSpan(Typeface.ITALIC),
      4. getStart(ssb, target),
      5. getEnd(ssb, target),
      6. flag
      7. )
      8. }
      9. return ssb

      }

      fun isItalic(ssb: SpannableStringBuilder, start: Int, end: Int): SpannableStringBuilder {

      1. ssb.setSpan(
      2. StyleSpan(Typeface.ITALIC),
      3. getPosStart(ssb, start),
      4. getPosEnd(ssb, end),
      5. flag
      6. )
      7. return ssb

      }

      fun isBoldItalic(ssb: SpannableStringBuilder, target: String?): SpannableStringBuilder {

      1. if (!target.isNullOrEmpty()) {
      2. ssb.setSpan(
      3. StyleSpan(Typeface.BOLD_ITALIC),
      4. getStart(ssb, target),
      5. getEnd(ssb, target),
      6. flag
      7. )
      8. }
      9. return ssb

      }

      fun isBoldItalic(

      1. ssb: SpannableStringBuilder,
      2. start: Int,
      3. end: Int

      ): SpannableStringBuilder {

      1. ssb.setSpan(
      2. StyleSpan(Typeface.BOLD_ITALIC),
      3. getPosStart(ssb, start),
      4. getPosEnd(ssb, end),
      5. flag
      6. )
      7. return ssb

      }

      fun isUnderline(ssb: SpannableStringBuilder, target: String?): SpannableStringBuilder {

      1. if (!target.isNullOrEmpty()) {
      2. ssb.setSpan(UnderlineSpan(), getStart(ssb, target), getEnd(ssb, target), flag)
      3. }
      4. return ssb

      }

      fun isUnderline(ssb: SpannableStringBuilder, start: Int, end: Int): SpannableStringBuilder {

      1. ssb.setSpan(UnderlineSpan(), getPosStart(ssb, start), getPosEnd(ssb, end), flag)
      2. return ssb

      }

      fun isStrikethrough(ssb: SpannableStringBuilder, target: String?): SpannableStringBuilder {

      1. if (!target.isNullOrEmpty()) {
      2. ssb.setSpan(StrikethroughSpan(), getStart(ssb, target), getEnd(ssb, target), flag)
      3. }
      4. return ssb

      }

      fun isStrikethrough(

      1. ssb: SpannableStringBuilder,
      2. start: Int,
      3. end: Int

      ): SpannableStringBuilder {

      1. ssb.setSpan(StrikethroughSpan(), getPosStart(ssb, start), getPosEnd(ssb, end), flag)
      2. return ssb

      }

      fun foregroundColor(

      1. ssb: SpannableStringBuilder,
      2. target: String?,
      3. @ColorRes froegroundColorRes: Int

      ): SpannableStringBuilder {

      1. if (!target.isNullOrEmpty()) {
      2. ssb.setSpan(
      3. ForegroundColorSpan(ResUtils.getColor(froegroundColorRes)),
      4. getStart(ssb, target),
      5. getEnd(ssb, target),
      6. flag
      7. )
      8. }
      9. return ssb

      }

      fun foregroundColor(

      1. ssb: SpannableStringBuilder,
      2. start: Int,
      3. end: Int,
      4. @ColorRes froegroundColorRes: Int

      ): SpannableStringBuilder {

      1. ssb.setSpan(
      2. ForegroundColorSpan(ResUtils.getColor(froegroundColorRes)),
      3. getPosStart(ssb, start),
      4. getPosEnd(ssb, end),
      5. flag
      6. )
      7. return ssb

      }

      fun backgroundColor(

      1. ssb: SpannableStringBuilder,
      2. target: String?,
      3. @ColorRes backgroundColor: Int

      ): SpannableStringBuilder {

      1. if (!target.isNullOrEmpty()) {
      2. ssb.setSpan(
      3. BackgroundColorSpan(ResUtils.getColor(backgroundColor)),
      4. getStart(ssb, target),
      5. getEnd(ssb, target),
      6. flag
      7. )
      8. }
      9. return ssb

      }

      fun backgroundColor(

      1. ssb: SpannableStringBuilder,
      2. start: Int,
      3. end: Int,
      4. @ColorRes backgroundColor: Int

      ): SpannableStringBuilder {

      1. ssb.setSpan(
      2. BackgroundColorSpan(ResUtils.getColor(backgroundColor)),
      3. getPosStart(ssb, start),
      4. getPosEnd(ssb, end),
      5. flag
      6. )
      7. return ssb

      }

      fun textSize(

      1. ssb: SpannableStringBuilder,
      2. target: String?,
      3. @DimenRes textSize: Int

      ): SpannableStringBuilder {

      1. if (!target.isNullOrEmpty()) {
      2. ssb.setSpan(
      3. AbsoluteSizeSpan(ResUtils.getDimensionPixelSize(textSize)),
      4. getStart(ssb, target),
      5. getEnd(ssb, target),
      6. flag
      7. )
      8. }
      9. return ssb

      }

      fun textSize(

      1. ssb: SpannableStringBuilder,
      2. start: Int,
      3. end: Int,
      4. @DimenRes textSize: Int

      ): SpannableStringBuilder {

      1. ssb.setSpan(
      2. AbsoluteSizeSpan(ResUtils.getDimensionPixelSize(textSize)),
      3. getPosStart(ssb, start),
      4. getPosEnd(ssb, end),
      5. flag
      6. )
      7. return ssb

      }

  1. fun textSize(
  2. ssb: SpannableStringBuilder,
  3. target: String?,
  4. textSize: Float
  5. ): SpannableStringBuilder {
  6. if (!target.isNullOrEmpty()) {
  7. ssb.setSpan(
  8. AbsoluteSizeSpan(DensityUtils.dp2px(textSize)),
  9. getStart(ssb, target),
  10. getEnd(ssb, target),
  11. flag
  12. )
  13. }
  14. return ssb
  15. }
  16. fun textSize(
  17. ssb: SpannableStringBuilder,
  18. start: Int,
  19. end: Int,
  20. textSize: Float
  21. ): SpannableStringBuilder {
  22. ssb.setSpan(
  23. AbsoluteSizeSpan(DensityUtils.dp2px(textSize)),
  24. getPosStart(ssb, start),
  25. getPosEnd(ssb, end),
  26. flag
  27. )
  28. return ssb
  29. }
  30. fun clickSpan(
  31. ssb: SpannableStringBuilder,
  32. target: String?,
  33. clickSpan: ClickableSpan
  34. ): SpannableStringBuilder {
  35. if (!target.isNullOrEmpty()) {
  36. ssb.setSpan(clickSpan, getStart(ssb, target), getEnd(ssb, target), flag)
  37. }
  38. return ssb
  39. }
  40. fun clickSpan(
  41. ssb: SpannableStringBuilder,
  42. start: Int,
  43. end: Int,
  44. clickSpan: ClickableSpan
  45. ): SpannableStringBuilder {
  46. ssb.setSpan(clickSpan, getPosStart(ssb, start), getPosEnd(ssb, end), flag)
  47. return ssb
  48. }
  49. fun url(
  50. ssb: SpannableStringBuilder,
  51. target: String?,
  52. url: String?
  53. ): SpannableStringBuilder {
  54. if (!target.isNullOrEmpty() && !url.isNullOrEmpty()) {
  55. ssb.setSpan(URLSpan(url), getStart(ssb, target), getEnd(ssb, target), flag)
  56. }
  57. return ssb
  58. }
  59. fun url(
  60. ssb: SpannableStringBuilder,
  61. start: Int,
  62. end: Int,
  63. url: String?
  64. ): SpannableStringBuilder {
  65. if (!url.isNullOrEmpty()) {
  66. ssb.setSpan(URLSpan(url), getPosStart(ssb, start), getPosEnd(ssb, end), flag)
  67. }
  68. return ssb
  69. }
  70. fun ssb(src: String?): SpannableStringBuilder {
  71. if (src.isNullOrEmpty()) {
  72. return SpannableStringBuilder()
  73. }
  74. return SpannableStringBuilder(src)
  75. }
  76. }
  77. //针对单个部分使用
  78. class Builder(private val src: String, private val target: String? = null) {
  79. private var ssb: SpannableStringBuilder = SpannableStringBuilder(src)
  80. private var start: Int = 0
  81. private var end: Int = 0
  82. init {
  83. start = getStart()
  84. end = getEnd()
  85. }
  86. private val flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
  87. private var isBold: Boolean = false//加粗
  88. private var isItalic = false//倾斜
  89. private var isBoldItalic = false//加粗倾斜
  90. private var isUnderline = false//下划线
  91. private var isStrikethrough = false//删除线
  92. private var isSuperscript = false //上标
  93. private var isSubscript = false //下标
  94. @ColorInt
  95. private var foregroundColor = 0 //前景色
  96. @ColorInt
  97. private var backgroundColor = 0 //背景色
  98. @ColorInt
  99. private var quoteColor = 0//引用线颜色
  100. private var proportion = 0f //字体比例
  101. private var xProportion = 0f //横向字体比例
  102. /**
  103. * 设置字体
  104. *
  105. * @param fontFamily 字体
  106. * <ul>
  107. * <li>monospace</li>
  108. * <li>serif</li>
  109. * <li>sans-serif</li>
  110. * </ul>
  111. * @return {@link Builder}
  112. */
  113. private var fontFamily: String? = null
  114. /**
  115. * 设置对齐
  116. *
  117. * @param align 对其方式
  118. * <ul>
  119. * Alignment#ALIGN_NORMAL}正常</li>
  120. * Alignment#ALIGN_OPPOSITE}相反</li>
  121. * Alignment#ALIGN_CENTER}居中</li>
  122. * </ul>
  123. * @return {@link Builder}
  124. */
  125. private var align: Layout.Alignment? = null
  126. //<p>需添加view.setMovementMethod(LinkMovementMethod.getInstance())</p>
  127. private var clickSpan: ClickableSpan? = null//点击事件
  128. private var url: String? = null//超链接
  129. private var textSize: Int = 0 //设置字体大小
  130. fun textSize(textSize: Float): Builder {
  131. this.textSize = DensityUtils.dp2px(textSize)
  132. return this
  133. }
  134. fun textSize(@DimenRes textSize: Int): Builder {
  135. this.textSize = ResUtils.getDimensionPixelSize(textSize)
  136. return this
  137. }
  138. fun clickSpan(clickSpan: ClickableSpan): Builder {
  139. this.clickSpan = clickSpan
  140. return this
  141. }
  142. fun url(url: String): Builder {
  143. this.url = url
  144. return this
  145. }
  146. fun align(align: Layout.Alignment): Builder {
  147. this.align = align
  148. return this
  149. }
  150. fun fontFamily(fontFamily: String): Builder {
  151. this.fontFamily = fontFamily
  152. return this
  153. }
  154. fun isSubscript(isSubscript: Boolean): Builder {
  155. this.isSubscript = isSubscript
  156. return this
  157. }
  158. fun isSuperscript(isSuperscript: Boolean): Builder {
  159. this.isSuperscript = isSuperscript
  160. return this
  161. }
  162. fun xProportion(xProportion: Float): Builder {
  163. this.xProportion = xProportion
  164. return this
  165. }
  166. fun proportion(proportion: Float): Builder {
  167. this.proportion = proportion
  168. return this
  169. }
  170. private fun getStart(): Int {
  171. var start = 0
  172. if (src.isNullOrEmpty()) {
  173. return start
  174. }
  175. if (!target.isNullOrEmpty()) {
  176. start = src.indexOf(target)
  177. }
  178. if (start > ssb.length) {
  179. start = ssb.length
  180. }
  181. return start
  182. }
  183. private fun getEnd(): Int {
  184. var end = 0
  185. if (src.isEmpty()) {
  186. return end
  187. }
  188. end = if (!target.isNullOrEmpty()) {
  189. src.indexOf(target) + target.length
  190. } else {
  191. ssb.length
  192. }
  193. if (end > ssb.length) {
  194. end = ssb.length
  195. }
  196. return end
  197. }
  198. fun start(start: Int): Builder {
  199. this.start = start
  200. return this
  201. }
  202. fun end(end: Int): Builder {
  203. this.end = end
  204. return this
  205. }
  206. fun isBold(isBold: Boolean): Builder {
  207. this.isBold = isBold
  208. return this
  209. }
  210. fun isItalic(isItalic: Boolean): Builder {
  211. this.isItalic = isItalic
  212. return this
  213. }
  214. fun isBoldItalic(isBoldItalic: Boolean): Builder {
  215. this.isBoldItalic = isBoldItalic
  216. return this
  217. }
  218. fun isUnderline(isUnderline: Boolean): Builder {
  219. this.isUnderline = isUnderline
  220. return this
  221. }
  222. fun isStrikethrough(isStrikethrough: Boolean): Builder {
  223. this.isStrikethrough = isStrikethrough
  224. return this
  225. }
  226. fun foregroundColor(@ColorRes foregroundColor: Int): Builder {
  227. this.foregroundColor = ResUtils.getColor(foregroundColor)
  228. return this
  229. }
  230. fun backgroundColor(@ColorRes backgroundColor: Int): Builder {
  231. this.backgroundColor = ResUtils.getColor(backgroundColor)
  232. return this
  233. }
  234. fun quoteColor(@ColorRes quoteColor: Int): Builder {
  235. this.quoteColor = ResUtils.getColor(quoteColor)
  236. return this
  237. }
  238. fun build(): SpannableStringBuilder {
  239. //设置所有
  240. setSpan()
  241. return ssb
  242. }
  243. private fun setSpan() {
  244. //处理start,end 防止越界
  245. if (start < 0) {
  246. start = 0
  247. } else if (start > ssb.length) {
  248. start = ssb.length
  249. }
  250. if (end < 0) {
  251. end = 0
  252. } else if (end > ssb.length) {
  253. end = ssb.length
  254. }
  255. if (isBold) {
  256. ssb.setSpan(StyleSpan(Typeface.BOLD), start, end, flag)
  257. }
  258. if (isItalic) {
  259. ssb.setSpan(StyleSpan(Typeface.ITALIC), start, end, flag)
  260. }
  261. if (isBoldItalic) {
  262. ssb.setSpan(StyleSpan(Typeface.BOLD_ITALIC), start, end, flag)
  263. }
  264. if (isUnderline) {
  265. ssb.setSpan(UnderlineSpan(), start, end, flag)
  266. }
  267. if (isStrikethrough) {
  268. ssb.setSpan(StrikethroughSpan(), start, end, flag)
  269. }
  270. if (foregroundColor != 0) {
  271. ssb.setSpan(ForegroundColorSpan(foregroundColor), start, end, flag)
  272. }
  273. if (backgroundColor != 0) {
  274. ssb.setSpan(BackgroundColorSpan(backgroundColor), start, end, flag)
  275. }
  276. if (quoteColor != 0) {
  277. ssb.setSpan(QuoteSpan(quoteColor), start, end, flag)
  278. }
  279. if (xProportion != 0f) {
  280. ssb.setSpan(ScaleXSpan(xProportion), start, end, flag)
  281. }
  282. if (proportion != 0f) {
  283. ssb.setSpan(RelativeSizeSpan(proportion), start, end, flag)
  284. }
  285. if (isSuperscript) {
  286. ssb.setSpan(SuperscriptSpan(), start, end, flag)
  287. }
  288. if (isSubscript) {
  289. ssb.setSpan(SubscriptSpan(), start, end, flag)
  290. }
  291. if (fontFamily != null) {
  292. ssb.setSpan(TypefaceSpan(fontFamily), start, end, flag)
  293. }
  294. if (align != null) {
  295. ssb.setSpan(AlignmentSpan.Standard(align!!), start, end, flag)
  296. }
  297. if (clickSpan != null) {
  298. ssb.setSpan(clickSpan, start, end, flag)
  299. }
  300. if (url != null) {
  301. ssb.setSpan(URLSpan(url), start, end, flag)
  302. }
  303. if (textSize != 0) {
  304. ssb.setSpan(AbsoluteSizeSpan(textSize), start, end, flag)
  305. }
  306. }
  307. }

}

  1. <a name="AP8TI"></a>
  2. ## 五、专门针对 kotlin 封装
  3. 对很多功能都可以封装,简化使用,这里使用了扩展函数,更方便在Kotlin中使用,不过在Java中也可以使用,使用方法如下:
  4. <a name="efe683e9"></a>
  5. #### 第一种情况,要设置的内容已经是一段完整的内容
  6. > 注意:链式调用时,只需要初始化第一个src就可以了,后续都会默认使用第一个,如果后续继续初始化src, 会导致前面的设置无效,只有最后一个生效。target和range都是为了确定要改变的文字的范围,两个初始化一个即可。
  7. 1. 对整个字符串设置效果
  8. > src 和target默认等于TextView的text

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

  1. 2. 设置部分文字效果
  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)

  1. 3. 对同一个文字设置多个效果
  2. > 对同一个部分做多种效果,只能第一个设置 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)

  1. 4. 对多个不同的文字分别设置不同的效果

tvTv5.typeSpan(range = 0..4,type = SsbKtx.type_bold_italic) .foregroundColorIntSpan(range = 7..11,color = Color.BLUE)

  1. 5. 设置部分点击

tvTv6.clickIntSpan(range = 0..4){ Toast.makeText(this, “hello”, Toast.LENGTH_SHORT).show() }

  1. 6. 设置部分超链接

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

  1. <a name="46068f3b"></a>
  2. #### 第二种情况,拼接成一个完整的字符串
  3. 1. 拼接成完整的内容

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

  1. 1. 如果想对拼接的内容做多个效果,可以在其后面调用对应的方法,只要traget或是range正确即可。
  2. <a name="6Hb8E"></a>
  3. #### 完整代码

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 {
    1. setSpan(AbsoluteSizeSpan(textSize), range.first, range.last, SsbKtx.flag)
    } }

/* 将一段指定的文字,设置类型,是否加粗,倾斜

  • @return */ fun CharSequence.typeSpan(range: IntRange, type: Int): CharSequence { return SpannableStringBuilder(this).apply {
    1. setSpan(StyleSpan(type), range.first, range.last, SsbKtx.flag)
    } }

/* 设置下划线

  • @param range
  • @return */ fun CharSequence.underlineSpan(range: IntRange): CharSequence { return SpannableStringBuilder(this).apply {
    1. setSpan(UnderlineSpan(), range.first, range.last, SsbKtx.flag)
    } }

/* 设置删除线

  • @param range
  • @return */ fun CharSequence.strikethroughSpan(range: IntRange): CharSequence { return SpannableStringBuilder(this).apply {
    1. 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 {
    1. 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 {
    1. 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 {
    1. setSpan(QuoteSpan(color), range.first, range.last, SsbKtx.flag)
    } }

/* 设置字体大小比例

  • @param range
  • @return */ fun CharSequence.proportionSpan(range: IntRange, proportion: Float): CharSequence { return SpannableStringBuilder(this).apply {
    1. setSpan(RelativeSizeSpan(proportion), range.first, range.last, SsbKtx.flag)
    } }

/* 设置横向字体大小比例

  • @param range
  • @return */ fun CharSequence.proportionXSpan(range: IntRange, proportion: Float): CharSequence { return SpannableStringBuilder(this).apply {
    1. setSpan(ScaleXSpan(proportion), range.first, range.last, SsbKtx.flag)
    } }

/* 设置上标

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

/* 设置下标

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

/* 设置字体

  • @param range
  • @return */ fun CharSequence.fontSpan(range: IntRange, font: String): CharSequence { return SpannableStringBuilder(this).apply {
    1. 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 {
    1. 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 {
    1. 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 {

    1. val clickableSpan = object : ClickableSpan() {
    2. override fun onClick(widget: View) {
    3. clickAction()
    4. }
    5. override fun updateDrawState(ds: TextPaint) {
    6. ds.color = color
    7. ds.isUnderlineText = isUnderlineText
    8. }
    9. }
    10. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. textSize == 0 -> this
    5. range != null -> {
    6. text = src.sizeSpan(range, ResUtils.getDimensionPixelSize(textSize))
    7. this
    8. }
    9. target.isNotNullOrEmpty() -> {
    10. text = src.sizeSpan(src.range(target!!), ResUtils.getDimensionPixelSize(textSize))
    11. this
    12. }
    13. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. textSize == 0f -> this
    5. range != null -> {
    6. text = src.sizeSpan(range, DensityUtils.dp2px(textSize))
    7. this
    8. }
    9. target.isNotNullOrEmpty() -> {
    10. text = src.sizeSpan(src.range(target!!), DensityUtils.dp2px(textSize))
    11. this
    12. }
    13. else -> this
    } }

/* 追加内容设置字体大小

  • @param str
  • @param textSize
  • @return */ fun TextView?.appendSizeSpan(str: String?, textSize: Float): TextView? { str?.let {
    1. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. text = src.typeSpan(range, type)
    6. this
    7. }
    8. target.isNotNullOrEmpty() -> {
    9. text = src.typeSpan(src.range(target!!), type)
    10. this
    11. }
    12. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. text = src.underlineSpan(range)
    6. this
    7. }
    8. target.isNotNullOrEmpty() -> {
    9. text = src.underlineSpan(src.range(target!!))
    10. this
    11. }
    12. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. text = src.strikethroughSpan(range)
    6. this
    7. }
    8. target.isNotNullOrEmpty() -> {
    9. text = src.strikethroughSpan(src.range(target!!))
    10. this
    11. }
    12. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. text = src.foregroundColorSpan(range, color)
    6. this
    7. }
    8. target.isNotNullOrEmpty() -> {
    9. text = src.foregroundColorSpan(src.range(target!!), color)
    10. this
    11. }
    12. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. text = src.foregroundColorSpan(range, ResUtils.getColor(color))
    6. this
    7. }
    8. target.isNotNullOrEmpty() -> {
    9. text = src.foregroundColorSpan(src.range(target!!), ResUtils.getColor(color))
    10. this
    11. }
    12. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. text = src.backgroundColorSpan(range, color)
    6. this
    7. }
    8. target.isNotNullOrEmpty() -> {
    9. text = src.backgroundColorSpan(src.range(target!!), color)
    10. this
    11. }
    12. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. text = src.backgroundColorSpan(range, ResUtils.getColor(color))
    6. this
    7. }
    8. target.isNotNullOrEmpty() -> {
    9. text = src.backgroundColorSpan(src.range(target!!), ResUtils.getColor(color))
    10. this
    11. }
    12. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. text = src.quoteColorSpan(range, color)
    6. this
    7. }
    8. target.isNotNullOrEmpty() -> {
    9. text = src.quoteColorSpan(src.range(target!!), color)
    10. this
    11. }
    12. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. text = src.quoteColorSpan(range, ResUtils.getColor(color))
    6. this
    7. }
    8. target.isNotNullOrEmpty() -> {
    9. text = src.quoteColorSpan(src.range(target!!), ResUtils.getColor(color))
    10. this
    11. }
    12. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. text = src.proportionSpan(range, proportion)
    6. this
    7. }
    8. target.isNotNullOrEmpty() -> {
    9. text = src.proportionSpan(src.range(target!!), proportion)
    10. this
    11. }
    12. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. text = src.proportionXSpan(range, proportion)
    6. this
    7. }
    8. target.isNotNullOrEmpty() -> {
    9. text = src.proportionXSpan(src.range(target!!), proportion)
    10. this
    11. }
    12. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. text = src.superscriptSpan(range)
    6. this
    7. }
    8. target.isNotNullOrEmpty() -> {
    9. text = src.superscriptSpan(src.range(target!!))
    10. this
    11. }
    12. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. text = src.subscriptSpan(range)
    6. this
    7. }
    8. target.isNotNullOrEmpty() -> {
    9. text = src.subscriptSpan(src.range(target!!))
    10. this
    11. }
    12. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. text = src.fontSpan(range, font)
    6. this
    7. }
    8. target.isNotNullOrEmpty() -> {
    9. text = src.fontSpan(src.range(target!!), font)
    10. this
    11. }
    12. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. text = src.fontSpan(range, font)
    6. this
    7. }
    8. target.isNotNullOrEmpty() -> {
    9. text = src.fontSpan(src.range(target!!), font)
    10. this
    11. }
    12. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. text = src.alignSpan(range, align)
    6. this
    7. }
    8. target.isNotNullOrEmpty() -> {
    9. text = src.alignSpan(src.range(target!!), align)
    10. this
    11. }
    12. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. movementMethod = LinkMovementMethod.getInstance()
    6. text = src.urlSpan(range, url)
    7. this
    8. }
    9. target.isNotNullOrEmpty() -> {
    10. movementMethod = LinkMovementMethod.getInstance()
    11. text = src.urlSpan(src.range(target!!), url)
    12. this
    13. }
    14. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. movementMethod = LinkMovementMethod.getInstance()
    6. highlightColor = Color.TRANSPARENT // remove click bg color
    7. text = src.clickSpan(range, color, isUnderlineText, clickAction)
    8. this
    9. }
    10. target.isNotNullOrEmpty() -> {
    11. movementMethod = LinkMovementMethod.getInstance()
    12. highlightColor = Color.TRANSPARENT // remove click bg color
    13. text = src.clickSpan(src.range(target!!), color, isUnderlineText, clickAction)
    14. this
    15. }
    16. 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 {
    1. this == null -> this
    2. src.isNullOrEmpty() -> this
    3. target.isNullOrEmpty() && range == null -> this
    4. range != null -> {
    5. movementMethod = LinkMovementMethod.getInstance()
    6. highlightColor = Color.TRANSPARENT // remove click bg color
    7. text = src.clickSpan(range, ResUtils.getColor(color), isUnderlineText, clickAction)
    8. this
    9. }
    10. target.isNotNullOrEmpty() -> {
    11. movementMethod = LinkMovementMethod.getInstance()
    12. highlightColor = Color.TRANSPARENT // remove click bg color
    13. text = src.clickSpan(
    14. src.range(target!!),
    15. ResUtils.getColor(color),
    16. isUnderlineText,
    17. clickAction
    18. )
    19. this
    20. }
    21. 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依赖。