Android搜索关键字高亮显示及开发中遇到的坑

公司项目中有一个搜索功能,需求是要把搜索关键字高亮显示出来。本来觉得是一个很简单的问题,开发过程也比较顺利。但在测试阶段还是出了些Bug。而且有些问题还挺莫名其妙!
最初的想法,定义一个工具类,然后封装一个处理高亮的方法,需要高亮显示的直接调用这个工具类就行了。工具类封装了一个matcherSearchContent()的静态方法,然后用SpannableStringBuilder去处理关键字高亮显示,处理完成后方法返回处理好的SpannableStringBuilder。然后就可以直接给TextView去set处理好的SpannableStringBuilder。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TextHighLight {

public static SpannableStringBuilder matcherSearchContent(String text,String[] keyword){
SpannableStringBuilder spannable=new SpannableStringBuilder(text);
CharacterStyle span=null;
for(int i=0;i<keyword.length;i++){
Pattern pattern=Pattern.compile(keyword[i]);
Matcher matcher=pattern.matcher(text);
while(m.find()){
span=new ForegroundColorSpan(Color.parseColor("#ff5656"));
spannable.setSpan(span,m.start(),matcher.end(), Spannable.SPAN_MARK_MARK);
}
}
return spannable;
}
}

但是后来需求说字母是要忽略大小写的。这个该怎么实现啊,用逻辑实现还是很麻烦的。还好,可以用到正则表达式来完成加上一行代码 “(?i)” + keyword[i] 就可以了。修改后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TextHighLight {

public static SpannableStringBuilder matcherSearchContent(String text,String[] keyword){
SpannableStringBuilder spannable=new SpannableStringBuilder(text);
CharacterStyle span=null;
for(int i=0;i<keyword.length;i++){
wordReg = "(?i)" + keyword[i]; //忽略字母大小写
Pattern pattern = Pattern.compile(wordReg);
Matcher matcher=pattern.matcher(text);
while(m.find()){
span=new ForegroundColorSpan(Color.parseColor("#ff5656"));
spannable.setSpan(span,m.start(),matcher.end(), Spannable.SPAN_MARK_MARK);
}
}
return spannable;
}
}

嗯 这个解决的比较轻松。接下来测试阶段出现的问题。当输入特殊字符比如“*”时,程序出现异常闪退。调试发现原因是因为“*”号导致。在正则表达式中“*”是通配符,因此在匹配正则表达式时出现了异常。。解决办法也比较简单吗,就是判读如果包含“*”号的话就加上一个反斜杠转义一下就行了嘛!于是就有了下面的代码(因为测试的时候只有搜索“*“、”(“、”)”时出现闪退,因此只对这三个字符作了处理)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class TextHighLight {

/**
* 关键字高亮显示
* @param text 文字
* @param keyword 文字中的关键字数组
*/
public static SpannableStringBuilder matcherSearchContent(String text, String[] keyword) {
SpannableStringBuilder spannable = new SpannableStringBuilder(text);

CharacterStyle span;
String wordReg;
for (int i = 0; i < keyword.length; i++) {
String key = "";
// 处理通配符问题
if (keyword[i].contains("*") || keyword[i].contains("(") || keyword[i].contains(")")) {
char[] chars = keyword[i].toCharArray();
for (int k = 0; k < chars.length; k++) {
if (chars[k] == '*' || chars[k] == '(' || chars[k] == ')') {
key = key + "\\" + String.valueOf(chars[k]);
} else {
key = key + String.valueOf(chars[k]);
}
}
keyword[i] = key;
}

wordReg = "(?i)" + keyword[i]; //忽略字母大小写
Pattern pattern = Pattern.compile(wordReg);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
span = new ForegroundColorSpan(Color.parseColor("#ff5656"));
spannable.setSpan(span, matcher.start(), matcher.end(), Spannable.SPAN_MARK_MARK);
}
}

return spannable;
}
}

上边代码先判断了字符串中是否包含“*”、”(”、”)”号,如果包含的话就遍历字符串然后在“*、”(”、”)”号前边加上反斜杠转义。嗯 这次一定没问题了。然后运行测试,跑起来看效果。嗯,果真正常了!以为这样就完了那就太天真了。当在一级搜索页面测试的时候一个很奇怪的问题出现了!!输入“*”搜索时发现“*”竟然没有高亮显示!效果图如下:

一级搜索页面 二级页面搜索
二级搜索页面 一级搜索页面

很诡异,有木有!两个页面调用的同一个Adapter,调用的同一个处理高亮的方法,为什么一个显示了高亮效果而一个却不正常显示呢!接下来经过漫长的调试、打log、对比两个页面。看下面的log日志信息:
这里写图片描述
嗯?要匹配的字符串怎么变成了“\ \ *”了?正常来说应该匹配“\“ 的这样在正则表达式中”\“正好被转义成“”的,难怪出现没有匹配高亮显示,问题就出在了上边的for循环添加”"那段代码。看上边代码发现经过处理转义字符后将keyword[i]进行了一次重新赋值,而调用处理高亮的方法时传过来的参数关键字数组仅仅是一个引用,而实际的数组是在堆内存中存储的,因此重新赋值致使堆内存数据发生了改变!
既然找到了问题所在就应该分析引起问题的原因,为什么转义字符会被添加了两次?
调试的时候发现getView()方法确实是被重复执行了多次。这个原因就很奇怪了,按理说getView()方法的调用应该是跟ListView条目对应的。即有多少条数据,getView()就会被调用多少次。但是为什么在这个页面getView()会被重复执行,而在另一个页面getView()却只调用了一次?后来通过查阅资料发现是由于ListView的布局原因引起的。**
当ListView的layout_height属性设置为wrap_content的时候,getView()就会出现被重复调用的问题!,而layout_height设置位match_parent的时候getView()的调用则是正常的***,现在再会过头来看两个页面的布局,上边第一张图片是出现问题的布局,因为该页面有多个ListView,因此单个ListView的layout_height都被定义成了wrap_content从而出现了上述getView()被重复调用问题。而第二张图片中只有一个ListView,layout_height属性设置的是match_parent,因此这个页面getView()只被调用了一次,所以高亮显示也就正常了!

问题原因找到了,也就好解决了。解决的方案有两个,第一就是更改ListView()的layout_height属性,给其设置为定值。第二就是在matcherSearchContent()的方法里复制出来关键字的数组,然后再去匹配。这里采用的是第二种方案。修改后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class TextHighLight {

/**
* 关键字高亮显示
* @param text 文字
* @param keyword 文字中的关键字数组
*/
public static SpannableStringBuilder matcherSearchContent(String text, String[] keyword) {
String[] keyword = new String[keyword1.length];
System.arraycopy(keyword1, 0, keyword, 0, keyword1.length);
SpannableStringBuilder spannable = new SpannableStringBuilder(text);

CharacterStyle span;
String wordReg;
for (int i = 0; i < keyword.length; i++) {
String key = "";
// 处理通配符问题
if (keyword[i].contains("*") || keyword[i].contains("(") || keyword[i].contains(")")) {
char[] chars = keyword[i].toCharArray();
for (int k = 0; k < chars.length; k++) {
if (chars[k] == '*' || chars[k] == '(' || chars[k] == ')') {
key = key + "\\" + String.valueOf(chars[k]);
} else {
key = key + String.valueOf(chars[k]);
}
}
keyword[i] = key;
}

wordReg = "(?i)" + keyword[i]; //忽略字母大小写
Pattern pattern = Pattern.compile(wordReg);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
span = new ForegroundColorSpan(Color.parseColor("#ff5656"));
spannable.setSpan(span, matcher.start(), matcher.end(), Spannable.SPAN_MARK_MARK);
}
}

return spannable;
}
}

到这里关于关键字高亮的所有问题总算是解决了。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!