公司项目中有一个搜索功能,需求是要把搜索关键字高亮显示出来。本来觉得是一个很简单的问题,开发过程也比较顺利。但在测试阶段还是出了些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 {
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 {
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; } }
|
到这里关于关键字高亮的所有问题总算是解决了。