Skip to content

Instantly share code, notes, and snippets.

@muehlemann
Created March 25, 2022 18:26
Show Gist options
  • Save muehlemann/2fd10123b8f77f1c5c463829e03e098e to your computer and use it in GitHub Desktop.
Save muehlemann/2fd10123b8f77f1c5c463829e03e098e to your computer and use it in GitHub Desktop.
package // TODO: Your Package Here
import android.content.Context
import android.text.*
import android.text.Annotation
import android.text.style.CharacterStyle
import android.text.style.ClickableSpan
import android.view.View
import androidx.annotation.ColorInt
import androidx.annotation.StringRes
class StyleUtil {
open class Style(open val key: String) {
data class Bold(override val key: String, val isBold: Boolean) : Style(key)
data class Underline(override val key: String, val isUnderlined: Boolean) : Style(key)
data class ForegroundColor(override val key: String, @ColorInt val color: Int) : Style(key)
data class BackgroundColor(override val key: String, @ColorInt val color: Int) : Style(key)
}
interface Listener {
fun styleClicked(key: String)
}
class Builder {
private var text: Spannable = SpannableStringBuilder("")
private var styles: List<Style> = emptyList()
private var listener: Listener? = null
fun text(context: Context, @StringRes res: Int) = apply { this.text = SpannableStringBuilder(context.getText(res)) }
fun styles(styles: List<Style>) = apply { this.styles = styles }
fun listener(listener: Listener) = apply { this.listener = listener }
fun build(): Spannable {
val annotations = text.getSpans(0, text.length, Annotation::class.java)
for (annotation in annotations) {
text.setSpan(
generateSpans(annotation.value, styles, listener),
text.getSpanStart(annotation),
text.getSpanEnd(annotation),
text.getSpanFlags(annotation)
)
}
return text
}
private fun generateSpans(key: String, styles: List<Style>, listener: Listener?): CharacterStyle {
return object : ClickableSpan() {
override fun onClick(view: View) {
listener?.styleClicked(key)
}
override fun updateDrawState(textPaint: TextPaint) {
for (style in styles) {
if (key == style.key) {
when (style) {
is Style.Bold -> textPaint.isFakeBoldText = style.isBold
is Style.BackgroundColor -> textPaint.bgColor = style.color
is Style.ForegroundColor -> textPaint.color = style.color
is Style.Underline -> textPaint.isUnderlineText = style.isUnderlined
}
}
}
}
}
}
}
}
@muehlemann
Copy link
Author

StyleUtil

Spans are great but can be a pain to work with in applications that are localized. The main reason for this is that in different localizations the start and stop indexes for a span will differ. The following class allows you to apply some basic styles along with a styleClicked(key: String) callback, to strings loaded from XML that utilize the annotation tag.

strings.xml

<resources>
    <string name="str">A <annotation key="foo">test</annotation>. Very <annotation key="bar">cool</annotation>!</string>
</resources>

example.kt

class Example: StyleUtil.Listener {

    fun styleExample() {
        val styles = listOf(
            StyleUtil.Style.Bold("foo", true),
            StyleUtil.Style.Underline("bar", true)
            StyleUtil.Style.ForegroundColor("bar", Color.GRAY)
        )

        val span = StyleUtil.Builder().text(context, R.string.str)
                    .styles(styles)
                    .listener(this)
                    .build()
    }

    // StyleUtil.Listener

    override fun styleClicked(key: String) {
        ...
    }
}

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