ImageSpan / Linkify / Spans / StyleSpan / Text / URLSpan

Introduction to Spans

In this article we are going to look at a useful mechanism for applying styles to sections of text. While it is easy enough to style blocks of text using standard Android styles, there is also a mechanism for applying styles to text at a sub-block level. In HTML it is possible to add emphasis by applying inline markup as shown in this sentence. In Android we can apply a style to a TextView widget which will apply a given style to a specific block of text, but what if we want finer grained control like that of HTML? We use Spans: a mechanism for applying different styles to a block of text to character level.

Let’s start by creating a simple Android project name Spans, targeting Android 2.3.3, with the package name “com.stylingandroid.spans”, and create an activity named “MainActivity”. Next we’ll change the string displayed in the main layout to something a bit longer. Edit res/values/strings.xml and change the value of “hello” to be “This is a string which will enable us to demonstrate spans.”.

If we run this we should see the following:

Basic App
Basic App

Suppose that we want to embolden the word “demonstrate” in the string. First we need to assign an ID to the TextView control in res/layout/main.xml:



	

Note the ID that we set on line 5. We can now obtain a reference to this and set a span on it. In MainActivity.java:

@Override
public void onCreate( Bundle savedInstanceState )
{
	super.onCreate( savedInstanceState );
	setContentView( R.layout.main );
	TextView textView = (TextView)findViewById( R.id.TextView );
	Spannable spannable = (Spannable)textView.getText();
	StyleSpan boldSpan = new StyleSpan( Typeface.BOLD );
	spannable.setSpan( boldSpan, 41, 52, Spannable.SPAN_INCLUSIVE_INCLUSIVE );
}

Most of this should be pretty easy to understand, it is only lines 6, 7, & 8 where we are doing anything that you may not have encountered before. In line 6 we are getting the text and casting it to an android.text.Spannable. In line 7 we are creating a new StyleSpan object, and we can use any of the constants in android.graphics.Typeface to define our style. In line 8 we are setting a style within the spannable to apply boldSpan from the character at offset 41 to 52, the final argument defines how the span behaves if we subsequently insert text in to the spannable.

This is actualy quite straightforward, but if we try and run this, our app will crash giving a ClassCastException. The reason for this is that, for efficiency, TextView does not use a Spanable unless it needs to. We could get around this by either creating the CharSequence ourselves as a Spannable, and using textView.setText( spannable, TextView.BufferType.SPANNABLE ) but , as is often the case in Android, there is another way that we can force Android to use a Spannable for this TextView by altering res/layout/main.xml:



	

On line 5 we have added an attribute to force the control to implement Spannable text. If we try this, it works and we see:

Simple Bold Span
Simple Bold Span

An often used phrase on this blog is “there also is another way of doing this” and that is true of spans as well. Suppose that we want to construct the string at runtime instead of using a string value resource. Let’s start by adding another TextView widget to res/layout/main.xml:



	
	

Note that we have not specified an android:bufferType for TextView2.

Next we need to copy a new drawable to res/drawable/emoticon.png, the drawable can be downloaded here.

Finally, we need to add some code to the end of MainActivity.onCreate() to utilise this drawable:

@Override
public void onCreate( Bundle savedInstanceState )
{
	super.onCreate( savedInstanceState );
	setContentView( R.layout.main );
	TextView textView = (TextView)findViewById( R.id.TextView );
	Spannable spannable = (Spannable)textView.getText();
	StyleSpan boldSpan = new StyleSpan( Typeface.BOLD );
	spannable.setSpan( boldSpan, 41, 52, Spannable.SPAN_INCLUSIVE_INCLUSIVE );
	
	TextView textView2 = (TextView)findViewById( R.id.TextView2 );
	SpannableStringBuilder ssb = new SpannableStringBuilder( "Here's a smiley  " );
	Bitmap smiley = BitmapFactory.decodeResource( getResources(), R.drawable.emoticon );
	ssb.setSpan( new ImageSpan( smiley ), 16, 17, Spannable.SPAN_INCLUSIVE_INCLUSIVE );	
	textView2.setText( ssb, BufferType.SPANNABLE );
}

On line 12 we instantiate a SpannableStringBuilder object. This is something of a hybrid of StringBuilder and a Spannable. On line 13 we instantiate a bitmap object, and on line 14 we set a span, this time using an ImageSpan which is based upon the bitmap that we instantiated previously. If we run this we should see:

Span with emoticon
Span with emoticon

Note: When I was writing the code for this article, I initially found that the default icon.png image was being used instead of emoticon.png. For some reason the new drawable had not been properly mapped in to the project when I added it, but it was easily rectified by cleaning the project through Project|Clean… in Eclipse which forces the resource IDs to be regenerated.

So far we’ve had a look at StyleSpan and ImageSpan, but there are many more Spans to play with, just have a look in android.text.style. However, we’ll have a quick look at one more span type because there can be a problem using it. Let’s add a hyperlink to our text:

@Override
public void onCreate( Bundle savedInstanceState )
{
	super.onCreate( savedInstanceState );
	setContentView( R.layout.main );
	TextView textView = (TextView)findViewById( R.id.TextView );
	Spannable spannable = (Spannable)textView.getText();
	StyleSpan boldSpan = new StyleSpan( Typeface.BOLD );
	spannable.setSpan( boldSpan, 41, 52, Spannable.SPAN_INCLUSIVE_INCLUSIVE );
		
	TextView textView2 = (TextView)findViewById( R.id.TextView2 );
	SpannableStringBuilder ssb = new SpannableStringBuilder( "Here's a smiley  , and here's a link http://blog.stylingandroid.com" );
	Bitmap smiley = BitmapFactory.decodeResource( getResources(), R.drawable.emoticon );
	ssb.setSpan( new ImageSpan( smiley ), 16, 17, Spannable.SPAN_INCLUSIVE_INCLUSIVE );
	URLSpan urlSpan = new URLSpan( "http://blog.stylingandroid.com" ) 
	{
		@Override
		public void onClick( View widget )
		{
			Intent i = new Intent( Intent.ACTION_VIEW );
			i.setData( Uri.parse( getURL() ) );
			startActivity( i );
		}
	};
	ssb.setSpan( urlSpan, 37, 67, Spannable.SPAN_INCLUSIVE_INCLUSIVE );
	textView2.setText( ssb, BufferType.SPANNABLE );
}

On the face of it, this should work, but if we try it we’ll find that the link isn’t clickable. The obvious solution is to try adding android:linksClickable=”true” to our TextView definition in the layout file, but this doesn’t work either. The problem is that TextView uses an android.text.method.MovementMethod to handle cursor positioning, scrolling, and text positioning within the control and, because this has been defined as a static control, a basic MovementMethod has been used. To get things working, we just need to make sure that we explicitly use a LinkMovementMethod on the control:

@Override
public void onCreate( Bundle savedInstanceState )
{
	super.onCreate( savedInstanceState );
	setContentView( R.layout.main );
	TextView textView = (TextView)findViewById( R.id.TextView );
	Spannable spannable = (Spannable)textView.getText();
	StyleSpan boldSpan = new StyleSpan( Typeface.BOLD );
	spannable.setSpan( boldSpan, 41, 52, Spannable.SPAN_INCLUSIVE_INCLUSIVE );
		
	TextView textView2 = (TextView)findViewById( R.id.TextView2 );
	SpannableStringBuilder ssb = new SpannableStringBuilder( "Here's a smiley  , and here's a link http://blog.stylingandroid.com" );
	Bitmap smiley = BitmapFactory.decodeResource( getResources(), R.drawable.emoticon );
	ssb.setSpan( new ImageSpan( smiley ), 16, 17, Spannable.SPAN_INCLUSIVE_INCLUSIVE );
	URLSpan urlSpan = new URLSpan( "http://blog.stylingandroid.com" ) 
	{
		@Override
		public void onClick( View widget )
		{
			Intent i = new Intent( Intent.ACTION_VIEW );
			i.setData( Uri.parse( getURL() ) );
			startActivity( i );
		}
	};
	ssb.setSpan( urlSpan, 37, 67, Spannable.SPAN_INCLUSIVE_INCLUSIVE );
	textView2.setText( ssb, BufferType.SPANNABLE );
	textView2.setMovementMethod( LinkMovementMethod.getInstance() );
}

One again, there is an alternate way of doing this: android.text.util.Linkify. Linkify is a utility class which does the heavy lifting for us, and ensures that the correct MovementMethod is used automatically. We can change our code:

@Override
public void onCreate( Bundle savedInstanceState )
{
	super.onCreate( savedInstanceState );
	setContentView( R.layout.main );
	TextView textView = (TextView)findViewById( R.id.TextView );
	Spannable spannable = (Spannable)textView.getText();
	StyleSpan boldSpan = new StyleSpan( Typeface.BOLD );
	spannable.setSpan( boldSpan, 41, 52, Spannable.SPAN_INCLUSIVE_INCLUSIVE );
	
	TextView textView2 = (TextView)findViewById( R.id.TextView2 );
	SpannableStringBuilder ssb = new SpannableStringBuilder( "Here's a smiley  , and here's a link http://blog.stylingandroid.com" );
	Bitmap smiley = BitmapFactory.decodeResource( getResources(), R.drawable.emoticon );
	ssb.setSpan( new ImageSpan( smiley ), 16, 17, Spannable.SPAN_INCLUSIVE_INCLUSIVE );
	textView2.setText( ssb, BufferType.SPANNABLE );
	Linkify.addLinks( textView2, Linkify.WEB_URLS );
}

This will detect any URLs within the text of the TextView (so must be called after setText) and does the necessary conversion and sets the MovementMethod, if required. Linkify is extremely powerful and performs some standard conversions such as email addresses, phone numbers, and web addresses, as well as allowing you to define your own patterns and transformations.

Yet another way of achieving the same thing would be to add and an android:autoLink=”web” attribute to the TextView declaration in our layout file instead of using Linkify programmatically.

That concludes this article, as ever the final source can be found here.

© 2011, Mark Allison. All rights reserved.

Copyright © 2011 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

19 Comments

  1. Hi Mark,

    Nice site and nice article.

    I found your site from the UK design patterns boot and both are really valuable resources for the developer community.

    One slight bug though, for people using tablets for browsing now, more than a desktop or laptop, it appears that the code snippets widget is not ready for touch use and expects a mouse or similar pointing device.

    At least, that’s the way it is on a Galaxy Tab 10.1 running 3.1

    The scroll bars can’t be activated at all, so the code can’t be fully read.

    Not a major issue, but a usability issue that might become more important as the client profile for accessing your site changes.

    It’s the same on my phone, Samsung Galaxy S, running 2.3

    As one suggestion maybe the lines of code could be kept shorter so they fit within the width of the box.

    1. Hi Peter,

      Many thanks for the feedback.

      In my next article (due to be published on 29th July) I am using a different plugin to handle the code snippets. I have also tried (as best I can) to shorten the lines as much as possible. Although this is a fine balance because over-shortening lines of code can affect readability. It looks OK to me on both a Xoom tablet & a Nexus S.

      I would very much welcome any further comments that you may have, and any feedback of the layout of the next article.

      Regards,

      Mark

  2. Hi Mark,
    Thanks for this very clear, simple and efficient tutorial. I stubbled upon it randomly, and it gave me many ideas 😉

  3. Mark,

    Superb article for insights in text style

    i have a doubt, will this type of formatting affect the application performance, say for example i have three paragraph of text for a single activity and in single TextView, and i want to make a word in bold in second para, like wise some text style in between. is this the best way to do it

    or three separate TextView for each paragraph and we can apply styles for each paragraph (TextView) right. Correct me if i am wrong.

    Is there any other way for formatting text.

    One more help, do android have support for Rich text Format and Unicode support, share your views

    Thanks once again for this wonderful article, will check for your posts regularly

    1. There’s no right or wrong way to do this, it really depends on precisely what you’e trying to do. Personally, I’ve never experienced performance issues with using spans.

      There’s no native support for Rich Text Format, and Unicode is a character encoding and not a formatting markup.

      There is some (albeit limited) support for HTML markup using the android.text.Html class, but this sits on top of the Spans framework described in the main article – so it won’t work as an alternative to using Spans if you’re worried about performance.

  4. Mark,

    Many thanks for the grate tutorial indeed, I am currently doing a similar project to this one.

    I’m busy with a chat program and so far i have to select the smiley’s from a dialog and place it to the editText field so now when i press the send button to other buddy the smiley image doesn’t show on the listView.

    Is the any ways that i can send the smiley to a listView? because if can only send the image when i am using the textView is shows to other buddy i am send to.

    i.e. Hi there i miss u :)……sending this from a editText to listView
    Please help.

    Regards,
    MP

  5. A very insightful and easy-to-follow article, Mark! Thanks for sharing your knowledge on spans. They really aren’t covered very well (or at all) in most Android texts.

    Cheers from Rochester, NY, USA.
    -Tom

  6. Hi
    Great job…………
    is it possible to add spannable bitmap to email intent ?
    cos while composing mail i was not able to embed the bitmap in body text

    best regards…
    Mahesh

  7. How to store a SpannableString in SQLite or a temporary file,I’ve tried serialization but as the object is not serializable I’m unable to store it.Kindly help me out …

    Thanks in Advance

    1. Why do you need to store the SpannableString? SpannableString is a mechanism for attaching formatting markup to Strings for rendering purposes, it is not designed to be a storage format. You will need to devise your own format for storing the text and markup and construct a SpannableString from this when you have read your data and wish to render it.

    1. TextView is the View itself and the Span is a mechanism for adding styling directly to the text that you’re setting in the TextView. Using Spans is much more efficient and flexible that applying text styles via the TextView itself.

Leave a Reply to depe Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.