Skip to content

Instantly share code, notes, and snippets.

@radoyankov
Last active January 27, 2023 08:59
Show Gist options
  • Save radoyankov/29833fc1f5ecd577b0581d6de93ff60f to your computer and use it in GitHub Desktop.
Save radoyankov/29833fc1f5ecd577b0581d6de93ff60f to your computer and use it in GitHub Desktop.
Easy Spannable on Kotlin
val spanned = spannable{ bold("some") + italic(" formatted") + color(Color.RED, " text") }
val nested = spannable{ bold(italic("nested ")) + url("www.google.com", "text") }
val noWrapping = bold("no ") + sub("wrapping ) + sup("also ") + "works"
text_view.text = spanned + nested + noWrapping
import android.text.Spannable
import android.text.SpannableString
import android.text.TextUtils
import android.text.style.*
fun spannable(func: () -> SpannableString) = func()
private fun span(s: CharSequence, o: Any) = (if (s is String) SpannableString(s) else s as? SpannableString
?: SpannableString("")).apply { setSpan(o, 0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) }
operator fun SpannableString.plus(s: SpannableString) = SpannableString(TextUtils.concat(this, s))
operator fun SpannableString.plus(s: String) = SpannableString(TextUtils.concat(this, s))
fun bold(s: CharSequence) = span(s, StyleSpan(android.graphics.Typeface.BOLD))
fun italic(s: CharSequence) = span(s, StyleSpan(android.graphics.Typeface.ITALIC))
fun underline(s: CharSequence) = span(s, UnderlineSpan())
fun strike(s: CharSequence) = span(s, StrikethroughSpan())
fun sup(s: CharSequence) = span(s, SuperscriptSpan())
fun sub(s: CharSequence) = span(s, SubscriptSpan())
fun size(size: Float, s: CharSequence) = span(s, RelativeSizeSpan(size))
fun color(color: Int, s: CharSequence) = span(s, ForegroundColorSpan(color))
fun background(color: Int, s: CharSequence) = span(s, BackgroundColorSpan(color))
fun url(url: String, s: CharSequence) = span(s, URLSpan(url))
@m7mdra
Copy link

m7mdra commented Dec 26, 2018

hey great job you did there but you fall short to notice that if we want to apply multiple spans to the same text its not possible and the solution was to create couple extra function to receive SpannableString

import android.text.Spannable
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.TextUtils
import android.text.style.*

fun spannable(func: () -> SpannableString) = func()
private fun span(s: CharSequence, o: Any) =
    (if (s is String) SpannableString(s) else s as? SpannableString
        ?: SpannableString("")).apply { setSpan(o, 0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) }

operator fun SpannableString.plus(s: SpannableString) = SpannableString(TextUtils.concat(this, s))
operator fun SpannableString.plus(s: String) = SpannableString(TextUtils.concat(this, s))

fun bold(s: CharSequence) = span(s, StyleSpan(android.graphics.Typeface.BOLD))
fun bold(s: SpannableString) = span(s, StyleSpan(android.graphics.Typeface.BOLD))
fun italic(s: CharSequence) = span(s, StyleSpan(android.graphics.Typeface.ITALIC))
fun italic(s: SpannableString) = span(s, StyleSpan(android.graphics.Typeface.ITALIC))
fun underline(s: CharSequence) = span(s, UnderlineSpan())
fun underline(s: SpannableString) = span(s, UnderlineSpan())
fun strike(s: CharSequence) = span(s, StrikethroughSpan())
fun strike(s: SpannableString) = span(s, StrikethroughSpan())
fun sup(s: CharSequence) = span(s, SuperscriptSpan())
fun sup(s: SpannableString) = span(s, SuperscriptSpan())
fun sub(s: CharSequence) = span(s, SubscriptSpan())
fun sub(s: SpannableString) = span(s, SubscriptSpan())
fun size(size: Float, s: CharSequence) = span(s, RelativeSizeSpan(size))
fun size(size: Float, s: SpannableString) = span(s, RelativeSizeSpan(size))
fun color(color: Int, s: CharSequence) = span(s, ForegroundColorSpan(color))
fun color(color: Int, s: SpannableString) = span(s, ForegroundColorSpan(color))
fun background(color: Int, s: CharSequence) = span(s, BackgroundColorSpan(color))
fun background(color: Int, s: SpannableString) = span(s, BackgroundColorSpan(color))
fun url(url: String, s: CharSequence) = span(s, URLSpan(url))
fun url(url: String, s: SpannableString) = span(s, URLSpan(url))
fun normal(s: CharSequence) = span(s, SpannableString(s))
fun normal(s: SpannableString) = span(s, SpannableString(s))

this way it possible to nest spanns

spannable {
            italic(underline(bold(size(2f, color(Color.RED, "Red Bold Color")))))
        }

thanks.

@karrel84
Copy link

nice i love it!

@Danielvgftv
Copy link

This is amazing ♥️

@radoyankov
Copy link
Author

radoyankov commented Jun 19, 2019

This is amazing ❤️

Glad you like it ❤️

@Scorpio93
Copy link

Scorpio93 commented Jun 28, 2019

Thank you so much for your great idea.
My solution with using Kotlin extensions.

private const val EMPTY_STRING = ""
private const val FIRST_SYMBOL = 0

fun spannable(func: () -> SpannableString) = func()

private fun span(s: CharSequence, o: Any) = getNewSpannableString(s).apply {
    setSpan(o, FIRST_SYMBOL, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}

private fun getNewSpannableString(charSequence: CharSequence): SpannableString{
    return if (charSequence is String){
        SpannableString(charSequence)
    }else{
        charSequence as? SpannableString ?: SpannableString(EMPTY_STRING)
    }
}

operator fun SpannableString.plus(s: CharSequence) = SpannableString(TextUtils.concat(this, "", s))

fun CharSequence.makeSpannableString() = span(this, Spanned.SPAN_COMPOSING)
fun CharSequence.makeBold() = span(this, StyleSpan(BOLD))
fun CharSequence.makeItalic() = span(this, StyleSpan(ITALIC))
fun CharSequence.makeUnderline() = span(this, UnderlineSpan())
fun CharSequence.makeStrike() = span(this, StrikethroughSpan())
fun CharSequence.makeSuperscript() = span(this, SuperscriptSpan())
fun CharSequence.makeSubscript() = span(this, SubscriptSpan())
fun CharSequence.makeAnotherSize(size : Float) = span(this, RelativeSizeSpan(size))
fun CharSequence.makeAnotherColor(color : Int) = span(this, ForegroundColorSpan(color))
fun CharSequence.makeAnotherBackground(color : Int) = span(this, BackgroundColorSpan(color))
fun CharSequence.makeUrl(url : String) = span(this, URLSpan(url))

Using example:

textView.text = spannable{ 
    "Example".makeSpannableString()
             .makeBold()
             .makeItalic()
             .makeUnderline()
}

@HxBreak
Copy link

HxBreak commented Nov 30, 2020

nice job

@gonztirado
Copy link

gonztirado commented Mar 26, 2021

Thanks for all you guys! I'm using @Scorpio93 implementation, very nice job!

I also added a two more extensions for ClickableSpan:

/** You will need set movementMethod = LinkMovementMethod.getInstance() in the TextView to allow clicking on the span */
fun CharSequence.clickable(listener: View.OnClickListener) : SpannableString {
    val clickSpan = object : ClickableSpan() {
        override fun onClick(widget: View) {
            listener.onClick(widget)
        }
    }
    return span(this, clickSpan)
}
/** You will need set movementMethod = LinkMovementMethod.getInstance() in the TextView to allow clicking on the span */
fun CharSequence.clickableWithoutUnderline(listener: View.OnClickListener) : SpannableString {
    val clickSpan = object : ClickableSpan() {
        override fun onClick(widget: View) {
            listener.onClick(widget)
        }
        override fun updateDrawState(ds: TextPaint) {
            super.updateDrawState(ds)
            ds.isUnderlineText = false
        }
    }
    return span(this, clickSpan)
}

@luckerbai
Copy link

Nice job~Thanks for all you guys!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment