Highlight search results in ListView

This what I use :

  • Every occurence is replaced (not only prefix)
  • Case and accent are ignored while searching but retained in the result.
  • It uses directly SpannableString, which you can use in setText(). I believe it's more efficient than using an intermediate html step.

.

public static CharSequence highlight(String search, String originalText) {
    // ignore case and accents
    // the same thing should have been done for the search text
    String normalizedText = Normalizer.normalize(originalText, Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", "").toLowerCase();

    int start = normalizedText.indexOf(search);
    if (start < 0) {
        // not found, nothing to to
        return originalText;
    } else {
        // highlight each appearance in the original text
        // while searching in normalized text
        Spannable highlighted = new SpannableString(originalText);
        while (start >= 0) {
            int spanStart = Math.min(start, originalText.length());
            int spanEnd = Math.min(start + search.length(), originalText.length());

            highlighted.setSpan(new BackgroundColorSpan(<background_color>), spanStart, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

            start = normalizedText.indexOf(search, spanEnd);
        }

        return highlighted;
    }
}

The accepted answer is nice. But you can do it by a single line of code. What I've done in my case to avoid the case sensitive issue is:

Spannable sb = new SpannableString(originalText);
                    sb.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), originalText.toLowerCase().indexOf(query.toLowerCase()),
                            originalText.toLowerCase().indexOf(query.toLowerCase()) + query.length(),
                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
result.setText(sb);

Hope it might help! Note: Here 'query' is the part of the string that you want to highlight.


Simple & Advanced Search Highlighting Example [Case Insensitive Order]

1. Simple Search (Html):

public static void setSearchTextHighlightSimpleHtml(TextView textView, String fullText, String searchText) {
    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            fullText = fullText.replaceAll("(?i)(" + searchText + ")", "<span style=\"background-color:#FCFF48;\"><b><big><font color='#a10901'>$1</font></big></b></span>");
            textView.setText(Html.fromHtml(fullText, Html.FROM_HTML_MODE_LEGACY), TextView.BufferType.SPANNABLE);
        } else {
            fullText = fullText.replaceAll("(?i)(" + searchText + ")", "<b><big><font color='red'>$1</font></big></b>");
            textView.setText(Html.fromHtml(fullText), TextView.BufferType.SPANNABLE);
        }
    } catch (Exception e) {
        textView.setText(fullText);
    }
}

2. Simple Search (Spannable):

public static void setSearchTextHighlightSimpleSpannable(TextView textView, String fullText, String searchText) {

    // highlight search text
    if (null != searchText && !searchText.isEmpty()) {

        SpannableStringBuilder wordSpan = new SpannableStringBuilder(fullText);
        Pattern p = Pattern.compile(searchText, Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(fullText);
        while (m.find()) {

            int wordStart = m.start();
            int wordEnd = m.end();

            // Now highlight based on the word boundaries
            ColorStateList redColor = new ColorStateList(new int[][]{new int[]{}}, new int[]{0xffa10901});
            TextAppearanceSpan highlightSpan = new TextAppearanceSpan(null, Typeface.BOLD, -1, redColor, null);

            wordSpan.setSpan(highlightSpan, wordStart, wordEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            wordSpan.setSpan(new BackgroundColorSpan(0xFFFCFF48), wordStart, wordEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            wordSpan.setSpan(new RelativeSizeSpan(1.25f), wordStart, wordEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

        }

        textView.setText(wordSpan, TextView.BufferType.SPANNABLE);

    } else {
        textView.setText(fullText);
    }
}

3. Advanced Search (Spannable):

public static void setAdvancedSearch(TextView textView, String fullText, String searchText) {

    if (searchText.length() == 0) {
        textView.setText(fullText);
        return;
    }

    final String searchBoundary = " \n()।.,;?-+!";
    char[] boundaries = searchBoundary.toCharArray();

    // highlight search text
    if (isNotEquals(searchText, boundaries)) {

        SpannableStringBuilder wordSpan = new SpannableStringBuilder(fullText);
        Pattern p = Pattern.compile(searchText, Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(fullText);
        while (m.find()) {

            int wordStart = m.start();
            while (wordStart >= 0 && isNotEquals(fullText.charAt(wordStart), boundaries)) {
                --wordStart;
            }
            wordStart = wordStart + 1;

            int wordEnd = m.end();
            while (wordEnd < fullText.length() && isNotEquals(fullText.charAt(wordEnd), boundaries)) {
                ++wordEnd;
            }

            setWordSpan(wordSpan, wordStart, wordEnd);

        }

        textView.setText(wordSpan, TextView.BufferType.SPANNABLE);

    } else {
        textView.setText(fullText);
    }
}

private static boolean isNotEquals(char charAt, char[] boundaries) {
    return isNotEquals(String.valueOf(charAt), boundaries);
}

private static boolean isNotEquals(String searchText, char[] boundaries) {
    for (char boundary : boundaries) {
        boolean equals = searchText.equals(String.valueOf(boundary));
        if (equals) return false;
    }
    return true;
}

private static void setWordSpan(SpannableStringBuilder wordSpan, int wordStart, int wordEnd) {
    // Now highlight based on the word boundaries
    ColorStateList redColor = new ColorStateList(new int[][]{new int[]{}}, new int[]{0xffa10901});
    TextAppearanceSpan highlightSpan = new TextAppearanceSpan(null, Typeface.BOLD, -1, redColor, null);

    wordSpan.setSpan(highlightSpan, wordStart, wordEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    wordSpan.setSpan(new BackgroundColorSpan(0xFFFCFF48), wordStart, wordEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    wordSpan.setSpan(new RelativeSizeSpan(1.25f), wordStart, wordEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}