[转] Spans, a Powerful Concept.

转载自:http://flavienlaurent.com/blog/2014/01/31/spans/

Recently, I wrote a blog post about the NewStand app and its ActionBar icon translation effect. Cyril Mottier suggested me to use Spans to fade in/out the ActionBar title which is a very elegant solution.

Moreover, I always wanted to try all available types of Span: ImageSpan, BackgroundColorSpan etc. They are very usefull and simple to use but there is not any documentation and details about them.

So, in this article, I’m going to explore what can be done with Spans of the framework and then, I will show you how to push Spans to the next level.

You can download & install the sample application. Checkout the source.

In the framework

Hierarchy

Main rules:

  • if a Span affects character-level text formatting, it extends CharacterStyle.
  • if a Span affects paragraph-level text formatting, it implements ParagraphStyle
  • if a Span modifies the character-level text appearance, it implements UpdateAppearance
  • if a Span modifies the character-level text metrics|size, it implements UpdateLayout

It gives us beautiful class diagrams like this.

As it’s a bit complicated so I advise you to use a class visualizer (like this) to fully understand the hierarchy.

How it works?

Layout

When you set text on a TextView, it uses the base class Layout to manage text rendering.

The Layout class contains a boolean mSpannedText: true when the text is an instance of Spanned (SpannableString implements Spanned). This class only processes ParagraphStyle Spans.

The draw method calls 2 others methods:

  • drawBackground

For each line of text, if there is a LineBackgroundSpan for a current line, LineBackgroundSpan#drawBackground is called.

  • drawText

For each line of text, it computes LeadingMarginSpan and LeadingMarginSpan2 and calls LeadingMarginSpan#drawLeadingMargin when it’s necessary. This is also where AlignmentSpan is used to determine the text alignment. Finally, if the current line is spanned, Layout calls TextLine#draw (a TextLine object is created for each line).

TextLine

android.text.TextLine documentation says: Represents a line of styled text, for measuring in visual order and for rendering.

TextLine class contains 3 sets of Spans:

  • MetricAffectingSpan set
  • CharacterStyle set
  • ReplacementSpan set

The interesting method is TextLine#handleRun. It’s where all Spans are used to render the text. Relative to the type of Span, TextLine calls:

FontMetrics

If you want to know more about what is font metrics, just look at the following schema:

Playground

BulletSpan

android.text.style.BulletSpan

The BulletSpan affects paragraph-level text formatting. It allows you to put a bullet on paragraph start.

QuoteSpan

android.text.style.QuoteSpan

The QuoteSpan affects paragraph-level text formatting. It allows you to put a quote vertical line on a paragraph.

AlignmentSpan.Standard

android.text.style.AlignmentSpan.Standard

The AlignmentSpan.Standard affects paragraph-level text formatting. It allows you to align (normal, center, opposite) a paragraph.

UnderlineSpan

android.text.style.UnderlineSpan

The UnderlineSpan affects character-level text formatting. It allows you to underline a character thanks to Paint#setUnderlineText(true) .

StrikethroughSpan

android.text.style.StrikethroughSpan

The StrikethroughSpan affects character-level text formatting. It allows you to strikethrough a character thanks to Paint#setStrikeThruText(true)) .

SubscriptSpan

android.text.style.SubscriptSpan

The SubscriptSpan affects character-level text formatting. It allows you to subscript a character by reducing the TextPaint#baselineShift .

SuperscriptSpan

android.text.style.SuperscriptSpan

The SuperscriptSpan affects character-level text formatting. It allows you to superscript a character by increasing the TextPaint#baselineShift .

BackgroundColorSpan

android.text.style.BackgroundColorSpan

The BackgroundColorSpan affects character-level text formatting. It allows you to set a background color on a character.

ForegroundColorSpan

android.text.style.ForegroundColorSpan

The ForegroundColorSpan affects character-level text formatting. It allows you to set a foreground color on a character.

ImageSpan

android.text.style.ImageSpan

The ImageSpan affects character-level text formatting. It allows you to a character by an image. It’s one of the few span that is well documented so enjoy it!

StyleSpan

android.text.style.StyleSpan

The StyleSpan affects character-level text formatting. It allows you to set a style (bold, italic, normal) on a character.

TypefaceSpan

android.text.style.TypefaceSpan

The TypefaceSpan affects character-level text formatting. It allows you to set a font family (monospace, serif etc) on a character.

TextAppearanceSpan

android.text.style.TextAppearanceSpan

The TextAppearanceSpan affects character-level text formatting. It allows you to set a appearance on a character.

styles.xml

AbsoluteSizeSpan

android.text.style.AbsoluteSizeSpan

The AbsoluteSizeSpan affects character-level text formatting. It allows you to set an absolute text size on a character.

RelativeSizeSpan

android.text.style.RelativeSizeSpan

The RelativeSizeSpan affects character-level text formatting. It allows you to set an relative text size on a character.

ScaleXSpan

android.text.style.ScaleXSpan

The ScaleXSpan affects character-level text formatting. It allows you to scale on x a character.

MaskFilterSpan

android.text.style.MaskFilterSpan

The MaskFilterSpan affects character-level text formatting. It allows you to set a android.graphics.MaskFilter on a character.

Warning: BlurMaskFilter is not supported with hardware acceleration.

BlurMaskFilter

EmbossMaskFilter with a blue ForegroundColorSpan and a bold StyleSpan

Pushing Spans to the next level

Animate the foreground color

ForegroundColorSpan is read-only. It means that you can’t change the foreground color after instanciation. So, the first thing to do is to code a MutableForegroundColorSpan.

Now, we can change alpha or foreground color on the same instance. But when you set those properties, it doesn’t refresh the View: you have to do this manually by re-setting the SpannableString.

Now, we want to animate the foreground color. We use a custom android.util.Property.

Finally, we animate the custom property with an ObjectAnimator. Don’t forget to refresh the View on update.

ActionBar ‘fireworks’

The ‘fireworks’ animation is to make letter fade in randomly. First, cut the text into multiple spans (for example, one span by character) and fade in spans after spans. Using the previously introduced MutableForegroundColorSpan, we are going to create a special object representing a group of span. And for each call to setAlpha on the group, we randomly set the alpha for each span.

We create a custom property to animate the alpha of a FireworksSpanGroup.

Finally, we create the group and animate it with an ObjectAnimator.

Draw with your own Span

In this section, we are going to see a way to draw via a custom Span. This opens interesting perspectives for text customization.

First, we have to create a custom Span that extends the abstract class ReplacementSpan.

If you only want to draw a custom background, you can implements LineBackgroundSpan which is at paragraph-level.

We have to implement 2 methods:

  • getSize: this method returns the new with of your replacement.

text: text managed by the Span

start: start index of text

end: end index of text

fm: font metrics, can be null

  • draw: it’s here you can draw with the Canvas.

x: x-coordinate where to draw the text

top: top of the line

y: the baseline

bottom: bottom of the line

Let’s see an example where we draw a blue rectangle around the text.

Bonus

The Sample app contains some examples of pushing Spans to the next level like:

  • Progressive blur

  • Typewriter

Conclusion

Working on this article, I realised Spans are really powerfull and like Drawables, I think they are not used enough. Text is the main content of an application, it’s everywhere so don’t forget to make it more dynamic and attractive with Spans!

Leave a Reply

Your email address will not be published.