在做Wandroid项目时有一个搜索功能,要在搜索结果中将匹配到的关键词高亮显示。但是 玩安卓API并没有提供颜色的高亮,只有字体斜体,效果看起来并不明显,并且昵称也参与了搜索,但并没有增加HTML标签返回,这就有点美中不足了。因此我们自己动手来做一个。

API返回结果

{

...

"title": "微信在Github开源了Hardcoder,对Android开发者有什么影响?",

...

}

预期效果

实现步骤

因为是支持多关键词搜索(空格分割),所以需要将关键词根据空格分割成一个至多个关键词,只要实现了单关键词的高亮,那么多关键词的高亮自然就不是问题了。

先来看看单关键词的实现步骤:

通过indexOf定位搜索关键词的索引,存在则返回关键词首个字符在整个字符串中的位置,不存在则返回-1

找出索引后截取出包含的关键词以及关键词之前的词+关键词拼接的字符串

将关键词以及关键词之前的词+关键词拼接的字符串保存在MutableList>中

遍历集合,将匹配的词使用带有HTML标签的词替换

HtmlCompat.fromHtml让TextView可识别HTML标签

看上面的实现步骤可能还是会有点懵,没关系,下面来一个一个的梳理清楚。先来看看代码

尽管是单关键词,搜索结果中还是有可能会有包含多个关键词的,比如Wandroid项目采用Kotlin语言编写,kotlin语言真好用(此处的两个kotlin的首字母大小写不一致,是为了验证后面要忽略大小写)就包含2个Kotlin语言。

拿上面的字符串Wandroid项目采用Kotlin语言编写,kotlin语言真好用来举例,搜索单关键词Kotlin语言,使其高亮显示。

// CharSequenceExt.kt

const val emStart = "" // 斜体

const val emEnd = ""

const val fontStart = "" // 字体红色

const val fontEnd = ""

fun CharSequence?.appendHtmlTags(key: String): CharSequence {

val str = "Wandroid项目采用Kotlin语言编写,kotlin语言真好用"

val key = "Kotlin语言" // 需要将后面的"kotlin语言真好用"中的kotlin语言也匹配出来

// Pair

val textArr: MutableList> = mutableListOf()

var searchIndex = 0 // 标记已经匹配过的位置,while循环中的indexOf需要从searchIndex开始,表示不重复匹配已经匹配过的字符串

// 因为有2处可匹配到关键词,所以这里用while循环遍历

while(searchIndex < str.length) { // 匹配到最后一个字符就跳出循环

val index = str.indexOf(key, searchIndex, true) // true表示忽略大小写

if(index != -1) {

// 匹配到了关键词

val keyword = str.substring(index, index + key.length) // 和key一样,只是大小写不一定一样

val text = str.substring(searchIndex, index + key.length)

searchIndex = index + key.length

textArr.add(text to keyword)

} else {

if(searchIndex != str.length) {

// 没匹配到关键词就把str和搜索关键词添加到textArr集合中

textArr.add(str.subString(searchIndex) to key)

}

}

}

val builder = StringBuilder()

textArr.forEach {

builder.append(

if (!it.first.contains(it.second, true)) {

it.first

} else

it.first.replace(

it.second,

fontStart + emStart + it.second + emEnd + fontEnd

)

)

}

return builder.toString()

}

在while第一次循环中:

val str = "Wandroid项目采用Kotlin语言编写,kotlin语言真好用" // length = 34

val key = "Kotlin语言"

val index = str.indexOf(key, 0, true) // index = 12

得到关键词key在str的第12个位置开始,index不为-1,说明匹配到了关键词,则进入以下代码

val keyword = str.substring(index, index + key.length) // str.substring(12, 12 + 8)

val text = str.substring(searchIndex, index + key.length) // str.substring(0, 12 + 8)

searchIndex = index + key.length // 12 + 8

textArr.add(text to keyword)

以上代码得出:

keyword = Kotlin语言

text = Wandroid项目采用Kotlin语言

searchIndex = 20

textArr = [(Wandroid项目采用Kotlin语言, Kotlin语言)]

在while第二次循环中:

val str = "Wandroid项目采用Kotlin语言编写,kotlin语言真好用"

val key = "Kotlin语言"

val index = str.indexOf(key, 20, true) // index = 23

这次定位索引是从字符串str的第二十的位置开始,得出index为23,则进入以下代码

val keyword = str.substring(index, index + key.length) // str.substring(23, 23 + 8)

val text = str.substring(searchIndex, index + key.length) // str.substring(20, 23 + 8)

searchIndex = index + key.length // 23 + 8

textArr.add(text to keyword)

以上代码得出:

keyword = kotlin语言 // 小写k

text = 编写,kotlin语言

searchIndex = 31

textArr = [(Wandroid项目采用Kotlin语言, Kotlin语言), (编写,kotlin语言, kotlin语言)]

目前searchIndex = 31,还没有超出str.length = 34的范围,所以还会进入第三次循环

在while第三次循环中:

val str = "Wandroid项目采用Kotlin语言编写,kotlin语言真好用"

val key = "Kotlin语言"

val index = str.indexOf(key, 31, true) // index = -1

因为str从第31位开始就只有真好用这三个字了,所以是匹配不到关键词了,index返回-1,进入了else的代码块中

if(searchIndex != str.length) { // 31 != 34 = true

// 没匹配到关键词就把str和搜索关键词添加到textArr集合中

textArr.add(str.subString(searchIndex) to key) // textArr.add("真好用" to "Kotlin语言")

}

执行完后到break处跳出循环。

到目前为止,textArr的值为:

[("Wandroid项目采用Kotlin语言", "Kotlin语言"), ("编写,kotlin语言", "kotlin语言"), ("真好用", "Kotlin语言")]

从上面textArr的结果中可以看出分词规则是这样的:

从searchIndex到index + key.length截取的字符串作为Pair.first,一个分词片段

从index到index + key.length截取的字符串作为Pair.second,保存这个是为了要还原原字符串的大小写,避免原字符串中是大写,而搜索关键词是小写,造成最后replace的时候把原字符串中的大写替换成了小写。

在这里插入图片描述

既然得出了textArr,接下来就要遍历textArr,然后依次给匹配的关键词增加HTML标签

val builder = StringBuilder()

textArr.forEach {

builder.append(

// it是textArr的每一个元素,类型是Pair

// 判断每一个分词片段是否包含关键词

if (!it.first.contains(it.second, true)) {

// 不包含关键词,直接将分词片段返回拼接

it.first

} else

// 包含关键词,将分词片段中的关键词用增加了标签的字符串替换,保持了原有字符串中的大小写

it.first.replace(

it.second,

fontStart + emStart + it.second + emEnd + fontEnd

)

)

}

想要斜体+红色字体效果,我们就要像以下格式在关键词前后添加标签

这是高亮文字

再来捋一遍,textArr中有三个元素,遍历会执行三次forEach代码块中的代码

第一次

it = ("Wandroid项目采用Kotlin语言", "Kotlin语言"),it.first = "Wandroid项目采用Kotlin语言"中包含it.second = "Kotlin语言",会执行以下代码

it.first.replace(it.second,fontStart + emStart + it.second + emEnd + fontEnd)

即,将Wandroid项目采用Kotlin语言替换成Wandroid项目采用Kotlin语言

此时的builder为Wandroid项目采用Kotlin语言

第二次

it = ("编写,kotlin语言", "kotlin语言"),it.first = "编写,kotlin语言"中包含it.second = "kotlin语言",会执行以下代码

it.first.replace(it.second,fontStart + emStart + it.second + emEnd + fontEnd)

即,将编写,kotlin语言替换成编写,kotlin语言

与第一次得到的builder拼接,此时的builder为Wandroid项目采用Kotlin语言编写,kotlin语言

第三次

it = ("真好用", "Kotlin语言"),it.first = "真好用"中不包含it.second = "Kotlin语言",会直接将it.first返回与builder拼接

即,将真好用拼接在builder后面。

此时的builder为:

Wandroid项目采用Kotlin语言编写,kotlin语言真好用

最后

textView.text = HtmlCompat.fromHtml(builder.toString(), HtmlCompat.FROM_HTML_MODE_LEGACY)

就可以显示出高亮效果了。

多关键词高亮

到目前为止,显示高亮效果的还只是单关键词,那么如何实现多关键词高亮效果呢?单关键词的思路已经有了,其实多关键词高亮只需要将多关键词分割成一个个的单关键词就好了。

假设多关键词用空格分开,我们就需要用key.split(" ")将一个关键词字符串分割成多个关键词,避免一些不规范的输入,如前后有空格或者中间有不止一个空格,那就要消除这些不规范。

// 替换所有空格为一个空格

fun String?.replaceAllEmptyToOne(): String {

if (this.isNullOrEmpty()) return ""

val pattern = Pattern.compile("\\s+")

val matcher = pattern.matcher(this)

return matcher.replaceAll(" ")

}

val keys = key.trim() // 去除首尾空格

.toLowerCase(Locale.getDefault()) // 全部转成小写,忽略大小写

.replaceAllEmptyToOne() // 将多个连续的空格替换成一个

.splt(" ") // 分割关键词

.toSet() // 去除重复的关键词

通过一系列的操作得到的keys是一个比较规范的关键词组,通过遍历依次对每个单关键词进行高亮操作即可。

// CharSequenceExt.kt

// 多关键词高亮

fun CharSequence?.makeTextHighlightForMultiKeys(key: String): CharSequence {

if (this.isNullOrEmpty()) return ""

val keys = key.trim().toLowerCase(Locale.getDefault()).replaceAllEmptyToOne().split(" ").toSet()

var result: CharSequence = this

keys.forEach {

result = result.appendHtmlTags(it) // 对每个单关键词添加HTML标签

}

return HtmlCompat.fromHtml(result.toString(), HtmlCompat.FROM_HTML_MODE_LEGACY)

}

// MainActivity.kt

val text = "Wandroid项目采用Kotlin语言编写,kotlin语言真好用"

val result = text.makeTextHighlightForMultiKeys("Wandroid Kotlin")

tvText.text = result

到此,已经全部完成了,下面附上完整代码

import androidx.core.text.HtmlCompat

import java.util.*

import java.util.regex.Pattern

const val emStart = ""

const val emEnd = ""

const val fontStart = ""

const val fontEnd = ""

// 多关键词高亮

fun CharSequence?.makeTextHighlightForMultiKeys(key: String): CharSequence {

if (this.isNullOrEmpty()) return ""

val keys = key.trim().toLowerCase(Locale.getDefault()).replaceAllEmptyToOne().split(" ").toSet()

var result: CharSequence = this

keys.forEach {

result = result.appendHtmlTags(it)

}

return HtmlCompat.fromHtml(result.toString(), HtmlCompat.FROM_HTML_MODE_LEGACY)

}

// 搜索到的标题文本标红处理,返回的文本带有标签

fun String?.toSearchTitleColorString(): CharSequence {

return if (this.isNullOrEmpty()) "" else HtmlCompat.fromHtml(

if (this.contains(emStart)) {

this.replace(emStart, fontStart + emStart)

.replace(emEnd, emEnd + fontEnd)

} else {

this

},

HtmlCompat.FROM_HTML_MODE_LEGACY

)

}

fun CharSequence?.appendHtmlTags(key: String): CharSequence {

if (this.isNullOrEmpty()) return ""

if (!this.contains(key, true)) return this

// 解析出整个字符串中所有包含key的位置

val textArr: MutableList> = mutableListOf()

var searchIndex = 0

while (searchIndex < this.length) {

val index = this.indexOf(key, searchIndex, true)

if (index != -1) {

// 能匹到

val keyword = this.substring(index, index + key.length) // 和key一样,只是大小写不一定一样

val text = this.substring(searchIndex, index + key.length)

searchIndex = index + key.length

textArr.add(text to keyword)

} else {

if (searchIndex != length) {

// 还有字符串

textArr.add(substring(searchIndex) to key)

}

break

}

}

val builder = StringBuilder()

textArr.forEach {

builder.append(

if (!it.first.contains(it.second, true)) {

it.first

} else

it.first.replace(

it.second,

fontStart + emStart + it.second + emEnd + fontEnd

)

)

}

return builder.toString()

}

// 替换所有空格为一个空格

fun String?.replaceAllEmptyToOne(): String {

if (this.isNullOrEmpty()) return ""

val pattern = Pattern.compile("\\s+")

val matcher = pattern.matcher(this)

return matcher.replaceAll(" ")

}

it.first.replace(

it.second,

fontStart + emStart + it.second + emEnd + fontEnd

)

)

}

return builder.toString()

}

// 替换所有空格为一个空格

fun String?.replaceAllEmptyToOne(): String {

if (this.isNullOrEmpty()) return ""

val pattern = Pattern.compile("\\s+")

val matcher = pattern.matcher(this)

return matcher.replaceAll(" ")

}

android关键词检索功能,Android实现搜索关键词高亮显示-Kotlin相关推荐

  1. android的UDC功能,Android实现搜索历史功能

    本文实例为大家分享了Android实现搜索历史的具体代码,供大家参考,具体内容如下 SharedPreferences实现本地搜索历史功能,覆盖搜索重复的文本,可清空 1. 判断搜索内容是否含表情,不 ...

  2. 百度地图 Android SDK - 检索功能使用的简单演示样例

    百度地图 SDK 不仅为广大开发人员提供了炫酷的地图展示效果.丰富的覆盖物图层,更为广大开发人员提供了多种 LBS 检索的能力. 通过这些接口,开发人员能够轻松的訪问百度的 LBS 数据,丰富自己的移 ...

  3. android实现标签功能,Android实现热门标签的流式布局

    一.概述: 在日常的app使用中,我们会在android 的app中看见 热门标签等自动换行的流式布局,今天,我们就来看看如何 自定义一个类似热门标签那样的流式布局吧(源码下载在下面最后给出) 类似的 ...

  4. android播放mp3功能,Android Studio实现简单音乐播放功能的示例代码

    项目要求 基于Broadcast,BroadcastReceiver等与广播相关的知识实现简单的音乐播放功能,包括音乐的播放.暂停.切换.进度选择.音量调整. 设计效果 (进度条时间刷新功能还没有实现 ...

  5. android 添加附件功能,Android实现带附件的邮件发送功能

    本文实例讲解了基于基于jmail实现android邮件发送功能,分享给大家供大家参考,具体内容如下 在android上发送邮件方式: 第一种:借助gmail app客户端,缺点是必须使用gmail帐号 ...

  6. android sharesdk分享功能,Android ShareSDK快速实现分享功能

    第一步 :获取ShareSDK 为了集成ShareSDK,您首先需要到ShareSDK官方网站注册并且创建应用,获得ShareSDK的Appkey,然后到SDK的下载页面下载SDK的压缩包,解压以后可 ...

  7. android系统应用功能,Android系统应用(12)

    如何成为系统应用 方法一:在Manifest中声明android:sharedUserId的值为:android.uid.system,android.uid.phone,android.uid.lo ...

  8. android相册幻灯片功能,Android实现幻灯片式图片浏览器

    我们来实现一个幻灯片式图片浏览器: 最下面一个画廊视图,选中画廊中的图片,会在上面的ImageSwitcher控件中显示大图. 效果图如图 实现方法: 在布局文件中添加图片切换控件ImageSwitc ...

  9. android放微信@功能,Android仿微信语音消息的录制和播放功能

    一.简述 效果: 实现功能: 长按Button时改变Button显示文字,弹出Dialog(动态更新音量),动态生成录音文件,开始录音: 监听手指动作,规定区域.录音状态下手指划出规定区域取消录音,删 ...

最新文章

  1. 使用注解开发SpringMVC详细配置教程
  2. 四种JOIN简单实例
  3. 一文带你了解卷积神经网络基础,建议收藏
  4. spring 源代码地址
  5. Windows——Modern Standby(现代待机) S0改Suspend to RAM(待机到内存)S3睡眠解决方案(以机械革命F1 i5-11300H为例)
  6. 自定义UserControl的属性为什么不能在设计时显示在属性窗口中
  7. linux创建vnc服务器,五步建立一个VNC Linux服务器
  8. php用wordanalysis抓取姓名_利用vba查询/抓取 外部数据
  9. mysql 语句账号注入_mysql中SQL语句的注入问题
  10. Linux FTP安装问题
  11. puts遇到空格无法输出_ACM输出超限|puts与printf
  12. 身为开发人员,这些数据库合知识不掌握不合适!
  13. 打印1到最大的n位数
  14. 路由事件(鼠标路由事件+键盘路由事件)
  15. 柯洁将在年内和“阿尔法狗”进行终极人机大战
  16. 用python计算圆环面积公式_圆环的计算公式,一看就懂的
  17. python摄像头人脸识别代码_Python3利用Dlib19.7实现摄像头人脸识别的方法
  18. 污染土壤修复可以采取哪些方式
  19. Could not find artifact xxx.xxx:ww-www-ww:pom:1.0.1-SNAPSHOT in xxxx(http://xxx.xxx.xxx:xxxx私服地址)
  20. 图片Base64编码 图片Base64在线转换

热门文章

  1. 如何排出清理体内毒素让身体轻松没污染 - 生活至上,美容至尚!
  2. PKI/CA: Win2012 R2标准版 分布式部署AD域控环境的智能卡登陆配置问题记录_20180919_七侠镇莫尛貝
  3. superset安装使用说明
  4. 苹果软件系列产品介绍
  5. Git系列——win 10配置git环境
  6. java中遇到过的String的一些特性
  7. 股票策略 —— 戴维斯双击
  8. android日历总结,Android中Calendar类的用法总结
  9. cad2014卡顿的解决方法_AutoCAD 2012运行卡顿怎么办-cad卡顿解决办法
  10. 道德经的读书笔记范文3300字