Blog Infos
Author
Published
Topics
Published
Topics
A Dive into Text Styling with Annotated String in Jetpack Compose

Photo by Clay Banks on Unsplash

 

Introduction

Text plays a crucial role in user interfaces as a means of communication. Jetpack Compose introduces the AnnotatedString API, a dynamic tool that transcends ordinary text rendering. In this comprehensive guide, we will delve deep into the realm of AnnotatedString in Jetpack Compose.

Through meticulous explanations, a multitude of coding snippets, and real-world examples, you’ll emerge as a master of AnnotatedString, understanding its nuances and recognizing why and when it’s indispensable for your UI endeavors.

Key Concepts
1. Understanding AnnotatedString

AnnotatedString isn’t just a text container; it’s a gateway to dynamic styling. It lets you associate styles with specific text ranges, enabling the creation of text that’s both visually captivating and functionally interactive.

2. Annotations and Styles

AnnotatedString introduces dynamic text styling by allowing you to associate specific styles with defined text ranges. Annotations act as markers, specifying these ranges, while style objects are used to adorn the annotated portions. In our journey, we’ll explore two key style categories — SpanStyle and ParagraphStyle, each offering unique styling capabilities for creating visually compelling and interactive text.

3. Building AnnotatedString

With the buildAnnotatedString DSL, you wield the power to craft an AnnotatedString. Through styles and annotations, you dictate how your text appears to users.

Basic Text Styling
1. Applying Font Styles

The SpanStyle lets you tweak font properties like family, size, weight, and more for specific segments of text.

Font Styles Example

val boldText = buildAnnotatedString {
    withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
        append("Bold Text")
    }
    append("\n\n")
    append("Regular Text")
}
2. Colorful Text

The SpanStyle span allows you to apply colors to the text.

Colorful Text Example

val colorfulText = buildAnnotatedString {
    withStyle(style = SpanStyle(color = Color.Red)) {
        append("Vibrant Text")
    }
    append("\n\n")
    append("Regular Text")
}
3. Background Highlights

Mark / highlight specific text using SpanStyle.

Text Highlighting Example

val highlightedText = buildAnnotatedString {
    withStyle(style = SpanStyle(background = Color.Yellow)) {
        append("Highlighted Text")
    }
    append("\n\n")
    append("Regular Text")
}
Create Clickable Text
1. Clickable Links

Let’s explore how we can add a clickable link inside a text string

Clickable Links Example

val annotatedLinkString: AnnotatedString = buildAnnotatedString {
  val str = "Let's open google!"
  val startIndex = str.indexOf("google")
  val endIndex = startIndex + 6
  append(str)
  addStyle(
      style = SpanStyle(
          color = Color.Red,
          textDecoration = TextDecoration.Underline
      ), start = startIndex, end = endIndex
  )
  addUrlAnnotation(
      UrlAnnotation("https://google.com"),
      start = startIndex,
      end = endIndex
  )
}

val uriHandler = LocalUriHandler.current
ClickableText(
    modifier = Modifier.padding(20.dp).fillMaxWidth(),
    text = annotatedLinkString,
    onClick = {
        annotatedLinkString
            .getUrlAnnotations(it, it)
            .firstOrNull()?.let { annotation ->
                uriHandler.openUri(annotation.item.url)
            }
    }
)

Key Points —

  • AnnotatedString is used to define the text with a clickable link.
  • We identify the range of the link within the text and apply styling (red color and underline) using addStyle.
  • A URL annotation (“https://google.com“) is added to the link range using addUrlAnnotation.
  • The uriHandler is obtained to open URLs.
  • The ClickableText composable displays the annotated string.
  • When the text is clicked, the onClick lambda checks for URL annotations and opens the URL using uriHandler.
2. Expandable Text

Let’s see how we can create a toggleable text composable

Expandable Text Example

// Define tags for expanded and minified text
val tagExpanded = "expanded_text"
val tagMinified = "minified_text"
// Maintain the state of text toggling (expanded or minified)
val textToggleState = remember { mutableStateOf(Pair(tagMinified, "Read more ...")) }
// Create the annotated string for expanded and minified text
val expandedTextString: AnnotatedString = buildAnnotatedString {
    val toggleString = textToggleState.value.second
    
    // Define the base article snippet with a toggle string
    val snippet = "In a groundbreaking discovery, scientists " +
                "have identified a new species of marine life " +
                "in the deep sea. $toggleString"
    
    // Find the start and end indices of the toggle string
    val startIndex = snippet.indexOf(toggleString)
    val endIndex = startIndex + toggleString.length
    
    // Apply styling to the entire snippet (font size and semi-bold)
    withStyle(style = SpanStyle(fontSize = 24.sp)) {
        withStyle(style = SpanStyle(fontWeight = FontWeight.SemiBold)) {
            append(snippet)
        }
        // If the text is expanded, add more article content
        if(textToggleState.value.first == tagExpanded) {
            append(
                "\n\nThis new species, tentatively named " +
                "'DeepSea Marvel,' was found at a depth " +
                "of 4,000 meters beneath the ocean's surface."
            )
        }
    }
    
    // Apply styling to the specified range (magenta color and underline)
    addStyle(
        style = SpanStyle(
            color = Color.Magenta,
            textDecoration = TextDecoration.Underline
        ), start = startIndex, end = endIndex
    )
    
    // Add annotations based on the text state (expanded or minified)
    if(textToggleState.value.first == tagExpanded) {
        addStringAnnotation(
            tagMinified,
            "Read again ...",
            start = startIndex,
            end = endIndex
        )
    } else {
        addStringAnnotation(
            tagExpanded,
            "Show less ...",
            start = startIndex,
            end = endIndex
        )
    }
}
// Create a clickable text composable to display the article text
ClickableText(
    modifier = Modifier.padding(16.dp).fillMaxWidth(),
    text = expandedTextString,
    onClick = {
        // Toggle between expanded and minified text based on annotations
        expandedTextString
            .getStringAnnotations(it, it)
            .firstOrNull()?.let { annotation ->
                if(annotation.tag == tagExpanded) {
                    textToggleState.value = Pair(tagExpanded, annotation.item)
                } else {
                    textToggleState.value = Pair(tagMinified, annotation.item)
                }
            }
    }
)

Key Points —

  1. Text Toggling State — The code maintains the state of text toggling using textToggleState. This state variable tracks whether the text is currently in the expanded or minified state.
  2. AnnotatedString — An AnnotatedString named expandedTextString is used to represent the text. It contains styling information and annotations for managing the expandable behavior.
  3. Toggle String — The toggleString provides context for users to expand or minimize the article content. It dynamically changes based on the text’s state.
  4. Styling — Styling is applied to the text using SpanStyle. The text is set to a larger font size and semi-bold for the headline. Additionally, it uses magenta color and underlining to highlight the toggleString.
  5. Annotations — Annotations are added to the AnnotatedString to indicate whether to show more or less text based on the state. Tags like tagExpanded and tagMinified help manage these annotations. When the text is in the expanded state (i.e., when users click “Read more …”), the code adds an annotation with the tag tagMinified and the text “Read again …” to the same range as the toggle string. This annotation indicates that readers can click to see the content again if they choose to minimize it.
  6. ClickableText Composable — The ClickableText composable displays the article text. When readers click on the text, it checks for annotations and toggles between the expanded and minified states.
Paragraph Text Styling

Paragraph styles in Jetpack Compose allow you to control the visual appearance of text at the paragraph level, providing flexibility in text layout. The following function exemplifies the application of withStyle to craft a paragraph style for the provided text parameter, subsequently justifying the text with a paragraph lineHeight set to 30.sp.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Building State Holders in Compose with Molecule: A New Approach to Reusable UI Components

Are your ViewModels exponentially growing out of control as they manage the state for each of your Composables? This talk introduces Molecule, a new library for creating state holders in Jetpack Compose.
Watch Video

Building State Holders in Compose with Molecule: A New Approach to Reusable UI Components

Jack Adams
Senion Android Engineer
Trainline

Building State Holders in Compose with Molecule: A New Approach to Reusable UI Components

Jack Adams
Senion Android Engin ...
Trainline

Building State Holders in Compose with Molecule: A New Approach to Reusable UI Components

Jack Adams
Senion Android Engineer
Trainline

Jobs

Paragraph Style Example

@Composable
fun LegalLetterText(text: String) {
    val customLineSpacingText = buildAnnotatedString {
        withStyle(
            style = ParagraphStyle(
                lineHeight = 30.sp,
                textAlign = TextAlign.Justify,
            )
        ) {
            append(text)
        }
    }
    Text(
        text = customLineSpacingText,
        modifier = Modifier.fillMaxWidth().padding(16.dp),
    )
}
More Example Ideas Anyone? 💡

Photo by Caroline Roose on Unsplash

Annotated strings offer a versatile toolset for transforming plain text into visually engaging and context-rich content. Here are some creative and practical ideas for leveraging annotated strings —

  1. Stylized Poetry — Use annotated strings to bring your poems to life, applying different styles and colors to emphasize emotions or themes.
  2. Musical Notations — Create musical scores or tutorials with annotated strings, visually representing notes, chords, and sheet music symbols.
  3. Code Commentary — Enhance your code documentation by using annotated strings to provide explanations, warnings, or code annotations.
  4. Syntax Learning — Build interactive learning modules for programming languages, highlighting syntax elements and providing explanations.
  5. Historical Letters — Recreate historical letters, diaries, or documents with annotated strings, preserving their original format and style.
  6. Abstract Art Text — Craft visually striking abstract art pieces using annotated strings, where text itself becomes an art form.
  7. Sci-Fi Terminal — Simulate a futuristic or sci-fi terminal interface with styled text, making your app feel like it’s from a distant future.
  8. ASCII Banner — Generate eye-catching ASCII art banners or headers using annotated strings, ideal for headers or branding.
  9. Emulator Text — Replicate the appearance of vintage computer or game console screens with annotated strings for retro-themed apps.
  10. Code Editor Text — Create a custom code editor with syntax highlighting and annotations for a developer-friendly experience.
  11. Blog Post — Format blog post content dynamically with annotated strings, allowing for rich text formatting, links, and more.
  12. News Feed — Display news articles with custom formatting, emphasizing headlines, images, and metadata using annotated strings.
Example Code Snippets
Sourced from ABC Network
1. Stylized Poetry

Stylized Poetry Example

val stylizedPoetry = buildAnnotatedString {
    withStyle(style = SpanStyle(fontSize = 24.sp)) {
        append("The ")
        withStyle(style = SpanStyle(fontWeight = FontWeight.Bold, color = Color.Red)) {
            append("rose")
        }
        append(" is ")
        withStyle(style = SpanStyle(fontStyle = FontStyle.Italic, color = Color.Green)) {
            append("red")
        }
        append("\n")
        withStyle(style = SpanStyle(textDecoration = TextDecoration.LineThrough, color = Color.Blue)) {
            append("The ")
        }
        withStyle(style = SpanStyle(fontStyle = FontStyle.Italic, color = Color.Red)) {
            append("violets")
        }
        append(" are blue")
    }
}
2. Musical Notations

Musical Notations Example

val musicalNotation = buildAnnotatedString {
    withStyle(style = SpanStyle(fontSize = 24.sp, color = Color.Black)) {
        append("Sheet Music")
    }
    append("\n\n")
    withStyle(style = SpanStyle(fontSize = 36.sp, color = Color.Black)) {
        append("♪")
    }
    append(" ")
    withStyle(style = SpanStyle(fontSize = 20.sp, color = Color.Gray)) {
        append("Piano Sonata in C Minor")
    }
    append("\n")
    withStyle(style = SpanStyle(fontSize = 24.sp, color = Color.Black)) {
        append("pp")
    }
    append(" ")
    withStyle(style = SpanStyle(fontSize = 18.sp, color = Color.Gray)) {
        append("Softly")
    }
    append(" ")
    withStyle(style = SpanStyle(fontSize = 28.sp, color = Color.Black)) {
        append("f")
    }
    append(" ")
    withStyle(style = SpanStyle(fontSize = 18.sp, color = Color.Gray)) {
        append("Loudly")
    }
}
3. Abstract Art Text

Abstract Art Text Example

val abstractArtText = buildAnnotatedString {
    withStyle(style = SpanStyle(fontSize = 42.sp, brush = Brush.horizontalGradient(listOf(Color.Red, Color.Blue, Color.Green)))) {
        append("Colors in Motion")
    }
    append("\n\n")
    append("Vivid shades of ")
    withStyle(style = SpanStyle(brush = Brush.verticalGradient(listOf(Color.Magenta, Color.Yellow)))) {
        append("passion")
    }
    append(" blend with the ")
    withStyle(style = SpanStyle(brush = Brush.horizontalGradient(listOf(Color.Blue, Color.Green)))) {
        append("calmness")
    }
    append(" of an eternal ")
    withStyle(style = SpanStyle(brush = Brush.radialGradient(listOf(Color.Red, Color.Magenta)))) {
        append("sunrise")
    }
    append(". A symphony of ")
    withStyle(style = SpanStyle(brush = Brush.linearGradient(listOf(Color.Blue, Color.Cyan)))) {
        append("colors")
    }
    append(" dances in the ")
    withStyle(style = SpanStyle(brush = Brush.sweepGradient(listOf(Color.Green, Color.Blue, Color.Magenta)))) {
        append("sky")
    }
    append(" like a dream.")
Sample Screenshots

Here are some more sample screenshots for some of the examples listed.

Terminal Text, Blog Post and Code Examples

Note — You can find all the code snippets for examples shown in this article in the gist provided. If there’s a specific one you would like to develop and need help, feel free to reach out!

Why Choose AnnotatedString?

Photo by Markus Winkler on Unsplash

 

1. Interactive Content

AnnotatedString enables you to create interactive text elements within your UI. Whether it’s clickable links, expandable sections, or user-triggered actions, AnnotatedString adds an extra layer of engagement to your app.

2. Rich Text Formatting

AnnotatedString goes beyond plain text. You can apply a myriad of formatting options, such as font styles, colors, highlights, and custom paragraph layouts. This flexibility allows you to bring your text to life and align it with your app’s design.

3. Contextual Styling

AnnotatedString excels at contextual styling. You can emphasize specific words or phrases within a text block, making it perfect for code commentary, highlighting keywords, or providing visual cues in your content.

4. Accessibility

With AnnotatedString, you can ensure that your text remains accessible to all users. Implement annotations to provide additional context for screen readers or other assistive technologies, enhancing the inclusivity of your app.

Conclusion

Whether it’s fine-tuning fonts, creating clickable links, or orchestrating visual symphonies, AnnotatedString empowers you to create UIs that leave an impression.

As you continue your journey with Jetpack Compose, remember this little gem — AnnotatedString. It’s your partner in the quest for delightful, user-friendly interfaces. So, embrace it, explore it, and let your UIs tell stories that captivate, inform, and make people smile.

It’s not about just styling text; it’s about crafting experiences that users remember!

Closing Remarks

If you liked what you read, please feel free to leave your valuable feedback or appreciation. I am always looking to learn, collaborate and grow with fellow developers.

If you have any questions feel free to message me!

Here is the link to the gist containing all the previously mentioned code snippets and code for some of the examples I mentioned at the end.

Follow me on Medium for more articles — Medium Profile

Connect with me on LinkedIn for collaboration — LinkedIn Profile

Also, you’re welcome to follow me on Twitter for more updates and insights — Twitter Profile

Happy Composing!

 

This article was previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
Compose is a relatively young technology for writing declarative UI. Many developers don’t even…
READ MORE
blog
When it comes to the contentDescription-attribute, I’ve noticed a couple of things Android devs…
READ MORE
blog
In this article we’ll go through how to own a legacy code that is…
READ MORE
blog
Compose is part of the Jetpack Library released by Android last spring. Create Android…
READ MORE
Menu