Search code examples
androidkotlintextviewspannableandroid-spannable

Kotlin set two different text styles to dynamically changing text in a text view using SpannableString


I have a text view with a value that can change. For example, it's Pressure. It can have different length - i.e. 999.1 or 1010.2 hPa.

The text looks like this:

enter image description here

I need to set different text styles for the first part of the text (the number - 1024.12) and the second part of the text (the unit of measurement).

So far I have tried:

  1. Using SpannableString and set spans to the same scannable string.

  2. Creating two scannable strings, setting spans on them separately, joining two scannable strings and setting that joined scannable strings as text to text view:

//the value of measurement
val spannable1 = SpannableString(button.customView.tvBottom.text.split(" ")[0]) 
//the unit of measurement
val spannable2 = SpannableString(button.customView.tvBottom.text.split(" ")[1]) 
                
spannable1.setSpan(R.style. Bold20Dark, 0, spannable1.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
spannable2.setSpan(R.style. Regular12Dark, 0, spannable2.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                
val spannableString = spannable1.toString() + spannable2.toString()
button.customView.tvBottom.setText(spannableString)

What stops me is the dynamic change of the number and its length, so I cannot figure out how to set a stable range if it constantly changes.

By different text styles I mean following styles:

 <style name="Bold20Dark">
        <item name="android:textSize">20sp</item>
        <item name="android:lineSpacingExtra">3sp</item>
        <item name="android:textColor">@color/dark</item>
        <item name="android:fontFamily">@font/fonr_bold</item>
    </style>

    <style name="Regular12Dark">
        <item name="android:gravity">center_horizontal</item>
        <item name="android:textSize">12sp</item>
        <item name="android:lineSpacingExtra">4sp</item>
        <item name="android:fontFamily">@font/font_regular</item>
        <item name="android:textColor">@color/grey</item>
    </style>

Is there a way to set two text styles if the range is changeable? Thank you.


Solution

  • You can make a spannable factory, override the newSpannable method and set it to the TextView. You just have to make sure you call the setText as setText(string, BufferType.SPANNABLE)

    val spannableFactory = object : Spannable.Factory() {
        override fun newSpannable(source: CharSequence?): Spannable {
            val spannable = source!!.toSpannable()
            val len1 = source.split(" ")[0].length
            val len2 = source.split(" ")[1].length
    
            spannable.setSpan(ForegroundColorSpan(Color.RED), 0, len1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
            spannable.setSpan(ForegroundColorSpan(Color.BLUE), len1, len1+len2+1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
            return spannable
        }
    }
    button.customView.tvBottom.setSpannableFactory(spannableFactory)
    button.customView.tvBottom.setText("1010.2 hPa", TextView.BufferType.SPANNABLE)