本文正在参加「金石计划」

日常开发中可能会遇到给 TextView 的全部或部分文本增加高亮作用的需求,以前可能是经过 Spannable 或者 Html 标签完成。

升级 Android 14 后就不用这么迂回了,因其首次引入直接设置高亮的 API:HighLights。需求留心的是 HighLights API 和 Android 1.0 即加入的 textColorHighlight API 不同:

  1. 14 的新 API 就是文本在 normal 状态下的高亮,之前这个是为了设置选中时文本高亮,
  2. 14 的新 API 只提供了 get/set 方法,没有提供与之匹配的 attribute。而之前的 API还提供了 android:textColorHighlight attribute 装备

下面咱们就来一探这个新 API 的玩法和 textColorHighlight API 的区别。

目录前瞻:

  1. 设置高亮
  2. 获取高亮
  3. 动态更新高亮
  4. 与选中时作用是否冲突
  5. 结语

1. 设置高亮

HighLights 采用的是熟知的制作者形式,即首要需求构建不同参数的 Builder 实例,针对参数也提供了两种设置方法:

  1. 一次指定单组高亮装备:addRange(Paint paint, int start, int end),假如多组需求设置相同高亮色彩的话,那要调用屡次

  2. 一次指定多组高亮装备:addRange(Paint paint, int… ranges),假如多组需求设置相同高亮色彩的话,只要调用一次即可

    已然是多组规模那么 int 参数必须是偶数数目的,即成对呈现,反之会发生如下的 Exception:

    java.lang.IllegalArgumentException: Flatten ranges must have even numbered elements

能够说上述两个 API 的参数都是成对呈现,关于高亮的呼应规模的话:前者是包括 inclusive 在内的,后者是不包括 exclusive 在内的,需求留意。

咱们经过代码实例演示经过上述两个 Builder API 构建一样的高亮作用,然后经过 TextViewsetHighLights() 反映。

class MainActivity : AppCompatActivity() {
  companion object {
    const val TEXT = "val builder = Highlights.Builder()"
   }
​
  override fun onCreate(savedInstanceState: Bundle?) {
     ...
    val yellowPaint = Paint().apply {
      color = Color.YELLOW
     }
​
    val greenPaint = Paint().apply {
      color = Color.GREEN
     }
​
    with(binding.textview1) {
      text = TEXT
      val builder = Highlights.Builder()
         .addRange(yellowPaint, 0, 3)
         .addRange(greenPaint, 14, 24)
         .addRange(greenPaint, 25, 32)
      highlights = builder.build()
     }
​
    with(binding.textview2) {
      text = TEXT
      val builder = Highlights.Builder()
         .addRanges(yellowPaint, 0, 3)
         .addRanges(greenPaint, 14, 24, 25, 32)
      highlights = builder.build()
     }
   }
}

能够看到不同的 Builder 参数设置方法能够对 val 设置黄色高亮,HighlightsBuilder 设置绿色高亮。

Android 14 新功能之 HighLights:快速实现文本高亮~

2. 获取高亮

设置到 TextView 对象的 HighLights 实例还能够经过 getHighlights() 获取,并经过如下的 API 获取高亮的细节:

  • 首要经过 getSize() 获取设置高亮的数量

  • 其次从 0 开端遍历下标

    • 经过 getPaint(int index) 获取高亮的 Paint 对象
    • 以及经过 getRanges(int index) 获取对应的 Paint 规模 Ranges(也是一个数组,需求遍历打印具体的起始位置)
class MainActivity : AppCompatActivity() {
   ...
  override fun onCreate(savedInstanceState: Bundle?) {
     ...
    binding.textview1.highlights?.run {
      Log.d("HighLights", "textview1 usedHighLights' size:$size")
​
      for (i in 0 until size) {
        Log.d("HighLights", "usedHighLights'" +
            " paint:${getPaint(i).color.toColorString()}")
        val range = getRanges(i)
        for (j in range.indices) {
          Log.d("HighLights", "ranges:${range[j]}")
         }
       }
     }
​
    binding.textview2.highlights?.run {
      Log.d("HighLights", "textview2 usedHighLights' size:$size")
​
      for (i in 0 until size) {
        Log.d("HighLights", "usedHighLights'" +
            " paint:${getPaint(i).color.toColorString()}")
        val range = getRanges(i)
        for (j in range.indices) {
          Log.d("HighLights", "ranges:${range[j]}")
         }
       }
     }
   }
}

如下的 log 能够看到打印出来的 Paint 色彩、规模 Ranges 和设置的参数是一一对应的。

03-23 23:08:27.196 7182 7182 D HighLights: textview1 usedHighLights' size:3
03-23 23:08:27.196 7182 7182 D HighLights: usedHighLights' paint:YELLOW
03-23 23:08:27.196 7182 7182 D HighLights: ranges:0
03-23 23:08:27.196 7182 7182 D HighLights: ranges:3
03-23 23:08:27.196 7182 7182 D HighLights: usedHighLights' paint:GREEN
03-23 23:08:27.196 7182 7182 D HighLights: ranges:14
03-23 23:08:27.196 7182 7182 D HighLights: ranges:24
03-23 23:08:27.196 7182 7182 D HighLights: usedHighLights' paint:GREEN
03-23 23:08:27.196 7182 7182 D HighLights: ranges:25
03-23 23:08:27.196 7182 7182 D HighLights: ranges:32

03-23 23:08:27.196 7182 7182 D HighLights: textview2 usedHighLights' size:2
03-23 23:08:27.196 7182 7182 D HighLights: usedHighLights' paint:YELLOW
03-23 23:08:27.196 7182 7182 D HighLights: ranges:0
03-23 23:08:27.196 7182 7182 D HighLights: ranges:3
03-23 23:08:27.196 7182 7182 D HighLights: usedHighLights' paint:GREEN
03-23 23:08:27.196 7182 7182 D HighLights: ranges:14
03-23 23:08:27.196 7182 7182 D HighLights: ranges:24
03-23 23:08:27.196 7182 7182 D HighLights: ranges:25
03-23 23:08:27.196 7182 7182 D HighLights: ranges:32

3. 动态更新高亮

已然咱们能够获取现已设置的 HighLights,那么更新其属性,能否动态更新高亮作用呢?

  1. 首要在 TextView 下增加动态更新 HighLights 的 Button

Android 14 新功能之 HighLights:快速实现文本高亮~
  1. 然后点击该 Button 之后将 textView1 的 Paint 色彩从 GREEN 改为 BLUE,并将其间 “Highlights” 的文本规模增大:头尾各扩展一个或多个下标
class MainActivity : AppCompatActivity() {
   ...
  override fun onCreate(savedInstanceState: Bundle?) {
     ...
    binding.changeHighlights.setOnClickListener {
      Log.d("HighLights", "changeHighlights tapped & change highlights")
      textView1Highlights?.apply {
        // Change color
        getPaint(1).color = Color.BLUE
        // Change ranges
        getRanges(1)[0] -= 3
        getRanges(1)[1] += 1for (i in 0 until size) {
          Log.d("HighLights", "textView1Highlights'" +
              " paint:${getPaint(i).color.toColorString()}")
          val range = getRanges(i)
          for (j in range.indices) {
            Log.d("HighLights", "ranges:${range[j]}")
           }
         }
       }
​
      binding.textview1.invalidate()
     }
   }
}

点击 Button 之后,色彩确实变成了蓝色,可是高亮规模却没有改变。

Android 14 新功能之 HighLights:快速实现文本高亮~

咱们打印的更新后 HighLights 的参数 log:能够看到,无论是色彩(GREEN -> BLUE)仍是规模(14 -> 11,24 -> 25)确实都现已更改了。

可为什么唯独 Ranges 没有改写?有可能是 14 预览版阶段的 Bug。

03-25 10:47:29.276 5344 5344 D HighLights: changeHighlights tapped & change highlights
03-25 10:47:29.276 5344 5344 D HighLights: textview1 textView1Highlights' size:3
03-25 10:47:29.276 5344 5344 D HighLights: textView1Highlights' paint:YELLOW
03-25 10:47:29.276 5344 5344 D HighLights: ranges:0
03-25 10:47:29.276 5344 5344 D HighLights: ranges:3
03-25 10:47:29.277 5344 5344 D HighLights: textView1Highlights' paint:BLUE
03-25 10:47:29.277 5344 5344 D HighLights: ranges:11
03-25 10:47:29.277 5344 5344 D HighLights: ranges:25
03-25 10:47:29.277 5344 5344 D HighLights: textView1Highlights' paint:BLUE
03-25 10:47:29.277 5344 5344 D HighLights: ranges:25
03-25 10:47:29.277 5344 5344 D HighLights: ranges:32

4. 与选中时作用是否冲突

咱们给上述其间一个 TextView 增加选中高亮色彩的装备即 textColorHighlight,该色彩与上述 HighLights 色彩不同,以明晰地判断两种高亮是否会发生冲突。

留意需求将 textIsSelectable 设置为 true,这样 TextView 才能够被长按选中。

<androidx.constraintlayout.widget.ConstraintLayout ... >
​
  <TextView
    android:id="@+id/textview1"
    ...
    android:textColorHighlight="@color/purple_200"
    android:textIsSelectable="true"
    ... />
  < ... >
</androidx.constraintlayout.widget.ConstraintLayout>

咱们在该 TextView 上长按看一下作用:

Android 14 新功能之 HighLights:快速实现文本高亮~

能够看到水滴选中的规模内会变成咱们设置的 textColorHighlight 紫色高亮,未选中的部分会按照 HighLights 装备的那样展示黄色和绿色以及没有设置 HighLights 的默认浅灰色。

5. 结语

能够看到新功能 HighLights 能够使得高亮的处理变得简单、易用,大家能够在 14 上采用该 API,当高版别遍及后,低版别上的自定义高亮逻辑就能够舍弃了。

至于其原理,因为 Android 14 尚处于预览版阶段、源码没有公开,无法获悉完成。但估量是 TextViewdraw 阶段会获取设置的 HighLights 包括的 size 以及对应的 PaintRanges,得以明晰地掌握各高亮的色彩和对应的规模,然后直接调用 CanvasdrawText(text, start, end, x, y, paint) 去完成绘制。

能够说 HighLights 这种 API 既方便了开发者的运用:从设置高亮到获取高亮到动态更新高亮,其明晰的逻辑必定程度上也能够简化 SDK 的完成。

事实上 Android 14 还针对 TextView 做了其他新功能的支撑,比方设置文内搜索成果的文本高亮、索引,后续同时进行解读:

  • setSearchResultHighlightColor(int color):设置一切匹配到搜索关键字的文本色彩
  • setSearchResultHighlights(int… ranges):设置一切匹配到搜索关键字的文本高亮 HighLights 的规模
  • setFocusedSearchResultHighlightColor(int color):设置当时聚集到的匹配关键字的文本色彩
  • setFocusedSearchResultIndex(int index):设置当时聚集到的匹配关键字的索引

参考

  • Android 14 Highlights
  • TextView’s setHighLights()
  • Spot on: Android 14 adds highlights to TextViews