Decorating TextView via SpannableString in Android

TextView is an extremely powerful view in Android. Obviously, they’re able to display text, but they can also display several styles of text, different fonts or colors, and even inline images, all within a single TextView. You can have specific portions of text respond to click events and really associate any object you want with any portion of text. These ranges of text are generically referred to as spans as in a span (range) of bold text or a span of subscript.

First, you should know about the two main types of spans defined by the interfaces CharacterStyle and ParagraphStyle. As you can probably guess, these interfaces refer to spans that affect one or more characters and spans that affect entire paragraphs, respectively. Most spans will implement one of these two interfaces (although many implement more than just these). See the following list of built-in spans to get an idea about what is already supported:

  • AbsoluteSizeSpan. A span that allows you to specify an exact size in pixels or density independent pixels.
  • AlignmentSpan. A span that attaches an alignment (from Layout.Alignment).
  • BackgroundColorSpan. A span that specifies a background color (the color behind the text, such as for highlighting).
  • ClickableSpan. A span that has an onClick method that is triggered. (This class is abstract, so you can extend it with a class that specifies the onClick behavior.)
  • DrawableMarginSpan. A span that draws a Drawable plus the specified amount of spacing.
  • DynamicDrawableSpan. A span that you can extend to provide a Drawable that may change (but the size must remain the same).
  • EasyEditSpan. A span that just marks some text so that the TextView can easily delete it.
  • ForegroundColorSpan. A span that changes the color of the text (basically just called setColor(int) on the TextPaint object).
  • IconMarginSpan. A span that draws a Bitmap plus the specified amount of spacing.
  • ImageSpan. A span that draws an image specified as a Bitmap, Drawable, URI, or resource ID.
  • LeadingMarginSpan. A span that adjusts the margin.
  • LocaleSpan. A span that changes the locale of text (available in API level 17 and above).
  • MaskFilterSpan. A span that sets the MaskFilter of the TextPaint (such as for blurring or embossing).
  • MetricAffectingSpan. A span that affects the height and/or width of characters (this is an abstract class).
  • QuoteSpan. A span that puts a vertical line to the left of the selected text to indicate it is a quote; by default the line is blue.
  • RasterizerSpan. A span that sets the Rasterizer of the TextPaint (generally not useful to you).
  • RelativeSizeSpan. A span that changes the text size relative to the supplied float (for instance, setting a 0.5 float will cause the text to render at half size).
  • ReplacementSpan. A span that can be extended when something custom is drawn in place of the spanned text (e.g., ImageSpan extends this).
  • ScaleXSpan. A span that provides a multiplier to use when calling the TextPaint’s setTextScaleX(float) method. (In other words, setting this to 0.5 will cause the text to be scaled to half size along the X-axis, thus appearing squished.)
  • StrikethroughSpan. A span that simply passes true to the TextPaint’s setStrikeThruText(boolean) method, causing the text to have a line through it (useful for showing deleted text, such as in a draft of a document).
  • StyleSpan. A span that adds bold and/or italic to the text.
  • SubscriptSpan. A span that makes the text subscript (below the baseline).
  • SuggestionSpan. A span that holds possible replacement suggestions, such as for a incorrectly spelled word (available in API level 14 and above).
  • SuperscriptSpan. A span that makes the text superscript (above the baseline).
  • TabStopSpan. A span that allows you to specify an offset from the leading margin of a line.
  • TextAppearanceSpan. A span that allows you to pass in a TextAppearance for styling.
  • TypefaceSpan. A span that uses a specific typeface family (monospace, serif, or sans-serif only).
  • UnderlineSpan. A span that underlines the text.
  • URLSpan. A ClickableSpan that attempts to view the specified URL when clicked.

Add click to part of string

Following is layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    android:gravity="center">

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Give us a rating..."
        />
</LinearLayout>

Following is code

TextView tvTitle = (TextView) findViewById(R.id.tvTitle);
String text = "Give us a rating...";

// initialize a new ClickableSpan
ClickableSpan clickableSpan = new ClickableSpan() {
    @Override
    public void onClick(View view) {
        Toast.makeText(activity, "us", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        super.updateDrawState(ds);
        int color = ContextCompat.getColor(activity, R.color.colorPrimary);
        ds.setColor(color);
        ds.setUnderlineText(false);
    }
};

// initialize a new SpannableStringBuilder instance
SpannableStringBuilder ssBuilder = new SpannableStringBuilder(text);

// apply the clickable text to the span
ssBuilder.setSpan(
    clickableSpan, // span to add
    text.indexOf("us"), // start of the span (inclusive)
    text.indexOf("us") + String.valueOf("us").length(), // end of the span (exclusive)
    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE // do not extend the span when text add later
);

// underline
ssBuilder.setSpan(
    new UnderlineSpan(),
    text.indexOf("Give"),
    text.indexOf("Give") + String.valueOf("Give").length(),
    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
);

// bold
ssBuilder.setSpan(
    new StyleSpan(Typeface.BOLD),
    text.indexOf("Give"),
    text.indexOf("Give") + String.valueOf("Give").length(),
    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
);

tvTitle.setText(ssBuilder);

// this step is mandated for the url and clickable styles
tvTitle.setMovementMethod(LinkMovementMethod.getInstance());
tvTitle.setHighlightColor(Color.TRANSPARENT);

The URLSpan and ClickableSpan both require the movementMethod of the TextView to be set as LinkMovementMethod.

android_textview_clickablespan.png

Change color of word in string

Following is code

String text = "Give us a rating...";
SpannableStringBuilder ssBuilder = new SpannableStringBuilder(text);

ssBuilder.setSpan(
    new ForegroundColorSpan(Color.RED),
    text.indexOf("Give"),
    text.indexOf("Give") + String.valueOf("Give").length(),
    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
);

ssBuilder.setSpan(
    new ForegroundColorSpan(Color.BLUE),
    text.indexOf("us"),
    text.indexOf("us") + String.valueOf("us").length(),
    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
);

ssBuilder.setSpan(
    new ForegroundColorSpan(Color.GREEN),
    text.indexOf("rating"),
    text.indexOf("rating") + String.valueOf("rating").length(),
    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
);

tvTitle.setText(ssBuilder);
android_textview_colorspan.png

Following code shows random colors for letters

Spannable span = new SpannableString(text);

for (int i = 0, len = colorfulText.length(); i < len; i++ ) {
    span.setSpan(new ForegroundColorSpan(getRandomColor()), i, i+1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}

tvTitle.setText(span);

private int getRandomColor(){
    Random rnd = new Random();
    return Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
}

Other spans

Change relative size of specified string

int flag = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE;
ssBuilder.setSpan(new RelativeSizeSpan(2), 0, text1.length(), flag);
ssBuilder.setSpan(new RelativeSizeSpan(0.5f), 0, text2.length(), flag);

Change background color of specified string

ssBuilder.setSpan(new BackgroundColorSpan(Color.CYAN), 0, text3.length(), flag);

Set subscript and superscript for string

ssBuilder.setSpan(new SubscriptSpan(), 0, text4.length(), flag);
ssBuilder.setSpan(new SuperscriptSpan(), 0, text5.length(), flag);

Set url for string

styledString.setSpan(new URLSpan("http://www.google.com"), 0, text6.length(), flag);

ImageSpan allows you to use images for span.

SpannableString ssBuilder = new SpannableString("This text is strange");
ssBuilder.setSpan(new ImageSpan(this, R.drawable.ic_launcher), 5, 9, flag);

TextAppearanceSpan allows you to set a resource appearance on a character.

SpannableString ssBuilder = new SpannableString("This text is strange");
ssBuilder.setSpan(new TextAppearanceSpan(this, R.style.SpanTextAppearance), 5, 9, flag);

Style is following

<style name="SpanTextAppearance" parent="@android:style/TextAppearance">
    <item name="android:textColor">#FF0000</item>
    <item name="android:textSize">20sp</item>
    <item name="android:textStyle">italic</item>
</style>

You could also use the Html.fromHtml method to create Spanned Strings using html tags. For an unofficial list of supported tags see this.

Another way to decorate TextView is TextDecorator.

Span for Toast

Spannable text = new SpannableString("Italic green text in toast");
text.setSpan(new StyleSpan(Typeface.ITALIC), 0, 18,  Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
text.setSpan(new ForegroundColorSpan(Color.GREEN), 0, 18,  Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Toast.makeText(activity, text, Toast.LENGTH_LONG).show();

Bonus

Sometimes it is useful to leave a blank at the end of a resource string representing an on-screen field name. To do it insert \u0020 directly in the XML for a blank you would like to preserve.

Example

<string name="score_text">Score :\u0020</string>
comments powered by Disqus