Spanomatic is an Android library that allows you to automatically add spans to text from resources strings.
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.grivos.spanomatic:spanomatic:1.0.1'
implementation 'io.github.inflationx:viewpump:2.0.3'
}
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))
}
}
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.
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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()
}
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>
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
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
Spanomatic was inspired by this blog post by Florina Muntenescu and the Rialto library by Mark Allison.