In a previous article we talked about styling characters or paragraphs in Android using spans. But all the examples used were based on hard coded text, where we knew exactly at which indexes to apply the span. Most of the time, the text we work with comes from resources and is internationalized. So the text we need to style and its positioning within a sentence may change.

For example, let’s say that we have the string “Best practices for text on Android” in English and Spanish, and we want to style the word “text”.

values/strings.xml

<resources>

<string name=”title”>Best practices for text on Android</string>

</resources>

values-es/strings.xml

<resources>

<string name=”title”>Texto en Android: mejores prácticas</string>

</resources>

We end up having to style different words, “text” and “Texto”, that have different positions in the strings they belong to, so looking for occurrences of a specific word is not a feasible task. Depending on what kind of styling we want to apply, we have multiple ways of styling internationalized text: by using HTML tags or using the Annotation span together with the annotation tag.

Basic text styling with HTML tags

If you only want to apply basic styling, use HTML tags in the string resources or Html.fromHtml . The following styling is supported:

Bold: <b> , <em>

, Italic: <i> , <cite> , <dfn>

, , 25% increase in text size <big>

20% decrease in text size <small>

Setting font properties: <font face=”font_family“ color=”hex_color”> . Examples of the possible font families include monospace , serif and sans_serif .

. Examples of the possible font families include , and . Setting a monospace font family: <tt>

Strikethrough: <s> , <strike> , <del>

, , Underline: <u>

Superscript: <sup>

Subscript: <sub>

Bullet points: <ul> , <li>

, Line breaks: <br>

Division: <div>

CSS style: <span style=”color|background_color|text-decoration”>

Paragraphs: <p dir=”rtl | ltr” style=”…”>

For example, to have the word “text” bold, the text in the strings.xml would be this:

// values/strings.xml

<string name=”title”>Best practices for <b>text</b> on Android</string>

// values-es/strings.xml

<string name=”title”><b>Texto</b> en Android: mejores prácticas</string>

In the UI, the text can be set like this:

textView.setText(R.string.title)

Complex text styling with Annotation

If your styling needs exceed the capabilities supported by HTML tags, or if you want to use custom styles, like custom bullet point styling or even completely new styles, then we need another solution. Mark the words to be styled and work with the android.text.Annotation class and the corresponding <annotation> tag in the strings.xml resource files.

The annotation tag allows us to define custom <key, value> pairs in the xml. When getting string resources as SpannedString , these pairs are automatically converted by the Android framework into Annotation spans, with the corresponding key and value. We can then parse the list of annotations attached to the text and add the right span to our text.

Make sure you add the <annotation> tag to all the translations of your string, in every strings.xml file.

Apply a custom typeface to the word “text”, in all languages

Let’s say that we want to set a custom typeface, by applying a CustomTypefaceSpan to the word “text”. Here’s what we need to do:

Add the <annotation> tag and define the <key, value> pair. In our case, the key is font , and the value is the type of font we want to use: title_emphasis .

// values/strings.xml

<string name=”title”>Best practices for

<annotation font=”title_emphasis”>text</annotation> on Android</string>

// values-es/strings.xml

<string name=”title”>

<annotation font=”title_emphasis”>Texto</annotation> en Android: mejores prácticas</string>

2. Get the string from resources, iterate through the annotations and get the ones with the key font and the corresponding value. Then create the custom span and set it to text in the same positions as the annotation span.

// get the text as SpannedString so we can get the spans attached to the text

val titleText = getText(R.string.title) as SpannedString // get all the annotation spans from the text

val annotations = titleText.getSpans(0, titleText.length, Annotation::class.java) // create a copy of the title text as a SpannableString

// so we can add and remove spans

val spannableString = SpannableString(titleText) // iterate through all the annotation spans

for (annotation in annotations) { // look for the span with the key "font"

if (annotation.key == "font") {

val fontName = annotation.value // check the value associated with the annotation key

if (fontName == "title_emphasis") { // create the typeface

val typeface = getFontCompat(R.font.permanent_marker) // set the span to the same indices as the annotation

spannableString.setSpan(CustomTypefaceSpan(typeface),

titleText.getSpanStart(annotation),

titleText.getSpanEnd(annotation),

Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)

}

}

} // now the spannableString contains both the annotation spans and the CustomTypefaceSpan

styledText.text = spannableString

You can find the complete code for this example here.

For a more complex example, in Java, using Annotations to add an image to the text and to set the foreground color, check out this commit from Plaid.

If you’re using the same text several times, for example in a RecyclerView , and you reconstruct the SpannableString for every item, there are some performance and memory implications you need to keep in mind:

The string is iterated multiple times: once by the framework to add the Annotation spans and once by you, to manually add your spans

spans and once by you, to manually add your spans A new instance of the SpannableString is created for every item.

To solve this, build the text only once, cache and then reuse the SpannableString .

Bonus section: Annotation spans and text parceling

In a previous article we mentioned that only framework spans are parceled when styled text is passed either in the same process (via Intent bundles, for example) or in between processes (by copying text).

Because Annotation spans are also ParcelableSpan s, then the <key, value> pairs are parceled and unparceled, making the Annotation span a way of applying custom styling to text that is parceled, as long as the receiving end knows how to interpret the annotations.

So, to keep your custom styling when you pass the text to an Intent Bundle, you first need to add Annotation spans to your text. You can do this in the XML resources via the <annotation> tag, like shown above, or in code by creating a new Annotation and setting it as a span:

val spannableString = SpannableString(“My spantastic text”)

val annotation = Annotation(“font”, “title_emphasis”)

spannableString.setSpan(annotation, 3, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) // start Activity with text with spans

val intent = Intent(this, MainActivity::class.java)

intent.putExtra(TEXT_EXTRA, spannableString)

startActivity(intent)

Retrieve the text from the Bundle as a SpannableString and then parse the Annotations attached, like in the example above.