How to set multiple spans on a TextView's text on the same partial text?

58,312

Solution 1

Simply set additional spans. They are going to overlap/merge when neccessary. This code works for me:

final SpannableString text = new SpannableString("Hello stackOverflow");
text.setSpan(new RelativeSizeSpan(1.5f), text.length() - "stackOverflow".length(), text.length(),
            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
text.setSpan(new ForegroundColorSpan(Color.RED), 3, text.length() - 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(text);

Solution 2

I know this a new reply to an already answered question but I'd like to share a utility class I made which makes this task easier.


Java Version

public class SimpleSpanBuilder {
    private class SpanSection{
        private final String text;
        private final int startIndex;
        private final CharacterStyle[] styles;

        private SpanSection(String text, int startIndex,CharacterStyle... styles){
            this.styles = styles;
            this.text = text;
            this.startIndex = startIndex;
        }

        private void apply(SpannableStringBuilder spanStringBuilder){
            if (spanStringBuilder == null) return;
            for (CharacterStyle style : styles){
                spanStringBuilder.setSpan(style, startIndex, startIndex + text.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
            }
        }
    }

    private List<SpanSection> spanSections;
    private StringBuilder stringBuilder;

    public SimpleSpanBuilder(){
        stringBuilder = new StringBuilder();
        spanSections = new ArrayList<>();
    }

    public SimpleSpanBuilder append(String text,CharacterStyle... styles){
        if (styles != null && styles.length > 0) {
            spanSections.add(new SpanSection(text, stringBuilder.length(),styles));
        }
        stringBuilder.append(text);
        return this;
    }

    public SimpleSpanBuilder appendWithSpace(String text,CharacterStyle... styles){
        return append(text.concat(" "),styles);
    }

    public SimpleSpanBuilder appendWithLineBreak(String text,CharacterStyle... styles){
        return append(text.concat("\n"),styles);
    }

    public SpannableStringBuilder build(){
        SpannableStringBuilder ssb = new SpannableStringBuilder(stringBuilder.toString());
        for (SpanSection section : spanSections){
            section.apply(ssb);
        }
        return ssb;
    }

    @Override
    public String toString() {
        return stringBuilder.toString();
    }
}

Usage:

SimpleSpanBuilder ssb = new SimpleSpanBuilder();
ssb.appendWithSpace("Hello");
ssb.append("StackOverflow",new ForegroundColorSpan(Color.RED),new RelativeSizeSpan(1.5));
textView.setText(ssb.build());

Kotlin Version

class SimpleSpanBuilder() {

    class Span {
        private var startIndex: Int = 0
        internal var text: String
        private var styles: Array<out CharacterStyle>

        internal constructor(index: Int, text: String, vararg styles: CharacterStyle) {
            this.startIndex = index
            this.text = text
            this.styles = styles
        }

        constructor(text: String, vararg styles: CharacterStyle) : this(0, text, *styles)

        internal fun setIndex(index: Int): Span {
            return Span(index, this.text, *this.styles)
        }

        internal fun apply(spanStringBuilder: SpannableStringBuilder?) {
            if (spanStringBuilder == null) return
            for (style in styles) {
                spanStringBuilder.setSpan(
                    style,
                    startIndex,
                    startIndex + text.length,
                    Spannable.SPAN_INCLUSIVE_EXCLUSIVE
                )
            }
        }
    }

    private val spanSections = mutableListOf<Span>()
    private val stringBuilder = StringBuilder()

    constructor(text: String, vararg styles: CharacterStyle) : this() {
        plus(Span(text, *styles))
    }

    operator fun plus(span: SimpleSpanBuilder.Span): SimpleSpanBuilder {
        spanSections.add(span.setIndex(stringBuilder.length))
        stringBuilder.append(span.text)
        return this
    }

    fun build(): SpannableStringBuilder {
        val ssb = SpannableStringBuilder(stringBuilder.toString())
        for (section in spanSections) {
            section.apply(ssb)
        }
        return ssb
    }

    override fun toString(): String {
        return stringBuilder.toString()
    }
}

Usage

var ssb = SimpleSpanBuilder("Hello ",ForegroundColorSpan(Color.BLUE))
ssb += SimpleSpanBuilder.Span(
    "StackOverflow",
    ForegroundColorSpan(Color.RED),
    RelativeSizeSpan(1.5f)
)

textView.text = ssb.build()

Solution 3

Most Easy Way?

textView.setText("I love coding");

setHighLightedText(textView,"coding");

Just use below method -

public void setHighLightedText(TextView tv, String textToHighlight) {
    String tvt = tv.getText().toString();
    int ofe = tvt.indexOf(textToHighlight, 0);
    Spannable wordToSpan = new SpannableString(tv.getText());

    for (int ofs = 0; ofs < tvt.length() && ofe != -1; ofs = ofe + 1) {
        ofe = tvt.indexOf(textToHighlight, ofs);
        if (ofe == -1)
            break;
        else {
            // you can change or add more span as per your need
            wordToSpan.setSpan(new RelativeSizeSpan(2f), ofe,ofe + textToHighlight.length(), 0); // set size
            wordToSpan.setSpan(new ForegroundColorSpan(Color.RED), ofe, ofe + textToHighlight.length(), 0);// set color
            tv.setText(wordToSpan, TextView.BufferType.SPANNABLE);
        }
    }
}

Solution 4

Kotlin can help doing this with an extension on SpannableStringBuilder:

fun SpannableStringBuilder.spansAppend(
    text: CharSequence,
    flags: Int,
    vararg spans: Any
): SpannableStringBuilder {
    val start = length
    append(text)

    spans.forEach { span ->
        setSpan(span, start, length, flags)
    }

    return this
}

Examples of usage:

val builder = SpannableStringBuilder()
builder.append("Start of string ")
builder.spansAppend(
    "text spanned",
    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
    RelativeSizeSpan(1.1f),
    ForegroundColorSpan(Color.RED)
)
Share:
58,312
android developer
Author by

android developer

Really like to develop Android apps &amp; libraries on my spare time. Github website: https://github.com/AndroidDeveloperLB/ My spare time apps: https://play.google.com/store/apps/developer?id=AndroidDeveloperLB

Updated on July 05, 2022

Comments

  • android developer
    android developer almost 2 years

    Suppose I have the next text :

    Hello stackOverflow

    And I wish to set the second word to be both RelativeSizeSpan (to set a relative font size) and TextAppearanceSpan (to set the color of the text) , how do I merge them both ?

    All I know is that I can choose one of them , using the next code for example :

    final SpannableString textToShow = new SpannableString("Hello stackOverflow");
    textToShow.setSpan(new RelativeSizeSpan(1.5f), textToShow.length() - "stackOverflow".length(),textToShow.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    textView.setText(textToShow);
    

    But I need to also set the color , or even add other features from other spanning classes ...

    What can I do ?

  • Zielony
    Zielony over 11 years
    random number I had in my head. It's here only to show that you can have multiple spans intersecting each other
  • Arda Kara
    Arda Kara over 9 years
    but how can I add more than one ForegroundcolorSpan for instance?
  • android developer
    android developer over 8 years
    This is actually a nice solution. +1 for the effort.
  • android developer
    android developer over 6 years
    @Zielony What if I have made a new class ? Should it expand ReplacementSpan or something else, so that I will be able to put multiple of them on the same text?
  • Zielony
    Zielony over 6 years
    As far as I know, the algorithm doesn't care about the types
  • Prinkal Kumar
    Prinkal Kumar over 6 years
    how to set click listener on texts
  • W.K.S
    W.K.S over 6 years
    @PrinkalKumar Try the updated answer with ClickableSpan
  • android developer
    android developer about 6 years
    This is nice, but I've already accepted an answer which worked fine. However, I will give you +1 for the effort.
  • android developer
    android developer almost 6 years
    Can you please show a sample or two of how to use your code? I'm not sure I understand what it does...
  • Kevin Robatel
    Kevin Robatel almost 6 years
    @androiddeveloper I have added an example
  • android developer
    android developer almost 6 years
    Oh this is very nice. Thank you for your work and effort. Could be very helpful some day. I hope I will check it out again when I need it. Such short code for such useful thing. I will give you +1 for this.
  • android developer
    android developer almost 6 years
    Wait, this is an already existing function? Someone wrote me this today: stackoverflow.com/a/52331300/878126
  • Kevin Robatel
    Kevin Robatel almost 6 years
    Don't know this. inSpan is from Android-KTX github.com/android/android-ktx/blob/master/src/main/java/…
  • Neeraj
    Neeraj over 5 years
    A detailed example can be found in this link
  • Bugs Happen
    Bugs Happen over 4 years
    Also note that ClickableSpan won't work without textView.movementMethod = LinkMovementMethod.getInstance()
  • Bugs Happen
    Bugs Happen over 4 years
    Also note that ClickableSpan won't work without textView.movementMethod = LinkMovementMethod.getInstance()
  • famfamfam
    famfamfam about 3 years
    hi, did u support image?
  • Ensar Bayhan
    Ensar Bayhan over 2 years
    This should be the accepted answer! Working like a charm, thanks!