Skip to content

Instantly share code, notes, and snippets.

@vinaysshenoy
Created October 12, 2018 11:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vinaysshenoy/a4b3bbf3d9ee7f707ce3825ce7a63f2d to your computer and use it in GitHub Desktop.
Save vinaysshenoy/a4b3bbf3d9ee7f707ce3825ce7a63f2d to your computer and use it in GitHub Desktop.
Android Resource Annotation Applier
<resources>
<string name="label">This is a string
<annotation textStyle="italic"><annotation textColor="@color/grey">that can have</annotation></annotation>
different styles.
</string>
<color name="grey">#222222</color>
</resources>
package com.vinaysshenoy.util
import android.content.Context
import android.graphics.Typeface
import android.support.annotation.StringRes
import android.support.v4.content.res.ResourcesCompat
import android.text.Annotation
import android.text.Spannable
import android.text.SpannableString
import android.text.SpannedString
import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import timber.log.Timber
/**
* Singleton that encapsulates parsing of [Annotation] markup from string resources and provides
* a standard way of applying them to string resources.
*
* See this [blog post](https://medium.com/androiddevelopers/styling-internationalized-text-in-android-f99759fb7b8f)
* for more information.
**/
object TextAnnotations {
private val processors = listOf(
LoggingProcessor(),
TextStyleProcessor(),
TextForegroundColorResourcesProcessor()
)
fun processAnnotatedString(context: Context, @StringRes resourceId: Int): CharSequence {
val source = context.getText(resourceId) as SpannedString
return source
.getSpans(0, source.length, Annotation::class.java)
.fold(SpannableString(source)) { input, annotation ->
val spanStart = source.getSpanStart(annotation)
val spanEnd = source.getSpanEnd(annotation)
processors
.asSequence()
.filter { it.canProcess(annotation) }
.fold(input) { spannable, processor -> processor.process(context, source, annotation, spanStart, spanEnd, spannable) }
}
}
private class TextForegroundColorResourcesProcessor : Processor {
override fun canProcess(annotation: Annotation) = annotation.key == "textColor"
override fun process(
context: Context,
source: SpannedString,
annotation: Annotation,
spanStart: Int,
spanEnd: Int,
input: SpannableString
): SpannableString {
val colorResourceId = context.resources.getIdentifier(annotation.value, null, context.packageName)
val textColor = ResourcesCompat.getColor(context.resources, colorResourceId, null)
input.setSpan(ForegroundColorSpan(textColor), spanStart, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return input
}
}
private class TextStyleProcessor : Processor {
override fun canProcess(annotation: Annotation) = annotation.key == "textStyle"
override fun process(
context: Context,
source: SpannedString,
annotation: Annotation,
spanStart: Int,
spanEnd: Int,
input: SpannableString
): SpannableString {
val textStyleToApply = annotation.value
val style = when (textStyleToApply) {
"bold" -> Typeface.BOLD
"italic" -> Typeface.ITALIC
"bold_italic" -> Typeface.BOLD_ITALIC
else -> Typeface.NORMAL
}
input.setSpan(StyleSpan(style), spanStart, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return input
}
}
private class LoggingProcessor : Processor {
override fun canProcess(annotation: Annotation) = true
override fun process(
context: Context,
source: SpannedString,
annotation: Annotation,
spanStart: Int,
spanEnd: Int,
input: SpannableString
): SpannableString {
Timber.d("Found annotation: [${annotation.key}:${annotation.value}] on [$source] at [$spanStart, $spanEnd]")
return input
}
}
private interface Processor {
fun canProcess(annotation: Annotation): Boolean
fun process(
context: Context,
source: SpannedString,
annotation: Annotation,
spanStart: Int,
spanEnd: Int,
input: SpannableString
): SpannableString
}
}
class MyActivity: AppCompatActivity() {
lateinit var tv: TextView by lazy { findViewById(R.id.textView) }
override fun onCreate(savedInstanceState: Bundle?) {
tv.text = TextAnnotations.processAnnotatedString(context, R.string.label)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment