Skip to content

Instantly share code, notes, and snippets.

@shekibobo
Created February 12, 2018 15:09
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 shekibobo/f09c81f61d14eb99af2ced5d0eef6bf7 to your computer and use it in GitHub Desktop.
Save shekibobo/f09c81f61d14eb99af2ced5d0eef6bf7 to your computer and use it in GitHub Desktop.
Collection of Extensions for String and SpannableString
package com.collectiveidea.util.string
import android.graphics.Paint
import android.graphics.Typeface
import android.text.TextPaint
import android.text.style.MetricAffectingSpan
class CustomTypefaceSpan(private val typeface: Typeface) : MetricAffectingSpan() {
override fun updateDrawState(drawState: TextPaint) = apply(drawState)
override fun updateMeasureState(paint: TextPaint) = apply(paint)
private fun apply(paint: Paint) {
val oldTypeface = paint.typeface
val oldStyle = oldTypeface?.style ?: 0
val fakeStyle = oldStyle and typeface.style.inv()
if (fakeStyle and Typeface.BOLD != 0) {
paint.isFakeBoldText = true
}
if (fakeStyle and Typeface.ITALIC != 0) {
paint.textSkewX = -0.25f
}
paint.typeface = typeface
}
}
package com.collectiveidea.util.string
import android.content.Context
import android.support.v4.content.res.ResourcesCompat
import android.text.Html
import android.text.Spannable
import android.text.SpannableString
import android.text.Spanned
import android.text.style.AbsoluteSizeSpan
import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import android.text.style.TextAppearanceSpan
import com.collectiveidea.exampleapp.R
import timber.log.Timber
// Wrap a backwards compatible implementation of `Html.fromHtml()` as a string extension.
fun String.fromHtml(): Spanned {
return if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.N) {
@Suppress("DEPRECATION")
Html.fromHtml(this)
} else {
Html.fromHtml(this, Html.FROM_HTML_MODE_LEGACY)
}
}
// Return the string if it is not null or blank, otherwise return null.
// Example usage:
// textView.text = nullableOrBlankOrValidString.presence() ?: "Default Filler Text"
fun String?.presence(): String? {
return if (isNullOrBlank()) null else this
}
// Converts a regular `String` to a `SpannableString`
fun String.spannable(): SpannableString = SpannableString(this)
// Applies a `ForegroundColorSpan` to the receiver with the provided color at
// the indices of the provided substring.
// If the receiver does not contain the matching substring, warn, and ignore.
// Returns the receiver to allow for chaining.
fun SpannableString.colorSpan(substring: String, color: Int): SpannableString {
return applySpan(substring, ForegroundColorSpan(color))
}
// Applies a `TextAppearanceSpan` to the receiver with the provided `TextAppearance`
// style at the indices of the provided substring.
// If the receiver does not contain the matching substring, warn, and ignore.
// Returns the receiver to allow for chaining.
fun SpannableString.textAppearanceSpan(
context: Context,
substring: String,
style: Int
): SpannableString {
val span = TextAppearanceSpan(context, style)
return applySpan(substring, span).also {
// Handle typeface being set by font resource
val fontName = span.family
?.removePrefix("res/font/")
?.replaceAfter(".", "")
?.replace(".", "") ?: ""
val res = fontName.presence()?.let { context.getResId(it, R.font::class.java) } ?: -1
if (res != -1) { fontSpan(context, substring, res) }
}
}
// Applies a `StyleSpan` to the receiver with the provided `Style` value
// at the indices of the provided substring.
// If the receiver does not contain the matching substring, warn, and ignore.
// Returns the receiver to allow for chaining.
fun SpannableString.styleSpan(substring: String, style: Int): SpannableString {
return applySpan(substring, StyleSpan(style))
}
// Applies a `AbsoluteSizeSpan` to the receiver with the provided size
// at the indices of the provided substring
fun SpannableString.sizeSpan(substring: String, size: Int): SpannableString {
return applySpan(substring, AbsoluteSizeSpan(size))
}
// Applies a `CustomTypefaceSpan` to the receiver with the provided font resource
// at the indices of the provided substring.
// If the receiver does not contain the matching substring, warn, and ignore.
// Returns the receiver to allow for chaining.
fun SpannableString.fontSpan(context: Context, substring: String, fontRes: Int): SpannableString {
if (fontRes == -1) {
Timber.w("Font resource not found: -1")
return this
}
val typeface = ResourcesCompat.getFont(context, fontRes)
return typeface?.let { applySpan(substring, CustomTypefaceSpan(typeface)) } ?: this
}
// Applies the provided span to the recevier at the indices of the provided substring.
// If the receiver does not contain the matching substring, warn, and ignore.
// Returns the receiver to allow for chaining.
fun SpannableString.applySpan(substring: String, span: Any): SpannableString {
val start = indexOf(substring, 0, true)
if (start == -1) {
Timber.w("Cannot apply span <$span>: <$this> does not contain substring <$substring>")
return this
}
val end = start + substring.length
setSpan(span, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
return this
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment