Search code examples
androidtextviewspannablestringformat-stringspannablestringbuilder

How to have a SpannableStringBuilder append a span that's inside a formatted string?


Background

Suppose I use SpannableStringBuilder to append multiple stuff into it, and one of them is string that I format from the strings.xml file, which has a span inside:

SpannableStringBuilder stringBuilder = new SpannableStringBuilder ();
stringBuilder.append(...)...

final SpannableString span = new SpannableString(...);
span.setSpan(new BackgroundColorSpan(0xff990000), ...,...,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
stringBuilder.append(getString(R.string.string_to_format, span));

stringBuilder.append(...)...
textView.setText(stringBuilder);

The problem

Sadly, formatting such a string removes the span itself, so in my case, there won't be any text with a background color.

This happens on the line of the "getString".

What I've tried

If I just append the span alone (without "getString"), it works fine.

I also tried to investigate Html.fromHtml, but it doesn't seem to support a background color for text anyway.

The question

Is it possible to format a string that has a span, yet still have the span within?

More specifically, the input is a string A from the strings.xml file, which only has a placeholder (no special HTML tags), and another string B that is supposed to replace the placeholder at runtime. The string B should have a highlight for a partial text of itself.

In my case, the highlighted text is a something to search for within string B.


Solution

  • OK, I've found an answer to my special end case, but I'd still like to know if there are better ways.

    Here's what I did:

    String stringToSearchAt=...
    String query=...
    int queryIdx = stringToSearchAt.toLowerCase().indexOf(query);
    stringToSearchAt= stringToSearchAt.substring(0,  queryIdx + query.length()) + "<bc/>" + stringToSearchAt.substring(queryIdx + query.length());
    final String formattedStr=getString(..., stringToSearchAt);
    stringBuilder.append(Html.fromHtml(formattedStr, null, new TagHandler() {
                                        int start;
    
                                        @Override
                                        public void handleTag(final boolean opening, final String tag, Editable output, final XMLReader xmlReader) {
                                            switch (tag) {
                                                case "bc":
                                                    if (!opening)
                                                        start = output.length() - query.length();
                                                    break;
                                                case "html":
                                                    if (!opening)
                                                        output.setSpan(new BackgroundColorSpan(0xff00bbaa), start, start + query.length(), 0);
                                            }
                                        }
                                    }));
    

    This is only good for my case, but in the case of general formatting, this won't suffice.