Wednesday, August 4, 2010

Easy method for formatting Android TextViews

Android TextViews don't have an easy method for changing the style of a substring of its text. You may have wanted to do something like textView.setTextColor(Color.RED, 10, 20); in order to set the 10th to the 20th characters red. I'll show you a method of making this easy to do; not just with colors, but with all sorts of styles.

Using regular HTML tags in strings

You do have a limited option to format your strings without any programmatic methods. In strings.xml, where you define your strings, you can use the simple HTML format tags <b>, <i>, and <u> for bold, italics, and underlining, respectively. For example,
<string name="text1">This text uses <b>bold</b> and <i>italics</i> by using inline tags such as <b> within the string file.</string>
A TextView with this string will appear as: This text uses bold and italics by using inline tags such as <b> within the string file.
Unfortunately, you cannot use any more HTML tags than that. In order to have some nice inline styles, we'll need to use CharacterStyles. This class has many sub-classes, and allows you to do everything from changing the text appearance (like ForegroundColorSpan, to more complicated things like making clickable links (ClickableSpan) and images (ImageSpan).

CharSequences

A quick note about how this works. Skip to the next section if you don't care. TextView, along with many other Android classes which use formatted text, don't just use simple Strings. They use CharSequences. Get this: a CharSequence is more abstract than a String; a String is a sub-class of CharSequence. A CharSequence defines a series of characters, such as a regular string, but it could also define a series of characters with formatting, such as a SpannableString. Internally, what we will do to change the middle of a TextView's text is to add spans to its text. More precisely, we will add CharacterStyles to the TextView's CharSequence (text), which is a SpannableString.

Format text dynamically

Here's a handy utility method which will take in some text, and return the text formatted. The key is to surround the text you want with tokens such as "##". The tokens will be removed in the returned result.
For example, if a TextView has its text set as "Hello, ##world##!" and you want world to be in red, call setSpanBetweenTokens("Hello ##world##!", "##", new ForegroundColorSpan(0xFFFF0000)); will return the text Hello, world! with world in red. Note that you can send multiple spans as parameters to this method.

/**
 * Given either a Spannable String or a regular String and a token, apply
 * the given CharacterStyle to the span between the tokens, and also
 * remove tokens.
 * <p>
 * For example, {@code setSpanBetweenTokens("Hello ##world##!", "##",
 * new ForegroundColorSpan(0xFFFF0000));} will return a CharSequence
 * {@code "Hello world!"} with {@code world} in red.
 *
 * @param text The text, with the tokens, to adjust.
 * @param token The token string; there should be at least two instances
 *             of token in text.
 * @param cs The style to apply to the CharSequence. WARNING: You cannot
 *            send the same two instances of this parameter, otherwise
 *            the second call will remove the original span.
 * @return A Spannable CharSequence with the new style applied.
 *
 * @see http://developer.android.com/reference/android/text/style/CharacterStyle.html
 */
public static CharSequence setSpanBetweenTokens(CharSequence text,
    
String tokenCharacterStyle... cs)
{
    
// Start and end refer to the points where the span will apply
    
int tokenLen = token.length();
    
int start = text.toString().indexOf(token) + tokenLen;
    
int end = text.toString().indexOf(tokenstart);

    
if (start > -1 && end > -1)
    {
        
// Copy the spannable string to a mutable spannable string
        
SpannableStringBuilder ssb = new SpannableStringBuilder(text);
        
for (CharacterStyle c : cs)
            
ssb.setSpan(cstartend, 0);

        
// Delete the tokens before and after the span
        
ssb.delete(endend + tokenLen);
        
ssb.delete(start - tokenLenstart);

        
text = ssb;
    }

    
return text;
}

Clickable spans

One of the spans you can set your text to is a ClickableSpan. You may have added this and wondered why anything didn't happen when you clicked on the link. Well, you need to have one extra step to let Android know that there is a clickable link and it needs to be navigated to. You need to set a MovementMethod to the TextView. You can investigate this further if you wish, or you can just see the sample code to make it work below (and it is also in the sample):


// Adapted from Linkify.addLinkMovementMethod(), to make links clickable.
//
MovementMethod 
m = textView.getMovementMethod();
if ((m == null) || !(m instanceof LinkMovementMethod))
{
    
textView.setMovementMethod(LinkMovementMethod.getInstance());
}

Sample Application

The sample application is a simple application with a few TextViews and a Button. The first TextView shows how you can set a TextView's text just by using HTML tags in strings.xml. The second TextView demonstrates how to change text dynamically, using the above utility method. When the button is clicked, it will first set some text red using a ForegroundColorSpan. Second, it will set some text bold and italics using a StyleSpan. Third, it will make a generic link by setting some text to blue, underlining it (UnderlineSpan), and then creating an onClick method which executes some custom code using a ClickableSpan. The final click demonstrates both a ForegroundColorSpan and a RelativeSizeSpan.


Project source code - formattedtext.zip (7.22 Kb)

60 comments:

Ed Burnette said...

Hi Matt, I just discovered your blog today and would like to add it to Planet Android if you don't mind (www.planetandroid.com). However because your articles are long (which I like, btw) I would much prefer an RSS or Atom feed that is not a full feed, i.e., it would contain an excerpt such as the first paragraph or two. This will also generate more traffic back to your site as people click through. Could you adjust the settings on your main feed, or make a new one for the planet to use? Email it to my gmail.com account (userid ed.burnette). Send me a 90px wide face or avatar/design .png icon too if you'd like it to appear next to your feed. Thanks!

Unknown said...

Thanks Matt. This was really helpful. I found it easier to extend SpannableStringBuilder to add styles as I'm appending to it. Here's how I did it:

public class StyleableSpannableStringBuilder extends SpannableStringBuilder {

public StyleableSpannableStringBuilder appendWithStyle(CharacterStyle c, CharSequence text) {
super.append(text);
int startPos = length() - text.length();
setSpan(c, startPos, length(), 0);
return this;
}

public StyleableSpannableStringBuilder appendBold(CharSequence text) {
return appendWithStyle(new StyleSpan(Typeface.BOLD), text);
}

Now you can use this StyleableSpannableStringBuilder like a normal StringBuilder just using inline appendBold() calls. It's easily extendable to add other methods for other styles and makes your view populating code very readable.

Anonymous said...

Cool! I like it.

thanks...

Richard Leggett said...
This comment has been removed by the author.
Richard Leggett said...

Hi Matt,

I'm struggling with a similar problem. If you switch your example to use a custom font created with Typeface.createFromAsset(), you lose the ability to have both bold and regular text in the same TextView... well you can do it, but the bold font looks fuzzy and awful as it just uses a faux bold version of the regular font.

I'm not sure how Google don't suffer the same problem with the built in fonts. Do you know of any way to use two actual Typefaces within one TextView?

(I should point out, when trying it with a single TTF, rather than bold and regular OTFs, it's improved, so perhaps it depends on the font).

Mur Votema said...

I just thank you. I haven't read your post till the end, because already the title "Using regular HTML tags in strings" helped me really much, to solve my problem with text formating.

Just to add: to change text-color I used tag 'font' with attribute 'color'.

Elad Katz said...

Thanks a lot!
I wrote something a little similar but this is much more robust (mine was a decorator that you would have to call one on top of the other)
Thanks for this, I was wondering though, how do u get images to works (imagespan)?

Joe Trite said...

Good stuff. Thanks!

Anonymous said...

What if I have multiple words that need styling, e.g. "##apples##, ##oranges##, hello world, ##bananas##" ?

Matt Quigley said...

@Anonymous

You would simply change the "if" statement to a "while" statement, meaning it would replace tokens while it still found them. At the bottom of the while block you would be sure to set the start/end variables again, as in "start = text.toString().indexOf(token, end - tokenLen - tokenLen) + tokenLen;" I haven't tested this but it's something similar.

Anonymous said...

good one

Kristher said...

How can I implement this on EditText?

Mesmo said...

Thanks for posting this.

Too bad the textviews don't support the ^br/^ tag.

Unknown said...

@Mesmo

A line break is simply done with '\n' in the text. You don't need the tag for that.

S. Morgan Biggs said...

@Mike - That's exactly what I've been looking for! So useful, wonder why it wasn't implemented in the first place?

Thank you very much!

Unknown said...

This is a great piece of code.

I have tried to use it to do as Anonymous asked and change the character style to several words within a paragraph.

I have managed to use a while loop that correctly identifies the start and end indexes of words i wish to change which is confirmed by the printout of the words I wish to highlight. Unfortunately the string that I return only highlights the last instance EG if the text was $$banana$$ apple pear $$orange$$ kiwi $$berry$$

only berry will be changed. My code is as follows, if you can provide any help it would be appreciated:

public static CharSequence formatText(CharSequence text,
String token, CharacterStyle... cs)
{
// Start and end refer to the points where the span will apply
int tokenLen = token.length();
int start = text.toString().indexOf(token) + tokenLen;
int end = text.toString().indexOf(token, start);
SpannableStringBuilder ssb = new SpannableStringBuilder(text);

while (start > -1 && end > -1)
{
// Copy the spannable string to a mutable spannable string

for (CharacterStyle c : cs)
ssb.setSpan(c, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

System.out.println("start: " + start);
System.out.println("end: " + end);
System.out.println(ssb.subSequence(start, end));

// Delete the tokens before and after the span
ssb.delete(end, end + tokenLen);
ssb.delete(start - tokenLen, start);
text = ssb;




start = text.toString().indexOf(token, end - tokenLen - tokenLen) + tokenLen;
//start = text.toString().indexOf(token, end + tokenLen);
//System.out.println("start: " + start);
end = text.toString().indexOf(token, start);
// System.out.println("end: " + end);


}

return ssb;
}

Unknown said...

Forget my last post, just solved it.

For those that have had the same problem here is my code. This will highlight MULTIPLE words or phrases with the specified token at either side.


private static CharSequence boldUnderlineText(CharSequence text, String token) {
int tokenLen = token.length();
int start = text.toString().indexOf(token) + tokenLen;
int end = text.toString().indexOf(token, start);

while (start > -1 && end > -1)
{
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text);

//spannableStringBuilder.setSpan(new ForegroundColorSpan(0xFF0099FF), start, end, 0);
spannableStringBuilder.setSpan(new UnderlineSpan(), start, end, 0);
/*spannableStringBuilder.setSpan(new ClickableSpan() {

}, start, end, 0);
*/
spannableStringBuilder.setSpan(new StyleSpan(Typeface.BOLD), start, end, 0);

System.out.println("start: " + start);
System.out.println("end: " + end);
System.out.println(spannableStringBuilder.subSequence(start, end));

// Delete the tokens before and after the span
spannableStringBuilder.delete(end, end + tokenLen);
spannableStringBuilder.delete(start - tokenLen, start);
text = spannableStringBuilder;

start = text.toString().indexOf(token, end - tokenLen - tokenLen) + tokenLen;
end = text.toString().indexOf(token, start);
}

return text;
}

Matt Quigley said...

Looks good to me, Alan! I'd recommend anyone reusing that code to remove the debugging System.out.print statements.

Unknown said...

By the way, it should not be start > -1 but start > -1 + tokenLen

If not, then you can get a problem if you restart and there is no token.

Anonymous said...

Alan Spowart's way is good but it only allows for one style of format. How would you do it to allow for multiple styles? Like Matt's original way.

SAS41 said...

Hello!
I tried to implement the following method to add multiple spans, but for some reason I can only add 1 span no matter what I do.

http://pastebin.com/gP8Xx3KA

Matt Quigley said...

@SAS41 did you try alan spowart's code in the comments?

Unknown said...

hello!

some have a sample implementing :
new AlignmentSpan.Standard(Alignment.ALIGN_CENTER)

your help is great appreciated

Anonymous said...

Hi, was wondering if I could set the text dynamically e.g from the editext with the specific token and then show it on a textview or is it done and only programmatically?

Jacob said...

For those interested, I've been working with various tokens to bold some text and italicize other parts within a single TextView - BUT NOT BOTH. At least for me. I imagine this can be used for any other type of formatting. To clarify Alan Spowart's post above, don't pass in a third parameter into the method, just do the check within the method itself:

switch(token){
case "###":
spannableStringBuilder.setSpan(new StyleSpan(Typeface.BOLD), start, end, 0);
break;

case "***":
spannableStringBuilder.setSpan(new StyleSpan(Typeface.ITALIC), start, end, 0);
break;
}

For some reason I couldn't get it to work by passing the formatting in as a parameter; but this works like a charm.

Unknown said...

Great ans thanks .
i have one more question i not able to manage ##apple(space)banana## ....
how we can implement it.

Unknown said...
This comment has been removed by the author.
Unknown said...

i have one more question i not able to manage _*apple(space)banana *_

Roshni said...

Thank you for sharing this information!

Vijay said...

The blog is really informative with lot of innovative ideas. Keep posting more in the future blog post.
IAS Academy in Chennai
IAS Academy in Anna Nagar
Top 10 IAS Coaching Centres in Chennai
Top IAS Academy in Chennai
Top IAS Coaching in Chennai

Anonymous said...

The blog is very useful and informative thanks for the sharing Ethical hacking training

Nithya said...

Thanks for sharing this helpful piece of coding, it still helps to modify basic web designs and add text to websites. HTML has always proved best for improving the look and feel of websites and blogs. These simple HTML format tags used with strings help view the text on many websites.

kouroshmusic said...

This was a great resource
Thanks for the manager of this site
good lunch

دانلود آهنگ جدید
دانلود آهنگ پرطرفدار
محسن ابراهیم زاده
سینا پارسیان
دانلود آهنگ
دانلود آهنگ شاد

every things said...


ِDownload New persian Songs
دانلود آهنگ دیس لاو
دانلود آهنگ ماشین
دانلود آهنگ جدید

Ramanasri IAS INSTITUTE said...

Amazing blog!!
ias online coaching

Robert said...

Very Helpful and Informative post.
Reusable Bulk Bags
Organic Cotton Products
Dishtowels

John said...

This was a great resource
Thanks for the manager of this site
good lunch
دانلود آهنگ جدید

دانلود آهنگ جدید
دانلود آهنگ پرطرفدار
محسن ابراهیم زاده
سینا پارسیان مهراد جم

علی لهراسبی

دانلود آهنگ
دانلود آهنگ شاد

Quirinus Solution Ltd said...

E-commerce Application Development Services Company
E commerce mobile Application Development Company
eCommerce Website Development Company
eCommerce Website Development Services
eCommerce Website Development Company
QuirinusSoft a eCommerce Website Development Company
eCommerce Website Development
Ecommerce Application Development Services Company
eCommerce website development company
Search Engine Optimization Services

Quirinus Solution Ltd said...

Ecommerce Application Development
Ecommerce Website Development
eCommerce Website Development Company
eCommerce Website Development Company
eCommerce Website Development Company in UK
eCommerce Website Development Company
Ecommerce Website Development Company
Android App Development Company
Ecommerce Website Development Company
Ecommerce Website Development Company

ve may bay tet said...

Aivivu chuyên cung cấp vé máy bay, Tham khảo

ve may bay di my gia re

vé máy bay từ los angeles về việt nam

gia ve may bay di Los Angeles

vé máy bay giá rẻ từ Canada về Việt Nam

Darren Demers said...

Faisalabad is one of the biggest cities in Pakistan and the hub of the textile industry. It is widely acknowledged as the Manchester of Pakistan due to its large industrial role. The quality of the fabrics produced in this city has no parallel. unstitched lawn suits , unstitched lawn suits In fact, the fabric is something of a specialty of Faisalabad. Many people from all over the country flock to this city for a spot of cloth shopping. We aim to provide you all of the best of Faisalabad at our store.

Anonymous said...

I have read so many posts about the blogger lovers however this piece of writing is genuinely a nice piece of writing, keep it up.
AI Certification Courses in Bangalore
Machine Learning r training in Bangalore
Best Deep learning Course in Bangalore

Datascience said...

Greetings! Very helpful advice in this particular post! you can Also check this articles - Read Here

eduschoolnews said...

Best on eduschoolnews.com You can get the latest News the following categories:

1 JAMB Result

2 JAMB cut off mark

3 JAMB CHANGE OF COURSES

4 JAMB CHANGE OF INSTITUTION

Godwin Ibanga said...

According to bestschoolnews.com you can apply for
Nasarawa State School of Nursing Lafia

Get Past Question here

Essien said...

Hey! This is my first visit to your blog! Your blog provided us with beneficial information to work on. You have done a outstanding job! Thanks for this article. Click here for more info.- futminna post utme past question

Ahmed ali said...

very interesting keep posting. magento developers slc

Anonymous said...

Thanks for sharing the info. Your articles haven’t been updated recently.
thejacketzone

anne m rubeo said...

I really love this list, but I would really appreciate

thejacketzone

Unknown said...

Nice post! This is a very nice blog that I will definitively come back to more times this year! Thanks for informative post. android programming wallpapers

Frank said...

TextView with this string will appear as Homework Help Online This text uses bold and italics by using inline tags

Mehak Khan said...

Keep up the good work; I read few posts on this website, including I consider that your blog is fascinating and has sets of the fantastic piece of information. Thanks for your valuable efforts. IoT hidden menu

Peyman Keyvani said...

It was great. You really have a very good site

George Mark said...

It was not first article by this author as I always found him as a talented author. Izaya Orihara Hoodie

Whatsapp Prime APK said...

Well Said & Good Thought!

Irani Bash said...


دانلود

Irani Bash said...


موزیک ها

Eva Stalin Business School said...

Best Information for Ever
Best MBA College in Chennai

Eva Stalin IAS Academy said...

Top Content to Student Best IAS Academy in Chennai

Cinsel sohbet said...

https://sohbetbe.blogspot.com/