Android: Highlight search query in results like Apple's notes.app - android

I am new to Android. I use an iPhone, so I am not just new to the programming, but also to the OS completely. I just started this week and have written a basic notes application.
Now, when I go to the search view, search say "cats", if a result appears and I click to go to that note, I want all instances of "cats" to be highlighted. Then, when I tap in the EditText, I want the highlighting to go away.
It would also be awesome if I could highlight the text within the search view.
Apple's Notes.app does this and I think it really adds to the search functionality. Couldn't find any images to show you what I mean. Hopefully I explained it well enough.
I tried this:
//highlight searched text
//Get the text of the EditText
String text = editText.getText().toString();
//Get indexes of the query in the EditText
int firstIndex = text.indexOf(query);
int lastIndex = text.lastIndexOf(query);
//Highlight the selection
editText.setSelection(firstIndex, lastIndex);
But we run into problems if there are multiple of the same word. Any thoughts?

Selection and highlighting are not the same thing. Usually, selecting something also highlights it, but you don't highlight something by selecting it. Besides, Android does not support multiple selection in EditText.
To highlight, you need to apply a CharacterStyle to the range of text, such as a BackgroundColorSpan.
This sample project applies a BackgroundColorSpan to highlight search results in a TextView, using:
private void searchFor(String text) {
TextView prose=(TextView)findViewById(R.id.prose);
Spannable raw=new SpannableString(prose.getText());
BackgroundColorSpan[] spans=raw.getSpans(0,
raw.length(),
BackgroundColorSpan.class);
for (BackgroundColorSpan span : spans) {
raw.removeSpan(span);
}
int index=TextUtils.indexOf(raw, text);
while (index >= 0) {
raw.setSpan(new BackgroundColorSpan(0xFF8B008B), index, index
+ text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
index=TextUtils.indexOf(raw, text, index + text.length());
}
prose.setText(raw);
}
The code shown first removes any existing BackgroundColorSpan instances, then applies new ones based on a search string.
Since EditText inherits from TextView, the same basic concept would apply here. However, IMHO, doing this sort of highlighting in an EditText will be foreign to users. I'd show the search results in a plain TextView, with an "edit" action bar item or something to move into editing mode.

I did some googling and looked searching "java" instead of "android" was helpful. Below is my working code:
private void highlightIndexes(String query){
String text = editText.getText().toString();
Map<Integer, Integer> indexMap = getIndexes(query);
Spannable spannable=new SpannableString(text);
Iterator<Integer> keySetIterator = indexMap.keySet().iterator();
while(keySetIterator.hasNext()){
Integer key = keySetIterator.next();
Integer value = indexMap.get(key);
spannable.setSpan(new ForegroundColorSpan(Color.BLUE), key, value, 0);
}
editText.setText(spannable);
}
private Map<Integer, Integer> getIndexes(String query){
Map<Integer, Integer> indexMap = new TreeMap<Integer, Integer>();
int queryLength = query.length();
query = query.substring(0, (queryLength -1)).toLowerCase(Locale.US);
String text = editText.getText().toString().toLowerCase(Locale.US);
int i, y;
i = text.indexOf(query);
y = i + queryLength - 1;
indexMap.put(i, y);
while(i >= 0) {
i = text.indexOf(query, i+1);
y = i + queryLength - 1;
if (i != -1 && y != -1){
indexMap.put(i, y);
}
}

Related

Markdown support in Android TextView

Is there a way to enable a TextView to detect markdown tags and render the text accordingly? More specifically, my app contains a TextView in which the users can provide a description, and often they will use markdown to format their description. Unfortunately the text doesn't render, and instead we see all the tags written out in the textview.
There's no built-in support for Markdown in the Android SDK. You'll have to use a lib like markdown4j or CommonMark.
I understand you want to convert a String containing Markdown markup to a formatted CharSequence that you can use in a TextView. The two options I know of are :
Bypass : Use a native C library to parse the text. Unfortunately the project seems dead.
commonmark-spannable-android : Pure java, based on the very good commonmark-java
library.
I used both and in my opinion, the second one is better : no need to deal with native architectures, smaller APK, and the performance is quite good (something like 2 times slower in my case, with is more than good enough)
Update : Found another option (it's the one I'm using now) :
Markwon : Pure java, also using commonmark-java as parser, with optional support for images and tables
There is no inherit support for markdown in textview, however if you only need simple markdown-lite implementation via simple "regexp" matching, this section from my "load readme from project root folder" in https://github.com/mofosyne/instantReadmeApp would help.
Note that this does not remove the markup in the text, only styles the lines differently. This may be a good or bad thing, depending on your application.
Oh and the nice thing? It styles in native textview, so the text is still selectable like normal text.
Specifically this line: https://github.com/mofosyne/instantReadmeApp/blob/master/app/src/main/java/com/github/mofosyne/instantreadme/ReadMe.java#L137
Slightly modified below: private void updateMainDisplay(String text) to private void style_psudomarkdown_TextView(String text, TextView textview_input), so you could use the same function for different textviews
```
/*
Text Styler
A crappy psudo markdown styler. Could do with a total revamp.
*/
/*
* Styling the textview for easier readability
* */
private void style_psudomarkdown_TextView(String text, TextView textview_input) {
//TextView mTextView = (TextView) findViewById(R.id.readme_info);
TextView mTextView = textview_input;
// Let's update the main display
// Needs to set as spannable otherwise http://stackoverflow.com/questions/16340681/fatal-exception-string-cant-be-cast-to-spannable
mTextView.setText(text, TextView.BufferType.SPANNABLE);
// Let's prettify it!
changeLineinView_TITLESTYLE(mTextView, "# ", 0xfff4585d, 2f); // Primary Header
changeLineinView(mTextView, "\n# ", 0xFFF4A158, 1.5f); // Secondary Header
changeLineinView(mTextView, "\n## ", 0xFFF4A158, 1.2f); // Secondary Header
changeLineinView(mTextView, "\n---", 0xFFF4A158, 1.2f); // Horizontal Rule
changeLineinView(mTextView, "\n>", 0xFF89e24d, 0.9f); // Block Quotes
changeLineinView(mTextView, "\n - ", 0xFFA74DE3, 1f); // Classic Markdown List
changeLineinView(mTextView, "\n- ", 0xFFA74DE3, 1f); // NonStandard List
//spanSetterInView(String startTarget, String endTarget, int typefaceStyle, String fontFamily,TextView tv, int colour, float size)
// Limitation of spanSetterInView. Well its not a regular expression... so can't exactly have * list, and *bold* at the same time.
spanSetterInView(mTextView, "\n```\n", "\n```\n", Typeface.BOLD, "monospace", 0xFF45c152, 0.8f, false); // fenced code Blocks ( endAtLineBreak=false since this is a multiline block operator)
spanSetterInView(mTextView, " **" , "** ", Typeface.BOLD, "", 0xFF89e24d, 1f, true); // Bolding
spanSetterInView(mTextView, " *" , "* ", Typeface.ITALIC, "", 0xFF4dd8e2, 1f, true); // Italic
spanSetterInView(mTextView, " ***" , "*** ", Typeface.BOLD_ITALIC, "", 0xFF4de25c, 1f, true); // Bold and Italic
spanSetterInView(mTextView, " `" , "` ", Typeface.BOLD, "monospace", 0xFF45c152, 0.8f, true); // inline code
spanSetterInView(mTextView, "\n " , "\n", Typeface.BOLD, "monospace", 0xFF45c152, 0.7f, true); // classic indented code
}
private void changeLineinView(TextView tv, String target, int colour, float size) {
String vString = (String) tv.getText().toString();
int startSpan = 0, endSpan = 0;
//Spannable spanRange = new SpannableString(vString);
Spannable spanRange = (Spannable) tv.getText();
while (true) {
startSpan = vString.indexOf(target, endSpan-1); // (!##$%) I want to check a character behind in case it is a newline
endSpan = vString.indexOf("\n", startSpan+1); // But at the same time, I do not want to read the point found by startSpan. This is since startSpan may point to a initial newline.
ForegroundColorSpan foreColour = new ForegroundColorSpan(colour);
// Need a NEW span object every loop, else it just moves the span
// Fix: -1 in startSpan or endSpan, indicates that the indexOf has already searched the entire string with not valid match (Lack of endspan check, occoured because of the inclusion of endTarget, which added extra complications)
if ( (startSpan < 0) || ( endSpan < 0 ) ) break;// Need a NEW span object every loop, else it just moves the span
// Need to make sure that start range is always smaller than end range. (Solved! Refer to few lines above with (!##$%) )
if (endSpan > startSpan) {
//endSpan = startSpan + target.length();
spanRange.setSpan(foreColour, startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// Also wannna bold the span too
spanRange.setSpan(new RelativeSizeSpan(size), startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanRange.setSpan(new StyleSpan(Typeface.BOLD), startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
tv.setText(spanRange);
}
private void changeLineinView_TITLESTYLE(TextView tv, String target, int colour, float size) {
String vString = (String) tv.getText().toString();
int startSpan = 0, endSpan = 0;
//Spannable spanRange = new SpannableString(vString);
Spannable spanRange = (Spannable) tv.getText();
/*
* Had to do this, since there is something wrong with this overlapping the "##" detection routine
* Plus you only really need one title.
*/
//while (true) {
startSpan = vString.substring(0,target.length()).indexOf(target, endSpan-1); //substring(target.length()) since we only want the first line
endSpan = vString.indexOf("\n", startSpan+1);
ForegroundColorSpan foreColour = new ForegroundColorSpan(colour);
// Need a NEW span object every loop, else it just moves the span
/*
if (startSpan < 0)
break;
*/
if ( !(startSpan < 0) ) { // hacky I know, but its to cater to the case where there is no header text
// Need to make sure that start range is always smaller than end range.
if (endSpan > startSpan) {
//endSpan = startSpan + target.length();
spanRange.setSpan(foreColour, startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// Also wannna bold the span too
spanRange.setSpan(new RelativeSizeSpan(size), startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanRange.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
//}
tv.setText(spanRange);
}
private void spanSetterInView(TextView tv, String startTarget, String endTarget, int typefaceStyle, String fontFamily, int colour, float size, boolean endAtLineBreak) {
String vString = (String) tv.getText().toString();
int startSpan = 0, endSpan = 0;
//Spannable spanRange = new SpannableString(vString);
Spannable spanRange = (Spannable) tv.getText();
while (true) {
startSpan = vString.indexOf(startTarget, endSpan-1); // (!##$%) I want to check a character behind in case it is a newline
endSpan = vString.indexOf(endTarget, startSpan+1+startTarget.length()); // But at the same time, I do not want to read the point found by startSpan. This is since startSpan may point to a initial newline. We also need to avoid the first patten matching a token from the second pattern.
// Since this is pretty powerful, we really want to avoid overmatching it, and limit any problems to a single line. Especially if people forget to type in the closing symbol (e.g. * in bold)
if (endAtLineBreak){
int endSpan_linebreak = vString.indexOf("\n", startSpan+1+startTarget.length());
if ( endSpan_linebreak < endSpan ) { endSpan = endSpan_linebreak; }
}
// Fix: -1 in startSpan or endSpan, indicates that the indexOf has already searched the entire string with not valid match (Lack of endspan check, occoured because of the inclusion of endTarget, which added extra complications)
if ( (startSpan < 0) || ( endSpan < 0 ) ) break;// Need a NEW span object every loop, else it just moves the span
// We want to also include the end "** " characters
endSpan += endTarget.length();
// If all is well, we shall set the styles and etc...
if (endSpan > startSpan) {// Need to make sure that start range is always smaller than end range. (Solved! Refer to few lines above with (!##$%) )
spanRange.setSpan(new ForegroundColorSpan(colour), startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanRange.setSpan(new RelativeSizeSpan(size), startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanRange.setSpan(new StyleSpan(typefaceStyle), startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// Default to normal font family if settings is empty
if( !fontFamily.equals("") ) spanRange.setSpan(new TypefaceSpan(fontFamily), startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
tv.setText(spanRange);
}
```
The above implementation supports only up to 2 headers (but you can easily modify the regexp to support more than 2 level headers).
It is a series of regexp based text view consisting of two functions for regexp that matches always a line changeLineinView() and changeLineinView_TITLESTYLE()
For multiline spanning spanSetterInView() function deals with it.
So extending it to fit your purpose as long as you have a regexp that doesn't clash with any other syntax would be possible.
Markdownish Syntax:
This is the supported syntax. Can't support full markdown, since this is only a lite hacky implementation. But kind handy for a no frills display that is easy to type on a mobile phone keypad.
# H1 only in first line (Due to technical hacks used)
## H2 headers as usual
## Styling
Like: *italic* **bold** ***bold_italic***
## Classic List
- list item 1
- list item 2
## Nonstandard List Syntax
- list item 1
- list item 2
## Block Quotes
> Quoted stuff
## codes
here is inline `literal` codes. Must have space around it.
```
codeblocks
Good for ascii art
```
Or 4 space code indent like classic markdown.
I can recommend MarkdownView. I use it for loading markdown files from the assets folder.
In case it helps anyone, here's my implementation...
In my layout:
<us.feras.mdv.MarkdownView
android:id="#+id/descriptionMarkdownView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:layout_constraintTop_toBottomOf="#id/thumbnailImageView"
app:layout_constraintStart_toEndOf="#id/guidelineStart"
app:layout_constraintEnd_toEndOf="#id/guidelineEnd"
app:layout_constraintBottom_toTopOf="#id/parent"/>
In my Activity:
val cssPath = "file:///android_asset/markdown.css"
val markdownPath = "file:///android_asset/markdown/filename.md"
descriptionMarkdownView.loadMarkdownFile(markdownPath, cssPath)
Take a look at the commonmark-java library.
I haven't tried that myself but I think you might be able to make it work in your case
I followed this post since last Friday and tested many of the Markdown libraries suggested here - this question and these answers were basically the best source about the topic I could find online.
Two of them caught my attention the most, MarkdownView and Markwon, but the former was easier to deal with than the latter and so I used it to empower a Room note taking app by Markdown formatting (which was my main personal goal).
If you want to have a Markdown live preview, you could just use this sample activity provided by the library itself and, among other options, if you need to adapt your own activity to it, I suggest you add the following pieces of code to your project:
build.gradle
implementation 'us.feras.mdv:markdownview:1.1.0'
private MarkdownView markdownView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.markdownView = findViewById(R.id.markdownView);
this.udateMarkdownView();
}
private void updateMarkdownView() {
markdownView.loadMarkdown(note_content.getText().toString());
}
Here you find the sample I put available on GitHub in which you can see a working project apart from the samples the library itself gives us as examples.
If you want to render HTML you can use Html.fromHtml("your string"), for more resources on Strings in Android check this link

TextView with anchor and raw links (SpannableString issue)

In my textview, I want both anchors and raw links to be clickable. I'm letting TextView.setAutoLinkMask(Linkify.ALL) handle all of the raw links and then retrieving the spans and setting them with SpannableString for all of the anchors.
I would like to be able to 'linkify' the anchors in my custom TextView implementation (extends TextView) in the setText(CharSequence text, BufferType type) call. Everything seems to be working properly except for the setSpan() call (end, start, and span are all correct). At the end of setText(...), I'm assigning the global CharSequence var to the new SpannableString. When setText() is called from my adapter, the spans identified in Object[]spans are not linked.
#Override
public void setText(CharSequence text, BufferType type) {
Spanned html = Html.fromHtml(text.toString().replace("\n", "<br />"));
Object[] spans = html.getSpans(0, html.length(), URLSpan.class);
SpannableString s = new SpannableString(html);
for (int i = 0; i < spans.length; i++) {
URLSpan span = (URLSpan) spans[i];
int end = html.getSpanEnd(span);
int start = html.getSpanStart(span);
int flags = html.getSpanFlags(span);
Log.i(LOG_TAG, "span: " + span.getURL() + "\nstart: " + start + "\nend: " + end);
s.setSpan(span, start, end, flags);
}
mText = s;
}
I found a slight workaround to this issue in the code listed below. In this implementation, I'm setting the spans in the adapter instead of the CustomTextView. This method works fine but doesn't entirely fit my needs because my TextView is used as an ExpandableTextView, meaning that there are 2 sets of texts (trimmed and full) and often times the trimmed version is returned (which is fine in typical scenarios, except that I want to add tags to the full text). This implementation often crashes because the spans that were originally identified were for the fullText and getText() is returning the trimmedText. So I think it'll be necessary for me to be able to do this in my custom TextView's setText(). BUT, the below method does work granted that trimmedText and fullText are the same length.
Spanned html = Html.fromHtml(postText.replace("\n", "<br />"));
Object[] spans = html.getSpans(0, html.length(), URLSpan.class);
h.content.setAutoLinkMask(Linkify.ALL);
h.content.setMovementMethod(LinkMovementMethod.getInstance());
h.content.setText(html);
h.content.setLinkTextColor(Color.rgb(136, 194, 226));
h.content.setVisibility(View.VISIBLE); // Need this otherwise the view disappears....
SpannableString ss = (SpannableString) h.content.getText();
for (int i = 0; i < spans.length; i++) {
URLSpan span = (URLSpan) spans[i];
int end = html.getSpanEnd(span);
int start = html.getSpanStart(span);
int flags = html.getSpanFlags(span);
ss.setSpan(span, start, end, flags);
}
Here's how I solved my problem:
At the point of my problem, I had already parsed through the content and added anchors (HTML links that would launch an activity when clicked) to my tags (# and #). I also wanted to make raw (e.g., www.google.com) clickable, so I tried setting my TextView to setAutoLinkMask(Linkify.ALL) but this broke the anchor links.
So this is what I did:
SpannableStringBuilder ssb =
new SpannableStringBuilder(Html.fromHtml
(this.content.replace("\n", "<br />")));
Pattern urlPattern = android.util.Patterns.WEB_URL;
Linkify.addLinks(ssb, urlPattern, "youractivityhere://");
myTextView.setText(ssb);
Basically I'm using a regex matcher to find any URLs within the String and then add a link using Linkify.addLinks(...).
I was making the problem way more difficult than needed.

Highlight a particular word in a String dynamically

I want to highlight a particular word in a text view ( more specifically similar to a twitter feed). The word may occur multiple times. Below I will post a sample sentence from twitter.
" Mumbai Master Blaster! #Sachin. Greatest players of all times. The legend of cricket #sachin. "
Here I want to highlight the word " #Sachin " with a particular color. Also please note that we don't know how many times this word could get repeated in the whole string. Could anyone help me to solve this issue.
Use next code:
public CharSequence linkifyHashtags(String text) {
SpannableStringBuilder linkifiedText = new SpannableStringBuilder(text);
Pattern pattern = Pattern.compile("#\\w");
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
int start = matcher.start();
int end = matcher.end();
String hashtag = text.substring(start, end);
ForegroundColorSpan span = new ForegroundColorSpan(Color.BLUE);
linkifiedText.setSpan(span, 0, hashtag.length(), 0);
}
return linkifiedText;
}

Highlight multiple words in a string using SpannableString

I'm using a SpannableString to underline certain words, however, I realized the code I have only highlights the first word if there are multiple words. Not exactly sure how to accomplish highlighting multiple words:
String keyword = "test";
String text = "This is a test to underline the three test words in this test";
SpannableString output = new SpannableString(text);
if (text.indexOf(keyword) > -1)
{
int keywordIndex = text.indexOf(keyword);
int keywordLength = keyword.length();
int start = keywordIndex;
int end = keywordIndex + (keywordLength);
output.setSpan(new UnderlineSpan(), start, end, 0);
}
I was thinking I could split the text at every space and loop through it, but wasn't sure if there was a better way.
I do have this code to highlight multiple words using a regular expression, however, I'm try to avoid regular expressions since it's in an Android app and I'm using it in a ListView and I'm told they are very expensive. Also this code I have only highlight whole words, so using the example text above, if the word "protest" was in the sentence, it wouldn't get highlighted using this code:
Matcher matcher = Pattern.compile("\\b(?:test")\\b").matcher(text);
while (matcher.find())
{
output.setSpan(new UnderlineSpan(), matcher.start(), matcher.end(), 0);
}

Android - Highlight a Word In a TextView?

I have a database search query which search in the database for a word entered by the user and return a Cursor.
In my ListActivity, I have a ListView which will hold the items (the Cursor items). The ListView items layout is basically a TextView. I mean, the ListView will be a list of TextView's.
What I want is to highlight the search term wherever it appears in the TextView. I mean by highlighting: different color or different background color or anything makes it different than the rest of the text.
Is this possible? and how?
Update:
cursor = myDbHelper.search(term); //term: a word entered by the user.
cursor.moveToFirst();
String[] columns = {cursor.getColumnName(1)};
int[] columnsLayouts = {R.id.item_title}; //item_title: the TextView holding the one raw
ca = new SimpleCursorAdapter(this.getBaseContext(), R.layout.items_layout, cursor,columns , columnsLayouts);
lv = getListView();
lv.setAdapter(ca);
For #Shailendra: The search() method will return some titles. I want to highlight the words in those titles that matches the term word. I hope this is clear now.
insert HTML code for color around word and set it to your textView .
like
String newString = oldString.replaceAll(textToHighlight, "<font color='red'>"+textToHighlight+"</font>");
textView.setText(Html.fromHtml(newString));
TextView textView = (TextView)findViewById(R.id.mytextview01);
//use a loop to change text color
Spannable WordtoSpan = new SpannableString("partial colored text");
WordtoSpan.setSpan(new ForegroundColorSpan(Color.BLUE), 2, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(WordtoSpan);
The numbers 2 and 4 are start/stop indexes for the coloring of the text, in this example "rti" would be colored.
So you would basically just find the starting index of your searching word in the title:
int startIndex = titleText.indexOf(term);
int stopIndex = startIndex + term.length();
and then replace the numbers 2 and 4 with the indexes and "partial colored text" with your title string.
source: https://stackoverflow.com/a/10279703/2160827
More Easy Way
You can use Spannable class for highlighting/formatting part of Text.
textView.setText("Hello, I am Awesome, Most Awesome"); // set text first
setHighLightedText(textView, "a"); // highlight all `a` in TextView
Here is the method.
/**
* use this method to highlight a text in TextView
*
* #param tv TextView or Edittext or Button (or derived from TextView)
* #param textToHighlight Text to highlight
*/
public void setHighLightedText(TextView tv, String textToHighlight) {
String tvt = tv.getText().toString();
int ofe = tvt.indexOf(textToHighlight, 0);
Spannable wordToSpan = new SpannableString(tv.getText());
for (int ofs = 0; ofs < tvt.length() && ofe != -1; ofs = ofe + 1) {
ofe = tvt.indexOf(textToHighlight, ofs);
if (ofe == -1)
break;
else {
// set color here
wordToSpan.setSpan(new BackgroundColorSpan(0xFFFFFF00), ofe, ofe + textToHighlight.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(wordToSpan, TextView.BufferType.SPANNABLE);
}
}
}
You can check this answer for clickable highlighted text.
I know it's old question but i have created a method to highlight a repeated-word in string\paragraph.
private Spannable highlight(int color, Spannable original, String word) {
String normalized = Normalizer.normalize(original, Normalizer.Form.NFD)
.replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
int start = normalized.indexOf(word);
if (start < 0) {
return original;
} else {
Spannable highlighted = new SpannableString(original);
while (start >= 0) {
int spanStart = Math.min(start, original.length());
int spanEnd = Math.min(start+word.length(), original.length());
highlighted.setSpan(new ForegroundColorSpan(color), spanStart,
spanEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
start = normalizedText.indexOf(word, spanEnd);
}
return highlighted;
}
}
usage:
textView.setText(highlight(primaryColor, textAll, wordToHighlight));
Based on the previous answers I developed the following function, you can copy/paste it
private void highlightMask(TextView textView, String text, String mask) {
boolean highlightenabled = true;
boolean isHighlighted = false;
if (highlightenabled) {
if (!TextUtils.isEmpty(text) && !TextUtils.isEmpty(mask)) {
String textLC = text.toLowerCase();
mask = mask.toLowerCase();
if (textLC.contains(mask)) {
int ofe = textLC.indexOf(mask, 0);
Spannable wordToSpan = new SpannableString(text);
for (int ofs = 0; ofs < textLC.length() && ofe != -1; ofs = ofe + 1) {
ofe = textLC.indexOf(mask, ofs);
if (ofe == -1) {
break;
} else {
// set color here
wordToSpan.setSpan(new BackgroundColorSpan(0xFFFFFF00), ofe, ofe + mask.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(wordToSpan, TextView.BufferType.SPANNABLE);
isHighlighted = true;
}
}
}
}
}
if (!isHighlighted) {
textView.setText(text);
}
}
I haven't done it but this looks promising:
http://developer.android.com/reference/android/text/SpannableString.html
http://developer.android.com/guide/topics/resources/string-resource.html
public final void setText (CharSequence text)
Since: API Level 1 Sets the string value of the TextView. TextView
does not accept HTML-like formatting, which you can do with text
strings in XML resource files. To style your strings, attach
android.text.style.* objects to a SpannableString, or see the
Available Resource Types documentation for an example of setting
formatted text in the XML resource file.
http://developer.android.com/reference/android/widget/TextView.html
Try this library Android TextHighlighter.
Implementations
TextView.setText() gets a parameter as Spannable not only CharacterSequence. SpannableString has a method setSpan() which allows applying styles.
See list of direct subclass form CharacterStyle https://developer.android.com/reference/android/text/style/CharacterStyle.html
example of giving background color and foreground color for word "Hello" in "Hello, World"
Spannable spannable = new SpannableString("Hello, World");
// setting red foreground color
ForegroundSpan fgSpan = new ForegroundColorSpan(Color.red);
// setting blue background color
BackgroundSpan bgSpan = new BackgroundColorSPan(Color.blue);
// setSpan requires start and end index
// in our case, it's 0 and 5
// You can directly set fgSpan or bgSpan, however,
// to reuse defined CharacterStyle, use CharacterStyle.wrap()
spannable.setSpan(CharacterStyle.wrap(fgSpan), 0, 5, 0);
spannable.setSpan(CharacterStyle.wrap(bgSpan), 0, 5, 0);
// apply spannableString on textview
textView.setText(spannable);
You do so in xml strings if your strings are static
<string name="my_text">This text is <font color='red'>red here</font></string>
I know this thread is old, but just in case anyone is looking to highlight strings in a textview, I have created a library that does exactly this. This is my first answer to a question on stack overflow, as I have just joined, hopefully it's formatted properly and relevant. It uses SpannableString and will locate all occurrences of a string you specify. Additionally, a custom ClickableSpan is built in giving you the option to set up listeners for text clicked if desired.
Linker
Lightweight android library for highlighting Strings inside of a textview (ignoring case), with optional callbacks.
Language: Java
MinSDK: 17
An image of it's functionality and all of the code can be found
here.
JavaDocs
To bring into your android project implement the artifact:
In the Project level build.gradle
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
In the App level build.gradle
dependencies {
implementation 'com.github.Gaineyj0349:Linker:1.2'
}
How to use:
1 - Construct a Linker object with a textview:
Linker linker = new Linker(textView);
2 - Add an array or a list of strings to be highlighted within the textview's text:
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
linker.addStrings(list);
AND/OR
String[] words = new String[]{"One", "Two", "Three"};
linker.addStrings(words);
3 - Add a callback: (this is optional):
linker.setListener(new LinkerListener() {
#Override
public void onLinkClick(String charSequenceClicked) {
// charSequenceClicked is the word that was clicked
Toast.makeText(MainActivity.this, charSequenceClicked, Toast.LENGTH_SHORT).show();
}
});
4 - Call the linker's update method to commit customization and rollout the setup.:
linker.update();
You always have the option to add Strings to the linker object, just make sure you call the update method after to refresh the spans.
linker.addStrings("yoda");
linker.update();
If you need a fresh slate with same linker object just call
linker.clearLinksList()
You can customize the links also:
1 - Customize all the link colors:
linker.setAllLinkColors(Color.BLUE);
2 - Customize link underlines:
linker.setAllLinkUnderline(false);
3 - If you wish to customize a color or underline setting for a certain string (note the string must already be added to the linker):
linker.setLinkColorForCharSequence("world", Color.MAGENTA);
linker.setUnderlineModeForCharSequence("world", true);
4 - If you wish to use different setups for every word then you can also give the linker object a list or array of LinkProfiles:
ArrayList<LinkProfile> profiles = new ArrayList<>();
profiles.add(new LinkProfile("hello world",
Color.GREEN, false));
profiles.add(new LinkProfile("goodbye cruel world",
Color.RED, false));
profiles.add(new LinkProfile("Whoa awesome!",
Color.CYAN, true));
linker.addProfiles(profiles);
Just remember to call .update() after any additions to the linker object.
Note that the library will take care of subtleties like adding two of the same words, or same parts of a word. For example if "helloworld" and "hello" are two of the words added to the linker, "helloworld" will be given preference over "hello" when they are in the same span of characters. The linker will sort according to larger words first and trace all spans as it links them - avoiding the issue of duplication as well as intersecting spans.
Licensed under MIT license .

Categories

Resources