Highlight search results in ListView

I have a ListView with String s. In the code below, I can highlight the search results, but the user must enter case-sensitive search words. How can I implement fuzzy sensitive highlighting of search results, for example, as a search based on Android contacts?

Here is my code for highlighting. I extend the ArrayAdapter and implement a custom filter to get a search string. In the getView method getView I check if my String in the ListView prefixString and selects it.

 public class HighlightListAdapter extends ArrayAdapter { ArrayList<String> objects; final Object mLock =new Object(); private ArrayList<String> mOriginalValues; private ArrayFilter filter; private String prefixString; public AuthorsListAdapter(Context context, int textViewResourceId, ArrayList<String> objects) { super(context, textViewResourceId, objects); this.objects = objects; } class ViewHolder{ TextView author; } public View getView(final int position, View convertView, ViewGroup parent){ // assign the view we are converting to a local variable View v = convertView; ViewHolder holder = null; // first check to see if the view is null. if so, we have to inflate it. // to inflate it basically means to render, or show, the view. LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (v == null) { holder = new ViewHolder(); v = inflater.inflate(R.layout.author_list_item, null); holder.author =(TextView) v.findViewById(R.id.author_list_item_text); v.setTag(holder); }else{ holder = (ViewHolder) v.getTag(); } final String author = objects.get(position); if (author != null) { holder.author.setText(author); if(prefixString !=null && prefixString.length()>1){ String s = author; **if(s.contains(prefixString)){ String rep = s.replace(prefixString, "<b><font color=#2825A6>"+ prefixString+ "</font></b>"); holder.author.setText(Html.fromHtml(rep)); }** // higlight } } return v; } @Override public int getCount() { // TODO Auto-generated method stub return objects.size(); } @Override public Filter getFilter() { // TODO Auto-generated method stub if(filter == null){ filter =new ArrayFilter(); } return filter; } @Override public Object getItem(int position) { // TODO Auto-generated method stub return this.objects.get(position); } private class ArrayFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence prefix) { FilterResults results = new FilterResults(); if (mOriginalValues == null) { synchronized (mLock) { mOriginalValues = new ArrayList<String>(objects); } } if (prefix == null || prefix.length() == 0) { ArrayList<String> list; synchronized (mLock) { list = new ArrayList<String>(mOriginalValues); } results.values = list; results.count = list.size(); } else { **prefixString = prefix.toString();** // get string to search ArrayList<String> values; synchronized (mLock) { values = new ArrayList<String>(mOriginalValues); } final int count = values.size(); final ArrayList<String> newValues = new ArrayList<String>(); for (int i = 0; i < count; i++) { final String value = values.get(i); final String valueText = value.toString().toLowerCase(); // First match against the whole, non-splitted value if (valueText.startsWith(prefixString)) { newValues.add(value); } else { final String[] words = valueText.split(" "); final int wordCount = words.length; // Start at index 0, in case valueText starts with space(s) for (int k = 0; k < wordCount; k++) { if (words[k].startsWith(prefixString)) { newValues.add(value); break; } } } } results.values = newValues; results.count = newValues.size(); } return results; } @SuppressWarnings("unchecked") @Override protected void publishResults(CharSequence constraint, FilterResults results) { objects = (ArrayList<String>) results.values; if (results.count > 0) { notifyDataSetChanged(); } else { notifyDataSetInvalidated(); } } }; } 
+6
source share
4 answers

This is what I use:

  • Each substitution is replaced (not just a prefix)
  • Case and accent are ignored in the search, but saved as a result.
  • It uses SpannableString directly, which you can use in setText() . I believe this is 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; } } 
+27
source

The accepted answer is pleasant. But you can do it with a single line of code. What I did in my case to avoid a case with a case 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 this helps! Note. Here, the “query” is the part of the line that you want to highlight.

+3
source

First is your code

 if(s.contains(prefixString)){ String rep = s.replace(prefixString, "<b><font color=#2825A6>"+ prefixString+ "</font></b>"); holder.author.setText(Html.fromHtml(rep)); } 

not very good. You should use String.startsWith to check if the beginning of s prefixString value of prefixString . Your actual code works, but it checks for the presence of prefixString in s , but does not care about its position. For case insensitive searches, you can use String.toLowerCase or String.toUpperCase for both strings when checking for prefixString . The case will be ignored.

 if(s.toLowerCase().startsWith(prefixString.toLowerCase())){ String rep = "<b><font color=#2825A6>" + prefixString + "</font></b>" + s.substring(prefixString.length()); holder.author.setText(Html.fromHtml(rep)); } 
+2
source

Advanced Search Highlight Example [Case Insensitive Order]

1. Simple search (Html):

 public static void setSearchTextHighlightSimpleHtml(TextView textView, String fullText, String searchText) { searchText = searchText.replace("'", ""); 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) { searchText = searchText.replace("'", ""); // 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. Quick search (advanced):

 public static void setAdvancedTitleHighlight(TextView textView, String fullText, String searchText) { searchText = searchText.replace("'", ""); final String WORD_SINGLE = " "; // highlight search text if (null != searchText && !searchText.isEmpty() && !searchText.equals(WORD_SINGLE)) { SpannableStringBuilder wordSpan = new SpannableStringBuilder(fullText); Pattern p = Pattern.compile(searchText, Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(fullText); while (m.find()) { final char WORD_BOUNDARY = ' '; int wordStart = m.start(); while (wordStart >= 0 && fullText.charAt(wordStart) != WORD_BOUNDARY) { --wordStart; } wordStart = wordStart + 1; int wordEnd = m.end(); while (wordEnd < fullText.length() && fullText.charAt(wordEnd) != WORD_BOUNDARY) { ++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); } textView.setText(wordSpan, TextView.BufferType.SPANNABLE); } else { textView.setText(fullText); } } 

4. Detailed search (advanced):

 public static void setAdvancedDetailsHighlight(TextView textView, String fullText, String searchText) { searchText = searchText.replace("'", ""); final String WORD_SINGLE = " "; final String WORD_SINGLE1 = "\n"; final String WORD_SINGLE2 = "("; final String WORD_SINGLE3 = ")"; final String WORD_SINGLE4 = "।"; final String WORD_SINGLE5 = "."; final String WORD_SINGLE6 = ","; final String WORD_SINGLE7 = ";"; final String WORD_SINGLE8 = "?"; final String WORD_SINGLE9 = "-"; final String WORD_SINGLE10 = "+"; // highlight search text if (null != searchText && !searchText.isEmpty() && !searchText.equals(WORD_SINGLE) && !searchText.equals(WORD_SINGLE1) && !searchText.equals(WORD_SINGLE2) && !searchText.equals(WORD_SINGLE3) && !searchText.equals(WORD_SINGLE4) && !searchText.equals(WORD_SINGLE5) && !searchText.equals(WORD_SINGLE6) && !searchText.equals(WORD_SINGLE7) && !searchText.equals(WORD_SINGLE8) && !searchText.equals(WORD_SINGLE9) && !searchText.equals(WORD_SINGLE10)) { SpannableStringBuilder wordSpan = new SpannableStringBuilder(fullText); Pattern p = Pattern.compile(searchText, Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(fullText); while (m.find()) { final char WORD_BOUNDARY = ' '; final char WORD_BOUNDARY1 = '\n'; final char WORD_BOUNDARY2 = '('; final char WORD_BOUNDARY3 = ')'; final char WORD_BOUNDARY4 = '।'; final char WORD_BOUNDARY5 = '.'; final char WORD_BOUNDARY6 = ','; final char WORD_BOUNDARY7 = ';'; final char WORD_BOUNDARY8 = '?'; final char WORD_BOUNDARY9 = '-'; int wordStart = m.start(); while (wordStart >= 0 && fullText.charAt(wordStart) != WORD_BOUNDARY && fullText.charAt(wordStart) != WORD_BOUNDARY1 && fullText.charAt(wordStart) != WORD_BOUNDARY2 && fullText.charAt(wordStart) != WORD_BOUNDARY3 && fullText.charAt(wordStart) != WORD_BOUNDARY4 && fullText.charAt(wordStart) != WORD_BOUNDARY5 && fullText.charAt(wordStart) != WORD_BOUNDARY6 && fullText.charAt(wordStart) != WORD_BOUNDARY7 && fullText.charAt(wordStart) != WORD_BOUNDARY8 && fullText.charAt(wordStart) != WORD_BOUNDARY9) { --wordStart; } wordStart = wordStart + 1; int wordEnd = m.end(); while (wordEnd < fullText.length() && fullText.charAt(wordEnd) != WORD_BOUNDARY && fullText.charAt(wordEnd) != WORD_BOUNDARY1 && fullText.charAt(wordEnd) != WORD_BOUNDARY2 && fullText.charAt(wordEnd) != WORD_BOUNDARY3 && fullText.charAt(wordEnd) != WORD_BOUNDARY4 && fullText.charAt(wordEnd) != WORD_BOUNDARY5 && fullText.charAt(wordEnd) != WORD_BOUNDARY6 && fullText.charAt(wordEnd) != WORD_BOUNDARY7 && fullText.charAt(wordEnd) != WORD_BOUNDARY8 && fullText.charAt(wordEnd) != WORD_BOUNDARY9) { ++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); } textView.setText(wordSpan, TextView.BufferType.SPANNABLE); } else { textView.setText(fullText); } } 
0
source

Source: https://habr.com/ru/post/946314/


All Articles