Search code examples
androidkotlinspannablestring

How to ignore certain words or characters whilst using setSpan


Given that we have returned separately a list of animals:

val animals = "cat, dog and mouse" 

Which we then concat to our animalsMessage so it looks as following:

val animalsMessage = "You have identified cat, dog and mouse"

Given my default font colour is white and I only wanted to change the val animals font colour in my animalsMessage, I could do:

animalsMessage.setSpan(
        ForegroundColorSpan(resources.getColor(R.color.yellow, null)),
        animalsMessage.length - animals.length,
        animalsMessage.length,
        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
    )

However, say I wanted to ignore the , and the word and whilst spanning, so they remained the default white colour, how would I go about doing that? I am basing this question on the assumption that there might be an and in the animals string and there might be one , or many.

I believe the answer lies in using a pattern matcher and then ignoring whilst spanning based on finding a match.

Things I have tried:

First, before concat my val animals to my val animalsMessage I tried to format my val animals as described above, to do that, I created the below method:

private fun ignoreSeparators(animals: String): SpannableString {
    val spannable = SpannableString(animals)
    val matcher: Matcher = Pattern.compile(",\\\\and").matcher(animals)
    while (!matcher.matches()) {
        val animal = matcher.group(1)
        val animalIndex: Int = animals?.indexOf(animal) - 1
        spannable.setSpan(ForegroundColorSpan(resources.getColor(R.color.yellow, null)), 0, animalIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
    }
    return spannable
}

I then planned on returning the spanned text and then concating it to my val animalsMessage, however, I get a crash saying that no match is found.


Solution

  • I would recommend doing the following, first, remove the , and and change to a list as follows...

     val animals = listOf("cat", "dog", "mouse")
    

    Then, pass them to the following method that will handle the styling, followed by adding the necessary comma and the and. The rule followed was that the and would always be between the last and second from last animal and all other values no matter how large the list is, would be separated by a comma.

    The second param, prefix, is simply our animalsMessage which we concat to, as mentioned in your question.

    private fun formatAnimalStrings(animals: List<String>, prefix: String): SpannableStringBuilder {
            val lastIndex = animals.size - 1
            val secondLastIndex = lastIndex - 1
            val result = SpannableStringBuilder(prefix)
            animals.forEachIndexed { index, animal ->
                val startIndex = result.length
                result.append(animals[index])
                result.setSpan(
                    ForegroundColorSpan(resources.getColor(R.color.yellow, null)),
                    startIndex,
                    startIndex + animal.length,
                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
                )
                if (index < lastIndex) {
                    if (index == secondLastIndex) {
                        result.append(" and ")
                    } else {
                        result.append(", ")
                    }
                }
            }
            result.append(".")
            return result
        }
    

    This would result in

    You have identified cat, dog and mouse

    (I have used bold to express yellow text colour, since I cannot make the text yellow)