To style text in Android, use spans! Change the color of a few characters, make them clickable, scale the size of the text or even draw custom bullet points with spans. Spans can change the TextPaint properties, draw on a Canvas , or even change text layout and affect elements like the line height. Spans are markup objects that can be attached to and detached from text; they can be applied to whole paragraphs or to parts of the text.

Let’s see how to use spans, what spans are provided out of the box, how to easily create your own and finally how to test them:

Styling text in Android

Applying spans

Framework spans

Creating custom spans

Testing custom spans implementation

Testing spans usage

Styling text in Android

Android offers several ways of styling text:

Single style — where the style applies to the entire text displayed by a TextView

— where the style applies to the entire text displayed by a TextView Multi style — where several styles can be applied to a text, at character or paragraph level

Single style implies styling of the entire content of the TextView, using XML attributes or styles and themes. This approach is an easy solution and works from XML but doesn’t allow styling of parts of the text. For example, by setting textStyle=”bold” , the entire text will be bold; you can’t define only specific characters to be bold.

<TextView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:textSize="32sp"

android:textStyle="bold"/>

Multi style implies adding several styles to the same text. For example, having one word italic and another one bold. Multi style can be achieved using HTML tags, spans or handling custom text drawing on the Canvas.

Left: Single style text. TextView with textSize=”32sp” and textStyle=”bold”. Right: Multi style text. Text with ForegroundColorSpan , StyleSpan(ITALIC), ScaleXSpan(1.5f), StrikethroughSpan.

HTML tags are easy solutions for simple problems, like making a text bold, italic, or even displaying bullet points. To style text containing HTML tags, call Html.fromHtml method. Under the hood, the HTML format is converted into spans. Please note that the Html class does not support all HTML tags and css styles like making the bullet points another colour.

val text = "My text <ul><li>bullet one</li><li>bullet two</li></ul>"

myTextView.text = Html.fromHtml(text)

You manually draw the text on Canvas when you have styling needs that are not supported by default by the platform, like writing text that follows a curved path.

Spans allow you to implement multi-style text with finer grained customisation. For example, you can define paragraphs of your text to have a bullet point by applying a BulletSpan . You can customise the gap between the text margin and the bullet and the colour of the bullet. Starting with Android P, you can even set the radius of the bullet point. You can also create a custom implementation for the span. Check out “Create custom spans” section below to find out how.

val spannable = SpannableString("My text

bullet one

bullet two") spannable.setSpan(

BulletPointSpan(gapWidthPx, accentColor),

/* start index */ 9, /* end index */ 18,

Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) spannable.setSpan(

BulletPointSpan(gapWidthPx, accentColor),

/* start index */ 20, /* end index */ spannable.length,

Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) myTextView.text = spannable

Left: Using HTML tags. Center: Using BulletSpan with default bullet size. Right: Using BulletSpan on Android P or custom implementation.

You can combine single style and multi style. You can consider the style you apply to the TextView as a “base” style. The spans text styling is applied “on top” of the base style and will override the base style. For example, when setting the textColor=”@color.blue” attribute to a TextView and applying a ForegroundColorSpan(Color.PINK) for the first 4 characters of the text, then, the first 4 characters will use the pink colour set by the span, and the rest of the text, the colour set by the TextView attribute.

<TextView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:textColor="@color/blue"/> val spannable = SpannableString(“Text styling”)

spannable.setSpan(

ForegroundColorSpan(Color.PINK),

0, 4,

Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) myTextView.text = spannable

Combining TextView with XML attributes and text with spans

Applying Spans

When using spans, you will work with one of the following classes: SpannedString , SpannableString or SpannableStringBuilder . The difference between them lies in whether the text or the markup objects are mutable or immutable and in the internal structure they use: SpannedString and SpannableString use linear arrays to keep records of added spans, whereas SpannableStringBuilder uses an interval tree.

Here’s how to decide which one to use:

Just reading and not setting the text nor the spans? -> SpannedString

the text nor the spans? -> Setting the text and the spans ? -> SpannableStringBuilder

? -> Setting a small number of spans (<~10)? -> SpannableString

(<~10)? -> Setting a larger number of spans (>~10) -> SpannableStringBuilder

For example, if you’re working with a text that doesn’t change, but to which you want to attach spans, you should use a SpannableString .

╔════════════════════════╦══════════════╦════════════════╗

║ Class ║ Mutable Text ║ Mutable Markup ║

╠════════════════════════╬══════════════╬════════════════╣

║ SpannedString ║ no ║ no ║

║ SpannableString ║ no ║ yes ║

║ SpannableStringBuilder ║ yes ║ yes ║

╚════════════════════════╩══════════════╩════════════════╝

All of these classes extend the Spanned interface, but the classes that have mutable markup ( SpannableString and SpannableStringBuilder ) also extend from Spannable .

Spanned -> immutable text with immutable markup

Spannable (extends Spanned )-> immutable text with mutable markup

Apply a span by calling setSpan(Object what, int start, int end, int flags) on the Spannable object. The what Object is the marker that will be applied from a start to an end index in the text. The flag marks whether the span should expand to include text inserted at their starting or ending point, or not. Independent of which flag is set, whenever text is inserted at a position greater than the starting point and less than the ending point, the span will automatically expand.

For example, setting a ForegroundColorSpan can be done like this:

val spannable = SpannableStringBuilder(“Text is spantastic!”) spannable.setSpan(

ForegroundColorSpan(Color.RED),

8, 12,

Spannable.SPAN_EXCLUSIVE_INCLUSIVE)

Because the span was set using the SPAN_EXCLUSIVE_INCLUSIVE flag, when inserting text at the end of the span, it will be extended to include the new text:

val spannable = SpannableStringBuilder(“Text is spantastic!”) spannable.setSpan(

ForegroundColorSpan(Color.RED),

/* start index */ 8, /* end index */ 12,

Spannable.SPAN_EXCLUSIVE_INCLUSIVE) spannable.insert(12, “(& fon)”)

Left: Text with ForegroundColorSpan. Right: Text with ForegroundColorSpan and Spannable.SPAN_EXCLUSIVE_INCLUSIVE

If the span is set with Spannable.SPAN_EXCLUSIVE_EXCLUSIVE flag, inserting text at the end of the span will not modify the end index of the span.

Multiple spans can be composed and attached to the same text segment. For example, text that is both bold and red can be constructed like this:

val spannable = SpannableString(“Text is spantastic!”) spannable.setSpan(

ForegroundColorSpan(Color.RED),

8, 12,

Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) spannable.setSpan(

StyleSpan(BOLD),

8, spannable.length,

Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)

Text with multiple spans: ForegroundColorSpan(Color.RED) and StyleSpan(BOLD)

Framework spans

The Android framework defines several interfaces and abstract classes that are checked at measure and render time. These classes have methods that allow a span to access objects like the TextPaint or the Canvas .

The Android framework provides 20+ spans in the android.text.style package, subclassing the main interfaces and abstract classes. We can categorize spans in several ways: