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

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



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] 就可以了。修改后代码如下:

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;
    }
}

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

public class TextHighLight {

    /**
     *   关键字高亮显示
     *   
     *   @param text 文字
     *      
     *   @param keyword1 文字中的关键字数组
     *              
     *   @return
     *  
     */
    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()的方法里复制出来关键字的数组,然后再去匹配。这里采用的是第二种方案。修改后代码如下:

public class TextHighLight {

    /**
     *   关键字高亮显示
     *   
     *   @param text 文字
     *      
     *   @param keyword1 文字中的关键字数组
     *              
     *   @return
     *  
     */
    public static SpannableStringBuilder matcherSearchContent(String text, String[] keyword1) {
        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;
    }
}

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

好库推荐

给大家推荐一下BannerViewPager。这是一个基于ViewPager实现的具有强大功能的无限轮播库。通过BannerViewPager可以实现腾讯视频、QQ音乐、酷狗音乐、支付宝、天猫、淘宝、优酷视频、喜马拉雅、网易云音乐、哔哩哔哩等APP的Banner样式以及指示器样式。

欢迎大家到github关注BannerViewPager

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值