S Fitz
S Fitz

Reputation: 1094

Style text of parameter in getString(int resId, Object... formatArgs) method

I have two String resources as such:

<string name="give_us_feedback">Give us feedback at %1$s if you want to make the app even better!</string>  
<string name="email">[email protected]</string>

I'd like to style the email part to be blue and underlined to indicate that the user can click on it (the whole TextView, not just the email text). I know to use SpannableString to color text, but it doesn't seem to work when I'm combining two strings via getString(int resId, Object... formatArgs), presumably because getString() will perform a cast or a .toString() on the Object being sent. Here's what doesn't work:

TextView emailTV = new TextView(this);
SpannableString email = new SpannableString(getString(R.string.email));
email.setSpan(new UnderlineSpan(), 0, email.length() - 1, 0);
email.setSpan(new ForegroundColorSpan(Color.BLUE), 0, email.length() - 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
String feedback = getString(R.string.give_us_feedback, email);
emailTV.setText(feedback);

Any ideas?

Upvotes: 13

Views: 5782

Answers (4)

android developer
android developer

Reputation: 116322

You can use my answer from here, so your code would be like:

val email:SpannableString 
... - prepare the email variable
val feedback = SpanFormatter.format(getString(R.string.give_us_feedbac),email)

Upvotes: 0

Donny Rozendal
Donny Rozendal

Reputation: 1054

Above answers don't work if your string already contains the same text as the argument.

So this is what I do (in Kotlin). I get the start index of the argument by searching for the literal argument text in the string resource. This is with using a regex.

val text = getString(R.string.id)
val textWithArgs = getString(R.string.id, argument)

// Searches for the start index of %1$s
val startIndex = """%1${"\\$"}s""".toRegex().find(text)?.range?.start
val endIndex = startIndex?.plus(argument.length)

val styledText = if (startIndex == null || endIndex == null) {
    textWithArgs
} else {
    SpannableString(textWithArgs).apply {
        setSpan(
            ForegroundColorSpan(
                ContextCompat.getColor(
                    context,
                    R.color.id
                )
            ), startIndex, endIndex, 0
        )
    }
}

Upvotes: 1

Morgan Koh
Morgan Koh

Reputation: 2465

I've wrote a method to handle it.

isSearchForward is a parameter to toggle whether to search the string from forward or backward, as this only highlights the first occurance.

private fun highlightKeywords(
    highlightColor: Int,
    message: String,
    keyword: String?,
    isSearchForward: Boolean? = true
): SpannableString {
    val spannableString = SpannableString(message)
    if (!keyword.isNullOrBlank()) {
        val startIndex = if (isSearchForward == true) {
            message.indexOf(keyword)
        } else {
            message.lastIndexOf(keyword)
        }
        val endIndex = startIndex + keyword.length

        spannableString.setSpan(UnderlineSpan(), startIndex, endIndex, 0)

        spannableString.setSpan(
            ForegroundColorSpan(highlightColor),
            startIndex,
            endIndex,
            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
        )
    }
    return spannableString
}

Upvotes: 0

Blackbelt
Blackbelt

Reputation: 157447

It's a bit tricky. Converting back to charsequence (String feedback = getString(R.string.give_us_feedback, email);) makes disappear the Spannable. Try this way (you want to check for the correct indexes in your string)

String emailString = getString(R.string.email);
String feedback = getString(R.string.give_us_feedback, emailString);
SpannableString email = new SpannableString(feedback);
int startIndex = feedBack.indexOf(emailString);
int endIndex = startIndex + emailString.length();
email.setSpan(new UnderlineSpan(), startIndex, endIndex, 0);
email.setSpan(new ForegroundColorSpan(Color.BLUE), startIndex, endIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
emailTV.setText(email);

Upvotes: 12

Related Questions