Highlighting key words in edittext - android

I want to highlight a specific words on an edittext while the user is typing I got the words highlighted but if I add any text before the highlighted word it changes back.
#Override
public void afterTextChanged(Editable p1)
{
// TODO: Implement this method
if(highlightSearchKey(p1.toString())!=null){
if(flag){
flag = false;
Spannable highlighted = highlightSearchKey(p1.toString());
p1.replace(0,p1.length(),highlighted);
}
}
The method to pick the key words
private Spannable highlightSearchKey(String title) {
Spannable highlight = null;
Pattern pattern;
Matcher matcher;
int word_index;
String title_str;
word_index = words.length;
title_str = Html.fromHtml(title).toString();
for (int index = 0; index < word_index; index++) {
pattern = Pattern.compile("(?i)" + words[index]);
matcher = pattern.matcher(title_str);
while (matcher.find()) {
highlight = (Spannable) Html.fromHtml(title);
highlight.setSpan(
new ForegroundColorSpan(Color.BLUE),
matcher.start(),
matcher.end(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
return highlight;
}

Related

android spannable edititext with cursor manipulation

I am trying to implement code highlighting using spanned text and html.fromhtml() function in an edittext than implements text watcher.
The problem occurs when i try to manipulate cursor for custom brackets, the app crashes due to some spannable string setspan error.
How do i use spannable code highlighting and set cursor position adjusting according to the spanned text.
Edit:
The function:
Spanned matchtext(String s)
{
//Pattern p =Pattern.compile(check[0]);
String a=s;
for(int i=0;i<Constants.keyWords.length;i++) {
a = a.replaceAll(Constants.keyWords[i], "<font color=\"#c5c5c5\">" + Constants.keyWords[i] + "</font>");
//a = s.replaceAll(";", "<font color=\"#c5c5c5\">" + ";" + "</font>");
}
Spanned ab = Html.fromHtml(a);
return ab;
}
And the function call:
mCodeEditText.removeTextChangedListener(tt);
bs = matchtext(s.toString());
mCodeEditText.setText(bs);
mCodeEditText.addTextChangedListener(tt);
Edit 2:
This is my new implementation, I just can't get the highlighting to work.
Spannable matchtext(String s, int pos) {
Spannable abc = new SpannableString(s);
for (int i = 0; i < Constants.keyWords.length; i++) {
if (pos - Constants.keyWords[i].length() >= 0) {
int j = s.indexOf(Constants.keyWords[i]);
if (j != -1) {
if ((s.subSequence(j, pos)).equals(Constants.keyWords[i]))
abc.setSpan(new ForegroundColorSpan(Color.BLUE), j, pos, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
}
}
}
return abc;
}

Jump on the word highlighted inside ScrollView

I have here code where it highlights all the words inside the TextView which is equal to the typed string. By pressing the buttons next/previous, It highlights the current word found.
What I'm trying to do is to go to the position of that word currently being highlighted.
I've added this code inside my onClick
myScroll.scrollBy(0, +20);
But it doesn't seem to be what im looking for
Also I've tried something like this
myScroll.scrollTo(0, selected); // getting the index of the highlighted word
// but it's not working
onClickListener to highlight next word
findViewById(R.id.next).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
selected++;
String text = ((EditText) findViewById(R.id.editText)).getText().toString();
if(text.equals("") {}
else
selected = changeTextView(details, text, selected);
}
});
method to highlight the text
private int changeTextView(TextView tv, String target, int selected) {
if(selected < 0 ) {
selected = 0;
}
String bString = (String) tv.getText().toString();
int startSpan = 0, endSPan = 0;
Spannable spanRange = new SpannableString(bString);
int currentIndex = 0;
while(true) {
startSpan = bString.indexOf(target, endSpan);
endSpan = startSpan + target.lenght();
boolean isLast = bString.indexOf(target, endSpan) < 0;
if(startSpan < 0)
break;
ParcelableSpan span;
if(currentIndex == selected || isLast && selected > currentIndex) {
span = new BackgroundColorSpan(Color.LTGRAY);
if(isLast && selected > currentIndex) {
selected = currentIndex;
}
} else {
span = new BackgroundColorSpan(Color.YELLOW);
}
currentIndex++;
spanRange.setSpan(
span,
startSpan,
endSpan,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
tv.setText(spanRange);
if(currentIndex == 0) {
return -1;
} else {
return selected;
}
}

Registering separate clicks for every Spanned string

My app need such scenario like in a textView I stored "#dev will goto home and #Roy will go to Station. I want to open a dialer activity to call the clicked person.
But using this code if I click on any #words it shows whole string.
am toasted string.
User can enter his own string and may be he/she enter or not the #contact and there is not defined index of the same.
multiAutoCompleteTextView.setText(addClickablePart(desc), TextView.BufferType.SPANNABLE);
private SpannableString addClickablePart(String str) {
SpannableString ss = new SpannableString(str);
Pattern pattern = Pattern.compile("[#]\\w*");
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
ClickableSpan clickableSpan = new ClickableSpan() {
#Override
public void onClick(View textView) {
// do toasting
TextView b = (TextView)textView;
String buttonText = b.getText().toString();
Toast.makeText(ViewNote.this,buttonText,Toast.LENGTH_SHORT).show();
}
};
ss.setSpan(clickableSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return ss;
}
You need to store the text which is associated with each ClickableSpan. Try something like this:
public static class ClickableSegmentSpan extends ClickableSpan {
private String segment;
public ClickableSegmentSpan(String segment) {
this.segment = segment;
}
#Override
public void onClick(View widget) {
Toast.makeText(widget.getContext(), segment, Toast.LENGTH_SHORT).show();
}
}
The addClickablePart method would change to this:
private SpannableString addClickablePart(String str) {
SpannableString ss = new SpannableString(str);
Pattern pattern = Pattern.compile("[#]\\w*");
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
int start = matcher.start();
int end = matcher.end();
ss.setSpan(new ClickableSegmentSpan(str.substring(start, end)), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return ss;
}

Highlight searched text in ListView items

I have a ListView and i am using a custom adapter to show data. Now i want to change searched text letter colour as in above screen shot.
Here is the code for SearchView
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.actionbar_menu_item, menu);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
final SearchView searchView = (SearchView) menu.findItem(R.id.action_search)
.getActionView();
searchView.setSearchableInfo(searchManager
.getSearchableInfo(getComponentName()));
searchView.setOnQueryTextListener(this);
return super.onCreateOptionsMenu(menu);
}
public boolean onQueryTextChange(String newText) {
// this is adapter that will be filtered
if (TextUtils.isEmpty(newText)){
lvCustomList.clearTextFilter();
}
else{
lvCustomList.setFilterText(newText.toString());
}
return false;
}
#Override
public boolean onQueryTextSubmit(String query) {
return false;
}
Thank you.
I assume that you have a custom Adapter with getCount() and getView() implemented and already filtering items, and you just need the bold part.
To achieve that, you need to use a SpannableString, which is basically text with markup attached. For example, a TextAppearanceSpan can be used to change typeface, font style, size, and color.
So, you should update your adapter's getView() to change the part where you use textView.setText() into something more or less like this:
String filter = ...;
String itemValue = ...;
int startPos = itemValue.toLowerCase(Locale.US).indexOf(filter.toLowerCase(Locale.US));
int endPos = startPos + filter.length();
if (startPos != -1) // This should always be true, just a sanity check
{
Spannable spannable = new SpannableString(itemValue);
ColorStateList blueColor = new ColorStateList(new int[][] { new int[] {}}, new int[] { Color.BLUE });
TextAppearanceSpan highlightSpan = new TextAppearanceSpan(null, Typeface.BOLD, -1, blueColor, null);
spannable.setSpan(highlightSpan, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(spannable);
}
else
textView.setText(itemValue);
Android Search Highlight Example [Case Insensitive Order]
1. Search: [Highlight Specific Word]
public static SpannableStringBuilder highlightSearchText(SpannableStringBuilder fullText, String searchText) {
if (searchText.length() == 0) return fullText;
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();
setWordSpan(wordSpan, wordStart, wordEnd);
}
return wordSpan;
}
2. Search: [Highlight Full Word]
public static SpannableStringBuilder highlightSearchText(SpannableStringBuilder fullText, String searchText) {
if (searchText.length() == 0) return fullText;
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);
}
return wordSpan;
} else {
return fullText;
}
}
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 boolean isNotEquals(char charAt, char[] boundaries) {
for (char boundary : boundaries) {
boolean isEquals = charAt == boundary;
if (isEquals) return false;
}
return true;
}
Common Method:
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);
}
You can use the CodeView library to implement this feature, all you need is to create 2 variables in the adapter to store the pattern of search result and the color of highlighting,
private Pattern syntaxPattern;
private Color highlightColor = Color.MAGENTA;
and create a setter in the adapter for pattern and color
public void updateSyntaxPattern(Pattern pattern) {
syntaxPattern = pattern;
notifyDataSetChanged();
}
then in getView method for ArrayAdapter or ViewHolder in RecyclerAdapter you need to add this pattern to the CodeView instance and remove the old patterns (from the last search result)
if(syntaxPattern != null) {
codeView.resetSyntaxPatternList();
codeView.addSyntaxPattern(syntaxPattern, highlightColor);
}
Now in SearchView onQueryTextSubmit or onQueryTextChange, depend on when you want the highlighter to work you will choose one of them you need to make the pattern from the search result and set it to the adapter
Pattern pattern = Pattern.compile(query);
adapter.updateSyntaxPattern(pattern);
It will work exactly as you want

Highlight search results in ListView

I have a ListView with Strings. With the below code I can highlight search results, but the user must type the words to search case sensitive. How can I implement a none - case sensitive highlighting of search results for example like the native Android Contact search?
Here is my code for Highlighting. I extend the ArrayAdapter and implement customized filter to get the string to search. In the getView method I check if my String in ListView contains the prefixString and highlight 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();
}
}
};
}
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);
}
First, your code
if(s.contains(prefixString)){
String rep = s.replace(prefixString, "<b><font color=#2825A6>"+ prefixString+ "</font></b>");
holder.author.setText(Html.fromHtml(rep));
}
is not good. You should use String.startsWith to check if the start of s equals to prefixString. Your actual code works, but it checks presence of prefixString in s, but doesn't care about its position.
For having case insensitive search, you can use String.toLowerCase or String.toUpperCase on both strings when checking presence of prefixString. 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));
}

Categories

Resources