Spantastic is an Android library that provides a simple and Kotlin fluent API for creating Android Spannable.
This library wrappers SpannableStringBuilder and add methods to easily decorate the text with multiple spans.
absoluteSize align background bold bullet clickable drawableMargin foreground iconMargin image italic leadingMargin lineHeight mask monospace quote relativeSize sansSerif scaleX serif strike style subscript superscript tab textAppearance typeface underline url
Step 1. Add the JitPack repository to your build file
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}Step 2. Add the dependency
dependencies {
implementation 'com.github.wellingtoncabral:android-spantastic:<LATEST-VERSION>'
}dependencies {
implementation ("com.github.wellingtoncabral:android-spantastic:$LATEST_VERSION")
}Use the spantastic{} builder to decorate texts from the SpantasticBuilder context.
This method will return a SpannableStringBuilder.
val span = spantastic {
}
binding.textView.text = spanYou can add text in many more ways.
val span = spantastic {
+"This is an example using unary plus operator\n"
"This is an example using a string invoke\n" {
// Add decorators
}
text("This is an example using text function\n") {
// Add decorators
}
text(context, R.string.my_string) {
// Add decorators
}
}
binding.textView.text = spanIt looks like:
To manipulate the text, add decorators inside the text block.
In the example below, mark the text with multiple decorators/spans.
val span = spantastic {
"Text with bold, underline, absolute size, foreground, and background decorations" {
bold()
underline()
foreground(Color.WHITE)
background(Color.BLACK)
absoluteSize(20)
}
}
binding.textView.text = spanIt looks like:
Note that the decorators are applied for a specific block of text. See an example:
val span = spantastic {
+ "Text with "
"bold" {
bold()
}
+ ", "
"relative size" {
relativeSize(2f)
}
+ ", "
"absolute size" {
absoluteSize(22)
}
+ ", "
"foreground" {
foreground(Color.RED)
absoluteSize(18)
}
+ ", and "
"background "{
background(Color.MAGENTA)
foreground(Color.WHITE)
absoluteSize(18)
}
+ "decorations."
}
binding.textView.text = spanIt looks like:
If you prefer put full text first and then apply decorators, you can set up the start and end position.
Let's take a look at this example that draws the molecular formula of caffeine:
val span = spantastic {
// Example using custom positions
"☕- C8H10N4O2" {
subscript {
start = 4
end = 5
}
subscript {
start = 6
end = 8
}
subscript {
start = 9
end = 10
}
subscript {
start = 11
end = 12
}
}
}
binding.textView.text = spanIn the same way without custom positions:
val span = spantastic {
+ "☕- C"
"8" { subscript() }
+ "H"
"10" { subscript() }
+ "N"
"4" { subscript() }
+ "O"
"2" { subscript() }
}
binding.textView.text = spanIt looks like:
You can create extension functions to build custom decoration components.
In the example below, we created some custom components: h1, title, divider, and contactList.
fun SpantasticBuilder.h1(text: String) {
text {
bold()
absoluteSize(22)
align(Layout.Alignment.ALIGN_CENTER)
divider()
}
}
fun SpantasticBuilder.title(text: String, shouldBreakLine: Boolean = true) {
text {
absoluteSize(10)
underline()
bold()
if (shouldBreakLine) newLine()
}
}
fun SpantasticBuilder.divider() {
newLine()
newLine()
}
fun SpantasticBuilder.contactList(
vararg contacts: String,
onClick: (String) -> Unit
) {
contacts.forEachIndexed { index, contact ->
"@$contact" {
clickable { onClick(contact) }
bold()
foreground(Color.MAGENTA)
}
if (index < contacts.size-1) + ", " else + "."
}
}So, you can use them easily like that:
val span = spantastic {
h1("Example")
title("Contact list: ")
+ "Click to show the contact info "
contactList("Wellington", "Thiago", "Patrick", "Geovana", "Júlia") { contact ->
showContactInfo(contact)
}
divider()
}
binding.textView.text = spanIt looks like:
If you already have an instance of SpannableStringBuilder, you can pass it to the spantastic builder to be appended in the context. For example, you can pass the editText.text as SpannableStringBuilder to the spantastic calling asSpannableStringBuilder like that:
binding.editText.text = spantastic(binding.editText.text.asSpannableStringBuilder()) {
bold()
}The bold decorator will be applied to the existing text in the EditText component.
Spantastic provides 2 ways to work with clickable areas: url and clickable.
Take a look in this simple example:
val span = spantastic {
"Text with url" {
url("https://github.com/wellingtoncabral")
}
newLine()
"Text with clickable area" {
clickable(isUnderlineText = true) {
showActivity()
}
}
}
binding.textView.text = spanIt looks like:
The first example uses url to open the brownser with the given url and the second one uses clickable which invokes the lambda method, in this case showActivity().
Let's take a look in a real use case:
val span = spantastic {
"By continuing, you agree to the Terms os Service and Privacy Policy." {
url("https://github.com/wellingtoncabral") {
start = 32
end = 48
}
clickable(
isUnderlineText = true,
init = {
start = 53
end = 67
}
) {
showActivity()
}
}
}
binding.textView.text = spanIt looks like:
Spantastic provides many ways to add images.
val span = spantastic {
"Text with image from resource id " {
image(context, R.drawable.ic_android, DynamicDrawableSpan.ALIGN_BASELINE)
}
newLine()
"Text with image " {
image(context, bitmap, DynamicDrawableSpan.ALIGN_CENTER)
}
+ " from the Bitmap "
}
binding.textView.text = spanIt looks like:
See also drawableMargin and iconMargin.
Use the textAppearance to set up a Android style resource.
val span = spantastic {
"Text with a custom appearance" {
textAppearance(context, R.style.CustomStyle)
}
}
binding.textView.text = span<resources>
<style name="CustomStyle">
<item name="android:textColor">@color/teal_700</item>
<item name="android:textSize">18sp</item>
<item name="android:textStyle">bold|italic</item>
</style>
</resources>It looks like:
val span = spantastic {
"Text with normal alignment" {
align(Layout.Alignment.ALIGN_NORMAL)
}
newLine()
"Text with center alignment" {
align(Layout.Alignment.ALIGN_CENTER)
}
newLine()
"Text with opposite alignment" {
align(Layout.Alignment.ALIGN_OPPOSITE)
}
}
binding.textView.text = spanIt looks like:
val span = spantastic {
val myTypeface = Typeface.create(
ResourcesCompat.getFont(context, R.font.aguafina_script), Typeface.NORMAL
)
"Text with a custom typeface" {
typeface(myTypeface)
absoluteSize(24)
}
}
binding.textView.text = spanIt looks like:
val span = spantastic {
"Text with a blur mask and absolute size 18" {
mask(BlurMaskFilter(radius = 5f, BlurMaskFilter.Blur.NORMAL))
absoluteSize(18)
}
}
binding.textView.text = spanIt looks like:
To see more and use case implementations, please take a look at the sample app.
https://github.com/wellingtoncabral/android-spantastic/tree/main/sample/src/main/java/com/wcabral/spantastic
