Spanomatic

Project Url: grivos/Spanomatic
Introduction: Automatically add spans to text from Android resources strings
More: Author   ReportBugs   
Tags:

Spanomatic is an Android library that allows you to automatically add spans to text from resources strings.

Getting Started

Dependencies

Add Jitpack in your root build.gradle:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

Spanomatic uses the ViewPump library, that provides an API for pre/post-inflation interceptors.
Add these dependencies to your app module's build.gradle file:

dependencies {
    ...
    implementation 'com.github.grivos:Spanomatic:1.2.1'
    implementation 'io.github.inflationx:viewpump:2.0.3'
}

Initialization

Init ViewPump and add the SpanomaticInterceptor in your app class:

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        ViewPump.init(
            ViewPump.builder()
                .addInterceptor(
                    SpanomaticInterceptor()
                )
                .build()
        )
    }

}

Wrap your activities context:

class BaseActivity : AppCompatActivity() {

    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase))
    }

}

String Resources Annotations

In order for Spanomatic to translate string resources annotations into spans, you need to wrap your strings in special annotations. The mechanic is like so:

<string name="annotated_string">This part isn\'t annotated,  <annotation key="value">but this part is</annotation>.</string>

Spanomatic parses the string resource, looks for these annotations, and depending on the key and value, it adds the appropriate spans. You can also nest annotations like so:

<string name="nested_annotations">This part isn\'t annotated,  <annotation key1="value1"><annotation key2="value2">but this part has two annotations</annotation></annotation>.</string>

Spanomatic supports several annotation keys out of the box, but you can also register handlers for custom annotation keys.

Supported Annotation Keys

fgColor

Description: Adds a ForegroundSpan.
Possible Values: Either a literal color hex ("#9C27B0") or a color reference ("@color/material_purple").
Example:

<string name="fg_color_hex">This text has <annotation fgColor="#9C27B0">purple foreground</annotation></string>

example_fg

bgColor

Description: Adds a BackgroundSpan.
Possible Values: Either a literal color hex or a color reference.
Example:

<string name="bg_color_hex">This text has <annotation bgColor="@color/material_green">green background</annotation></string>

example_bg

relativeTextSize

Description: Adds a RelativeSizeSpan.
Possible Values: The size multiplier.
Example:

<string name="relative_size">This text is two times <annotation relativeTextSize="2">bigger</annotation></string>

example_relative_size

absoluteTextSize

Description: Adds an AbsoluteSizeSpan.
Possible Values: A size identifier. Either a literal (18dp, 16sp, etc.) or a reference ("@dimen/big_text_size").
Example:

<string name="absolute_size">This text size is <annotation absoluteTextSize="20dp">20dp</annotation></string>

example_absolute_size

drawable

Description: Adds an ImageSpan.
Possible Values: A drawable reference and an optional alignment (either baseline or bottom). If the alignment is missing, the default is baseline.
The optional alignment is separated from the drawable reference using the pipe character (|).
Example:

<string name="drawable_with_alignment">This is a <annotation drawable="@drawable/ic_cake_16dp|bottom">cake</annotation> drawable span.</string>

example_drawable

format

Description: Adds one of StrikethroughSpan, StyleSpan, SuperscriptSpan, SubscriptSpan, UnderlineSpan.
Possible Values:

Value Span
strikethrough StrikethroughSpan()
bold StyleSpan(Typeface.BOLD)
italic StyleSpan(Typeface.ITALIC)
boldItalic StyleSpan(Typeface.BOLD_ITALIC)
superscript SuperscriptSpa()
subscript SubscriptSpan()
underline UnderlineSpan()

Example:

<string name="format_string">Spanomatic supports <annotation format="strikethrough">strikethrough</annotation>, <annotation format="bold">bold</annotation>, <annotation format="italic">italic</annotation>, <annotation format="boldItalic">bold italic</annotation>, <annotation format="superscript">superscript</annotation>, <annotation format="subscript">subscript</annotation> and <annotation format="underline">underline</annotation> spans.</string>

example_format

quote

Description: Adds a QuoteSpan.
Possible Values: Either empty or an optional literal color hex or a color reference.
Example:

<string name="quote"><annotation quote="">This is a quote</annotation></string>
<string name="quote_with_color"><annotation quote="#D81B60">This is a quote</annotation></string>

example_quote

bullet

Description: Adds a BulletSpan.
Possible Values: Either empty or an optional gap width (literal dimension or a dimension reference), and an optional literal color hex or a color reference.
Example:

<string name="bullet"><annotation bullet="">This is a bullet</annotation></string>
<string name="bullet_with_gap"><annotation bullet="16dp">This is a bullet</annotation></string>
<string name="bullet_with_gap_and_color"><annotation bullet="16dp|#00574B">This is a bullet</annotation></string>

example_bullet

url

Description: Adds a URLSpan.
Possible Values: A url.
Example:

<string name="url">This text has a <annotation url="https://www.google.com/">UrlSpan</annotation></string>

example_url

leadingMargin

Description: Adds a LeadingMarginSpan.
Possible Values: The size of the leading margin (either a literal dimension or a dimension reference).
Example:

<string name="leading_margin"><annotation leadingMargin="@dimen/leading_margin">This text has a leading margin</annotation></string>

example_leading_margin

typeface

Description: Adds a CustomTypefaceSpan.
Possible Values: The name of this typeface (either a literal string or a string reference).
Example:
First, you need to register a TypefaceProvider (the best place to do so is in your Application onCreate() method):

val latoRegular = ResourcesCompat.getFont(this, R.font.lato_regular)
val latoLight = ResourcesCompat.getFont(this, R.font.lato_light)
val latoBold = ResourcesCompat.getFont(this, R.font.lato_bold)
Spanomatic.typefaceProvider = object : TypefaceProvider {

    override fun getTypeface(name: String): Typeface? {
        return when (name) {
            "latoRegular" -> latoRegular
            "latoLight" -> latoLight
            "latoBold" -> latoBold
            else -> null
        }
    }

}

Then we can use these typefaces in a string resource:

<string name="typefaces">Here we have three different typefaces: <annotation typeface="latoRegular">Lato Regular</annotation>, <annotation typeface="latoLight">Lato Light</annotation> and <annotation typeface="latoBold">Lato Bold</annotation></string>

example_typeface

click

Description: Adds a ListenableClickableSpan (a custom ClickableSpan that can be registered with a click callback).
Note that this span doesn't add an UnderlineSpan, so if that's what you want, you should wrap it in another format="underline" annotation.
Possible Values: The name of this click (so we can register a callback to the span from the code).
Example:

<string name="click">This span is clickable: <annotation format="underline"><annotation click="click1">click me</annotation></annotation></string>

To catch the onClick event, you need to register a callback like so:

clickableTextView.addSpanClickListener("click1") {
    Toast.makeText(this, "Span was clicked", Toast.LENGTH_SHORT).show()
}

example_click_toast

Adding Support For Custom User Annotation Keys

You can register your own annotation span handlers like so:

Spanomatic.setAnnotationSpanHandler("myCustomKey") { annotationValue, context ->
    val myParsedValue = parseValue(annotationValue, context)
    MyCustomSpan(myParsedValue)
}

Then you can use it in your string resources:

<string name="custom">This span is <annotation myCustomKey="someValue">custom</annotation></string>

Using Spanomatic Manually

Spanomatic automatically adds spans in the layout inflation process, but you can also use it manually like so:

val spanned = addSpansFromAnnotations(R.string.annotated_string)
myTextView.text = spanned

Format Arguments

Spanomatic also supports format arguments:

<string name="annotated_string_with_parameters">This text has annotations with parameters: <annotation format="bold">%1$s</annotation>, <annotation format="italic">%2$b</annotation>, <annotation format="underline">%3$d</annotation></string>
val param1 = "text"
val param2 = false
val param3 = 5
val spanned = addSpansFromAnnotations(R.string.annotated_string_with_parameters, param1, param2, param3)
myTextView.text = spanned

Acknowledgements

Spanomatic was inspired by this blog post by Florina Muntenescu and the Rialto library by Mark Allison.

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools