Home > Blockchain >  SpannableString looses spans when used multiple times
SpannableString looses spans when used multiple times

Time:01-16

This is the code that I have.

val s = SpannableString("hello")
s.setSpan(StyleSpan(Typeface.BOLD), 0, s.length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
textView.text = TextUtils.concat(s, s)

The text shown in textView is: hellohello

Why aren't both "hello" in bold?

Edit: I am actually trying to concatenate some SpannableStrings with some normal Strings, something like TextUtils.concat("A", s, "B", s, "C") (the result of which I expect to be AhelloBhelloC). Replacing SPAN_INCLUSIVE_EXCLUSIVE with SPAN_INCLUSIVE_INCLUSIVE solves the "hellohello" problem but not this one.

CodePudding user response:

enter image description here

With SPAN_INCLUSIVE_INCLUSIVE:

enter image description here

After some digging I found TextUtils is using SpannableStringBuilder.append which is using SpannableStringBuilder.replace (https://developer.android.com/reference/android/text/SpannableStringBuilder#replace(int, int, java.lang.CharSequence, int, int)) under the hood and the replace is considered an insert at the end. With SPAN_EXCLUSIVE_INCLUSIVE it would not expand the formatting while it would with SPAN_INCLUSIVE_INCLUSIVE:

If the source contains a span with Spanned#SPAN_PARAGRAPH flag, and it does not satisfy the paragraph boundary constraint, it is not retained.

I also checked the resulting span and printed the start/end of the StyleSpan. The first line with SPAN_EXCLUSIVE_INCLUSIVE the last line with SPAN_INCLUSIVE_INCLUSIVE:

01-14 12:44:11.218  5017  5017 E test    : span start/end: 0/5
01-14 12:44:22.575  5079  5079 E test    : span start/end: 0/10

CodePudding user response:

If you append text to a Spannable it doesn't matter whether that text is just plain text or another Spannable. Spans in the original texts will either expand or not depending on the span's flags (Spannable.SPAN_xyz_INCLUSIVE vs Spannable.SPAN_xyz_EXCLUSIVE). That's why the bold formatting will either expand to the whole string with TextUtils.concat("A", s, "B", s, "C") (using Spannable.SPAN_xyz_INCLUSIVE) or not expand at all (using Spannable.SPAN_xyz_EXCLUSIVE).

Your attempt to append an already formatted Spannable won't work because a span can be used just once.

That's why this:

    val s = SpannableStringBuilder("hellohello")
    val span = StyleSpan(Typeface.BOLD)
    s.setSpan(span, 0, 5, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
    s.setSpan(span, 5, 10, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)

will result in hellohello, while this:

    val s = SpannableStringBuilder("hellohello")
    val span1 = StyleSpan(Typeface.BOLD)
    val span2 = StyleSpan(Typeface.BOLD)
    s.setSpan(span1, 0, 5, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
    s.setSpan(span2, 5, 10, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)

will result in hellohello

This answers your question:

Why aren't both "hello" in bold?

Unfortunately your question isn't very specific on what you're actually trying to achieve. If it's simply to have a Spannable with AhelloBhelloC, then you now know how. If your goal is to have a generic solution that allows to append arbitrary Spannable with arbitrary spans and keep those spans (CharacterStyle and ParagraphStyle) then you'll have to find a way to clone arbitrary Spanned texts with arbitrary spans (note if s is a SpannableStringBuilder then s.subSequence(0, s.length) copies spans but doesn't clone them). So here's how I'd do it:

private fun Spannable.clone(): Spannable {
    val clone = SpannableStringBuilder(toString())
    for (span in getSpans(0, length, Any::class.java)) {
        if (span is CharacterStyle || span is ParagraphStyle) {
            val st = getSpanStart(span).coerceAtLeast(0)
            val en = getSpanEnd(span).coerceAtMost(length)
            val fl = getSpanFlags(span)
            val clonedSpan = cloneSpan(span)
            clone.setSpan(clonedSpan, st, en, fl)
        }
    }
    return clone
}

private fun cloneSpan(span: Any): Any {
    return when (span) {
        is StyleSpan -> StyleSpan(span.style)
        // more clone code to be written...
        else -> span
    }
}

Then you can do:

val s  = SpannableString("hello")
s.setSpan(StyleSpan(Typeface.BOLD), 0, s.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.text = TextUtils.concat(s, " not bold ", s.clone())
  •  Tags:  
  • Related