SpannableString is an Android SDK class from the android.text package that allows you to apply multiple styles to different parts of a single text string in TextView. Unlike HTML markup, SpannableString works at the level of Span objects, controlling the visual display of text: color, size, typeface, underline, and interactive elements. According to Google Developers, SpannableString is used in Android system components for formatting links. It is the primary way to style text without using third-party libraries.
Key Takeaways
SpannableString is an Android class that implements the Spannable interface, storing text along with a set of Span objects that control visual display. Unlike a regular String, SpannableString allows attaching style attributes to specific character ranges: making part of the text red, increasing the font size in a heading, or adding a clickable link inside a paragraph.
The class is in the android.text package and is available since API Level 1. SpannableString is immutable — once created, its structure is fixed, and replacing text requires creating a new object. For dynamic editing, SpannableStringBuilder is used, which supports inserting and deleting characters without losing styles.
CharSequence is the base interface for text data, implemented by String, StringBuilder, and SpannableString. The main difference between SpannableString and String is the support for attaching arbitrary objects to a substring. TextView recognizes the Spannable interface and applies span objects to the corresponding text segments during rendering. If you pass a regular String to TextView, no styles will be applied.
SpannableString stores text as a char[] array and a separate array of span objects with metadata about start and end positions. When setSpan(what, start, end, flags) is called, the what object is saved in the list along with range information. During rendering, TextView sequentially applies all spans that fall within the displayed range, calling updateDrawState and updateMeasureState methods.
CharacterStyle is the base class for spans that affect individual characters regardless of their position in lines. This includes ForegroundColorSpan (text color), RelativeSizeSpan (relative size), StyleSpan (bold and italic), UnderlineSpan (underline), and others. Character spans are applied to each character in the specified range individually.
ParagraphStyle is an interface for spans that affect entire paragraphs. The most well-known representative is AlignmentSpan, which aligns the entire paragraph to the left, center, or right. Paragraph spans must cover the entire paragraph; otherwise, Android ignores their application. This limitation is because alignment or indentation only makes sense for a whole text block.
Span flags are four constants that define span behavior when text is inserted or deleted at the boundaries of its range. SPAN_EXCLUSIVE_EXCLUSIVE keeps the span active only within the original boundaries, SPAN_INCLUSIVE_INCLUSIVE extends it when text is added at the boundaries. SPAN_EXCLUSIVE_INCLUSIVE and SPAN_INCLUSIVE_EXCLUSIVE provide mixed behavior for the start and end of the range.
| Flag | Insertion on Left | Insertion on Right |
|---|---|---|
| SPAN_EXCLUSIVE_EXCLUSIVE | does not include | does not include |
| SPAN_INCLUSIVE_INCLUSIVE | includes | includes |
| SPAN_EXCLUSIVE_INCLUSIVE | does not include | includes |
| SPAN_INCLUSIVE_EXCLUSIVE | includes | does not include |
Choosing the correct flag is critical for Editable text in EditText, where users can insert and delete characters. For read-only TextView, SPAN_EXCLUSIVE_EXCLUSIVE is typically used — the style applies only to the original range and does not expand with programmatic changes.
The Android SDK provides over 25 built-in span classes covering most text styling tasks. Each class implements the CharacterStyle or ParagraphStyle interface and accepts parameters through its constructor. All classes are in the android.text.style package and are available without adding extra dependencies.
ForegroundColorSpan sets the text color for a specified range, accepting a color in int format. BackgroundColorSpan paints the background behind the text, useful for highlighting search results. AbsoluteSizeSpan sets an exact font size in pixels, RelativeSizeSpan applies a multiplier relative to the base text size in TextView.
StyleSpan accepts Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, or BOLD_ITALIC constants and changes the typeface of characters. UnderlineSpan adds underline, StrikethroughSpan adds strikethrough. SuperscriptSpan and SubscriptSpan create superscript and subscript. TypefaceSpan allows setting a custom font via a Typeface object for a text range.
ClickableSpan is an abstract class for creating clickable text segments. When tapped, the onClick() method is called. For clicks to work, TextView must have setMovementMethod(LinkMovementMethod.getInstance()). URLSpan is a subclass of ClickableSpan for hyperlinks with automatic browser opening. ClickableSpan is often combined with ForegroundColorSpan to visually highlight the link in blue.
val spannable = SpannableString("Open developer.android.com")
spannable.setSpan(
URLSpan("https://developer.android.com"),
9, 31, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
spannable.setSpan(
ForegroundColorSpan(Color.BLUE),
9, 31, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
textView.text = spannable
textView.movementMethod = LinkMovementMethod.getInstance()
Without LinkMovementMethod, clicks on URLSpan will not be handled. MovementMethod is responsible for intercepting touch events and finding the ClickableSpan at the touch position. The color span makes the link visible to the user.
Working with SpannableString begins by creating an instance from a text string and sequentially applying spans via the setSpan() method. The method takes four parameters: the span object, start position, end position, and flags. After setting all spans, the object is passed to TextView via setText().
Let us create a string where the first word is red and enlarged. For this, we use ForegroundColorSpan for color and RelativeSizeSpan for scale. Both spans are applied to the same range independently — the order of setSpan calls does not matter.
val text = "Header: remaining text"
val spannable = SpannableString(text)
val colorSpan = ForegroundColorSpan(Color.RED)
val sizeSpan = RelativeSizeSpan(1.5f)
spannable.setSpan(colorSpan, 0, 9, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
spannable.setSpan(sizeSpan, 0, 9, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.text = spannable
Characters from position 0 to 9 receive both styles simultaneously. TextView automatically applies all spans during rendering — no additional calls are needed. RelativeSizeSpan with a multiplier of 1.5f increases the font size by 50% relative to the base.
Html.fromHtml() creates a Spanned object from an HTML string, but the set of supported tags is limited. SpannableString gives full control over every attribute without HTML limitations. If you need to convert HTML to spans and then add custom styles, you can use Html.fromHtml() as a base and then supplement with spans via setSpan().
val htmlText = Html.fromHtml(
"<b>Important:</b> check the data",
Html.FROM_HTML_MODE_LEGACY
)
val spannable = SpannableString(htmlText)
spannable.setSpan(
ForegroundColorSpan(Color.RED),
0, 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
textView.text = spannable
The result — the text “Important:” will be bold (from HTML) and red (from the span). This approach is convenient when working with server content where part of the formatting is specified in HTML and part is added on the client side programmatically.
SpannableString allows applying an unlimited number of spans to the same or overlapping range. Combining spans is a key advantage over HTML markup, where nested tags can conflict. Spans are independent and applied sequentially during rendering.
For example, you can make a text segment simultaneously bold, red, and clickable. To do this, create three spans — StyleSpan, ForegroundColorSpan, and ClickableSpan — and apply each to the same range. The order of application does not affect the result, as each span is responsible for its own text attribute.
If spans of different types overlap partially, each works independently within its own boundaries. ForegroundColorSpan on range 0–10 and StyleSpan(BOLD) on range 5–15 will give bold red text on segment 5–10 and only bold on 10–15. No conflicts arise because each span modifies its own attribute during rendering.
The getSpans(int start, int end, Class type) method returns an array of spans within the specified range. This is useful for checking which styles are already applied or for removing specific spans. With nextSpanTransition(), you can iterate over span change boundaries — this is the foundation for custom TextView implementations that need to know where the style changes.
SpannableStringBuilder is a class for step-by-step construction of styled text with the ability to insert, replace, and delete fragments. Unlike SpannableString, which is created from a ready-made string and is immutable, Builder allows adding text parts sequentially and assigning styles on the fly. This is ideal for composite messages: logs, chats, news headlines with dynamic labels.
Builder implements the Spannable and Editable interfaces, making it compatible with EditText. The user can edit the text, and styles are preserved and correctly shifted when new characters are inserted. SpannableString, being immutable, is not suitable for editable fields.
The append() method returns the builder itself, allowing method chaining. After adding text, spans are applied via setSpan(). Positions are specified relative to the current builder length. Insert() and replace() are also available for more precise content control.
val builder = SpannableStringBuilder()
.append("New ")
.append("comment")
val blue = ForegroundColorSpan(Color.BLUE)
val gray = ForegroundColorSpan(Color.GRAY)
builder.setSpan(blue, 0, 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
builder.setSpan(gray, 6, 17, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.text = builder
The text “New ” is colored blue, and “comment” is gray. When inserting additional characters between them, the span will not affect the new text thanks to EXCLUSIVE flags. Builder automatically adjusts internal indices upon modification.
Using a large number of spans affects the rendering performance of TextView. Each span calls the updateDrawState() or updateMeasureState() method on every redraw. It is recommended to limit the number of spans per TextView to 50–100 for comfortable performance on mid-range devices. Spans that change text size (RelativeSizeSpan, AbsoluteSizeSpan) require layout recalculation on every change, which is significantly more expensive than color or underline spans.
TextAppearanceSpan allows applying an entire set of styles from an android:textAppearance XML resource with a single setSpan() call. Instead of three separate spans (color, size, font), a single TextAppearanceSpan with a style reference is used. This reduces the number of objects and simplifies maintenance — changing the style in the resource automatically applies to all texts using this span.
Creating a new span instance for each setSpan() adds extra load on the garbage collector. It is optimal to create constant span objects if they are used repeatedly. For example, ForegroundColorSpan(Color.RED) can be stored in a companion object and reused. However, spans with state (ClickableSpan with different handlers) must be created individually for each case.
To diagnose span performance issues, use Layout Inspector in Android Studio and the GPU profiler. If a TextView with many spans noticeably lags during scrolling, consider replacing some spans with static styles via TextAppearanceSpan or reducing the number of spans by combining attributes into custom UpdateAppearance implementations.
Frequently Asked Questions
String is an immutable sequence of characters without style support. SpannableString stores the same characters but additionally contains an array of Span objects with formatting information. TextView determines the type of the passed CharSequence and applies spans to the corresponding ranges during rendering. String ignores any style attributes and displays as plain text.
The removeSpan(Object span) method removes a specific span. For complete cleanup, call getSpans(0, length, Object::class.java), which returns an array of all spans, then remove each via removeSpan. Alternatively, create a new SpannableString(text.toString()) without spans. SpannableStringBuilder has a clear() method that removes both text and spans.
SpannableString works in EditText, but SpannableStringBuilder, which implements Editable, is preferred for editable text. EditText requires the Editable interface to track changes. If you pass SpannableString to EditText, the text will display with styles, but during editing Android will convert it to Editable, which may reset some spans.
In Jetpack Compose, Android SDK spans are not used directly. Instead, Compose provides AnnotatedString, its own equivalent of SpannableString with similar capabilities: SpanStyle for individual character styles and ParagraphStyle for paragraphs. Converting SpannableString to AnnotatedString is possible via buildAnnotatedString with iteration over spans.
Create a class that extends CharacterStyle and override the updateDrawState(TextPaint tp) method. Inside the method, modify TextPaint properties: color, stroke width, effects. For metric changes, use UpdateLayout or MetricAffectingSpan. Custom spans are applied via setSpan() just like built-in ones.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also