Spans

Compose ausprobieren
Jetpack Compose ist das empfohlene UI-Toolkit für Android. Weitere Informationen zum Verwenden von Text in der Google-Konversation

Spans sind leistungsstarke Markup-Objekte, mit denen Sie Text auf Zeichen- oder Absatzebene formatieren können. Wenn Sie Spans an Textobjekte anhängen, können Sie den Text auf verschiedene Arten ändern, z. B. Farbe hinzufügen, den Text anklickbar machen, die Textgröße skalieren und Text auf benutzerdefinierte Weise zeichnen. Mit Spans können auch TextPaint-Eigenschaften geändert, auf einem Canvas gezeichnet und das Textlayout geändert werden.

Android bietet mehrere Arten von Spans, die eine Vielzahl gängiger Textstilmuster abdecken. Sie können auch eigene Spans erstellen, um benutzerdefiniertes Styling anzuwenden.

Span erstellen und anwenden

Zum Erstellen einer Span können Sie eine der in der folgenden Tabelle aufgeführten Klassen verwenden. Die Klassen unterscheiden sich danach, ob der Text selbst veränderbar ist, ob das Text-Markup veränderbar ist und welche zugrunde liegende Datenstruktur die Span-Daten enthält.

Klasse Veränderlicher Text Veränderliches Markup Datenstruktur
SpannedString Nein Nein Lineares Array
SpannableString Nein Ja Lineares Array
SpannableStringBuilder Ja Ja Intervallbaum

Alle drei Klassen erweitern die Benutzeroberfläche Spanned. SpannableString und SpannableStringBuilder erweitern auch die Schnittstelle Spannable.

So entscheiden Sie, welche Sie verwenden sollten:

  • Wenn Sie den Text oder das Markup nach dem Erstellen nicht ändern, verwenden Sie SpannedString.
  • Wenn Sie einem einzelnen Textobjekt eine kleine Anzahl von Spannen hinzufügen möchten und der Text selbst nur lesbar ist, verwenden Sie SpannableString.
  • Wenn Sie Text nach dem Erstellen ändern und Spans an den Text anhängen möchten, verwenden Sie SpannableStringBuilder.
  • Wenn Sie einem Textobjekt eine große Anzahl von Spannen zuordnen möchten, unabhängig davon, ob der Text selbst nur lesbar ist, verwenden Sie SpannableStringBuilder.

Wenn Sie einen Span anwenden möchten, rufen Sie setSpan(Object _what_, int _start_, int _end_, int _flags_) auf ein Spannable-Objekt auf. Der Parameter what bezieht sich auf den Bereich, den Sie auf den Text anwenden. Die Parameter start und end geben den Textabschnitt an, auf den Sie den Bereich anwenden.

Wenn Sie Text innerhalb der Grenzen einer Span einfügen, wird die Span automatisch so erweitert, dass sie den eingefügten Text enthält. Wenn Text an den Rändern der Span eingefügt wird, also an den Indizes start oder end, bestimmt der Parameter flags, ob die Span um den eingefügten Text erweitert wird. Verwenden Sie das Flag Spannable.SPAN_EXCLUSIVE_INCLUSIVE, um eingefügten Text einzubeziehen, und das Flag Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, um ihn auszuschließen.

Das folgende Beispiel zeigt, wie ein ForegroundColorSpan an einen String angehängt wird:

Kotlin

val spannable = SpannableStringBuilder("Text is spantastic!")
spannable.setSpan(
    ForegroundColorSpan(Color.RED),
    8, // start
    12, // end
    Spannable.SPAN_EXCLUSIVE_INCLUSIVE
)

Java

SpannableStringBuilder spannable = new SpannableStringBuilder("Text is spantastic!");
spannable.setSpan(
    new ForegroundColorSpan(Color.RED),
    8, // start
    12, // end
    Spannable.SPAN_EXCLUSIVE_INCLUSIVE
);
Ein Bild mit grauem Text, teilweise rot
Abbildung 1. Text mit einem ForegroundColorSpan als Stil

Da die Spanne mit Spannable.SPAN_EXCLUSIVE_INCLUSIVE festgelegt wird, wird sie so erweitert, dass eingefügter Text an den Rändern der Spanne eingeschlossen wird, wie im folgenden Beispiel gezeigt:

Kotlin

val spannable = SpannableStringBuilder("Text is spantastic!")
spannable.setSpan(
    ForegroundColorSpan(Color.RED),
    8, // start
    12, // end
    Spannable.SPAN_EXCLUSIVE_INCLUSIVE
)
spannable.insert(12, "(& fon)")

Java

SpannableStringBuilder spannable = new SpannableStringBuilder("Text is spantastic!");
spannable.setSpan(
    new ForegroundColorSpan(Color.RED),
    8, // start
    12, // end
    Spannable.SPAN_EXCLUSIVE_INCLUSIVE
);
spannable.insert(12, "(& fon)");
Ein Bild, das zeigt, wie die Spanne mehr Text enthält, wenn SPAN_EXCLUSIVE_INCLUSIVE verwendet wird.
Abbildung 2. Wenn Sie Spannable.SPAN_EXCLUSIVE_INCLUSIVE verwenden, wird die Spanne erweitert, um zusätzlichen Text aufzunehmen.

Sie können demselben Text mehrere Spannen hinzufügen. Im folgenden Beispiel wird gezeigt, wie Sie fett formatierten und roten Text erstellen:

Kotlin

val spannable = SpannableString("Text is spantastic!")
spannable.setSpan(ForegroundColorSpan(Color.RED), 8, 12, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
spannable.setSpan(
    StyleSpan(Typeface.BOLD),
    8,
    spannable.length,
    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)

Java

SpannableString spannable = new SpannableString("Text is spantastic!");
spannable.setSpan(
    new ForegroundColorSpan(Color.RED),
    8, 12,
    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
spannable.setSpan(
    new StyleSpan(Typeface.BOLD),
    8, spannable.length(),
    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
Ein Bild mit Text mit mehreren Spans: `ForegroundColorSpan(Color.RED)` und `StyleSpan(BOLD)`
Abbildung 3. Text mit mehreren Spannweiten: ForegroundColorSpan(Color.RED) und StyleSpan(BOLD).

Android-Span-Typen

Android bietet über 20 Span-Typen im Paket android.text.style. Android kategorisiert Spannen auf zwei Arten:

  • Auswirkungen der Span-Option auf Text: Eine Span-Option kann sich auf die Textdarstellung oder Textmesswerte auswirken.
  • Umfang der Spans: Einige Spans können auf einzelne Zeichen angewendet werden, während andere auf einen ganzen Absatz angewendet werden müssen.
Ein Bild mit verschiedenen Spannweitenkategorien
Abbildung 4 Kategorien von Android-Bereichen

Diese Kategorien werden in den folgenden Abschnitten näher beschrieben.

Spans, die sich auf die Textdarstellung auswirken

Einige Spans, die auf Zeichenebene angewendet werden, wirken sich auf das Erscheinungsbild des Texts aus, z. B. das Ändern der Text- oder Hintergrundfarbe und das Hinzufügen von Unterstreichungen oder Durchstreichen. Diese Spannen erweitern die Klasse CharacterStyle.

Im folgenden Codebeispiel wird gezeigt, wie Text mit UnderlineSpan unterstrichen wird:

Kotlin

val string = SpannableString("Text with underline span")
string.setSpan(UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

Java

SpannableString string = new SpannableString("Text with underline span");
string.setSpan(new UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Ein Bild, das zeigt, wie Text mit einem „UnderlineSpan“ unterstrichen wird
Abbildung 5: Text, der mit einem UnderlineSpan unterstrichen ist

Spans, die sich nur auf die Textdarstellung auswirken, lösen ein Neuzeichnen des Texts aus, ohne dass das Layout neu berechnet wird. Diese Bereiche implementieren UpdateAppearance und erweitern CharacterStyle. CharacterStyle-Unterklassen definieren, wie Text gezeichnet wird, indem sie Zugriff zum Aktualisieren des TextPaint gewähren.

Spans, die sich auf Textmesswerte auswirken

Andere Bereiche, die auf Zeichenebene angewendet werden, wirken sich auf Textmesswerte wie Zeilenhöhe und Textgröße aus. Diese Spannen erweitern die Klasse MetricAffectingSpan.

Im folgenden Codebeispiel wird ein RelativeSizeSpan erstellt, mit dem die Textgröße um 50 % erhöht wird:

Kotlin

val string = SpannableString("Text with relative size span")
string.setSpan(RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

Java

SpannableString string = new SpannableString("Text with relative size span");
string.setSpan(new RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Ein Bild, das die Verwendung von RelativeSizeSpan zeigt
Abbildung 6: Text wurde mit einem RelativeSizeSpan vergrößert.

Wenn Sie einen Bereich anwenden, der sich auf Textmesswerte auswirkt, wird der Text von einem beobachtenden Objekt noch einmal für das korrekte Layout und Rendering gemessen. Wenn Sie beispielsweise die Textgröße ändern, werden Wörter möglicherweise auf verschiedenen Zeilen angezeigt. Wenn Sie die vorherige Spanne anwenden, werden der Text neu gemessen, das Textlayout neu berechnet und der Text neu gezeichnet.

Spans, die sich auf Textmesswerte auswirken, erweitern die Klasse MetricAffectingSpan. Bei dieser abstrakten Klasse können Unterklassen über den Zugriff auf die TextPaint definieren, wie sich die Span auf die Textmessung auswirkt. Da MetricAffectingSpan CharacterStyle erweitert, wirken sich Unterklassen auf die Darstellung des Texts auf Zeichenebene aus.

Bereiche, die sich auf Absätze auswirken

Ein Span kann sich auch auf Text auf Absatzebene auswirken, z. B. durch Ändern der Ausrichtung oder des Randes eines Textblocks. Für Bereiche, die sich auf ganze Absätze auswirken, wird ParagraphStyle verwendet. Wenn Sie diese Bereiche verwenden möchten, hängen Sie sie an den gesamten Absatz an, mit Ausnahme des abschließenden Zeilenumbruchzeichens. Wenn Sie versuchen, einen Absatzbereich auf etwas anderes als einen ganzen Absatz anzuwenden, wird er von Android nicht angewendet.

Abbildung 8 zeigt, wie Android Absätze im Text trennt.

Abbildung 7: Auf Android-Geräten enden Absätze mit dem Zeichen für einen neuen Absatz (\n).

Im folgenden Codebeispiel wird ein QuoteSpan auf einen Absatz angewendet. Wenn Sie die Span-Elemente an einer anderen Position als am Anfang oder Ende eines Absatzes anbringen, wird der Stil von Android nicht angewendet.

Kotlin

spannable.setSpan(QuoteSpan(color), 8, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)

Java

spannable.setSpan(new QuoteSpan(color), 8, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
Ein Bild mit einem Beispiel für QuoteSpan
Abbildung 8. Ein QuoteSpan, das auf einen Absatz angewendet wird.

Benutzerdefinierte Spans erstellen

Wenn Sie mehr Funktionen benötigen als die in den vorhandenen Android-Spans bereitgestellten, können Sie einen benutzerdefinierten Span implementieren. Entscheiden Sie bei der Implementierung Ihrer eigenen Span, ob sie sich auf Text auf Zeichen- oder Absatzebene auswirken soll und ob sie das Layout oder die Darstellung des Texts beeinflussen soll. So können Sie leichter feststellen, welche Basisklassen Sie erweitern können und welche Schnittstellen Sie möglicherweise implementieren müssen. Die folgende Tabelle dient als Referenz:

Szenario Klasse oder Schnittstelle
Die Span wirkt sich auf den Text auf Zeichenebene aus. CharacterStyle
Die Spanne wirkt sich auf die Textdarstellung aus. UpdateAppearance
Die Spanne wirkt sich auf Textmesswerte aus. UpdateLayout
Die Spanne wirkt sich auf den Text auf Absatzebene aus. ParagraphStyle

Wenn Sie beispielsweise eine benutzerdefinierte Span-Einheit implementieren möchten, die die Textgröße und ‑farbe ändert, erweitern Sie RelativeSizeSpan. Durch Vererbung erweitert RelativeSizeSpan CharacterStyle und implementiert die beiden Update-Schnittstellen. Da diese Klasse bereits Callbacks für updateDrawState und updateMeasureState bereitstellt, können Sie diese Callbacks überschreiben, um Ihr benutzerdefiniertes Verhalten zu implementieren. Im folgenden Code wird ein benutzerdefinierter Span erstellt, der RelativeSizeSpan erweitert und den updateDrawState-Callback überschreibt, um die Farbe des TextPaint festzulegen:

Kotlin

class RelativeSizeColorSpan(
    size: Float,
    @ColorInt private val color: Int
) : RelativeSizeSpan(size) {
    override fun updateDrawState(textPaint: TextPaint) {
        super.updateDrawState(textPaint)
        textPaint.color = color
    }
}

Java

public class RelativeSizeColorSpan extends RelativeSizeSpan {
    private int color;
    public RelativeSizeColorSpan(float spanSize, int spanColor) {
        super(spanSize);
        color = spanColor;
    }
    @Override
    public void updateDrawState(TextPaint textPaint) {
        super.updateDrawState(textPaint);
        textPaint.setColor(color);
    }
}

In diesem Beispiel wird gezeigt, wie Sie einen benutzerdefinierten Span erstellen. Sie können denselben Effekt erzielen, indem Sie RelativeSizeSpan und ForegroundColorSpan auf den Text anwenden.

Nutzung von Spannen testen

Über die Spanned-Oberfläche können Sie Spans sowohl festlegen als auch aus Text abrufen. Implementieren Sie beim Testen einen Android-JUnit-Test, um zu prüfen, ob die richtigen Spans an den richtigen Stellen hinzugefügt werden. Die Beispiel-App für Textformatierung enthält einen Bereich, in dem Aufzählungspunkte durch Anhängen von BulletPointSpan an den Text mit Markup versehen werden. Im folgenden Codebeispiel wird gezeigt, wie Sie testen, ob die Aufzählungspunkte wie erwartet angezeigt werden:

Kotlin

@Test fun textWithBulletPoints() {
   val result = builder.markdownToSpans("Points\n* one\n+ two")

   // Check whether the markup tags are removed.
   assertEquals("Points\none\ntwo", result.toString())

   // Get all the spans attached to the SpannedString.
   val spans = result.getSpans<Any>(0, result.length, Any::class.java)

   // Check whether the correct number of spans are created.
   assertEquals(2, spans.size.toLong())

   // Check whether the spans are instances of BulletPointSpan.
   val bulletSpan1 = spans[0] as BulletPointSpan
   val bulletSpan2 = spans[1] as BulletPointSpan

   // Check whether the start and end indices are the expected ones.
   assertEquals(7, result.getSpanStart(bulletSpan1).toLong())
   assertEquals(11, result.getSpanEnd(bulletSpan1).toLong())
   assertEquals(11, result.getSpanStart(bulletSpan2).toLong())
   assertEquals(14, result.getSpanEnd(bulletSpan2).toLong())
}

Java

@Test
public void textWithBulletPoints() {
    SpannedString result = builder.markdownToSpans("Points\n* one\n+ two");

    // Check whether the markup tags are removed.
    assertEquals("Points\none\ntwo", result.toString());

    // Get all the spans attached to the SpannedString.
    Object[] spans = result.getSpans(0, result.length(), Object.class);

    // Check whether the correct number of spans are created.
    assertEquals(2, spans.length);

    // Check whether the spans are instances of BulletPointSpan.
    BulletPointSpan bulletSpan1 = (BulletPointSpan) spans[0];
    BulletPointSpan bulletSpan2 = (BulletPointSpan) spans[1];

    // Check whether the start and end indices are the expected ones.
    assertEquals(7, result.getSpanStart(bulletSpan1));
    assertEquals(11, result.getSpanEnd(bulletSpan1));
    assertEquals(11, result.getSpanStart(bulletSpan2));
    assertEquals(14, result.getSpanEnd(bulletSpan2));
}

Weitere Testbeispiele finden Sie unter MarkdownBuilderTest auf GitHub.

Benutzerdefinierte Bereiche testen

Prüfen Sie beim Testen von Spannen, ob die TextPaint die erwarteten Änderungen enthält und ob die richtigen Elemente in der Canvas angezeigt werden. Angenommen, Sie haben eine benutzerdefinierte Span-Implementierung, die einem Text einen Aufzählungspunkt vorangestellt. Der Aufzählungspunkt hat eine bestimmte Größe und Farbe und es gibt eine Lücke zwischen dem linken Rand des Zeichenbereichs und dem Aufzählungspunkt.

Sie können das Verhalten dieser Klasse testen, indem Sie einen AndroidJUnit-Test implementieren und Folgendes prüfen:

  • Wenn Sie den Bereich richtig anwenden, wird auf dem Canvas ein Aufzählungspunkt der angegebenen Größe und Farbe angezeigt und zwischen dem linken Rand und dem Aufzählungspunkt ist der richtige Abstand vorhanden.
  • Wenn Sie die Spanne nicht anwenden, wird das benutzerdefinierte Verhalten nicht angezeigt.

Die Implementierung dieser Tests finden Sie im Beispiel für Textstil auf GitHub.

Sie können Canvas-Interaktionen testen, indem Sie den Canvas mocken, das gemockte Objekt an die Methode drawLeadingMargin() übergeben und prüfen, ob die richtigen Methoden mit den richtigen Parametern aufgerufen werden.

Weitere Beispiele für Span-Tests finden Sie unter BulletPointSpanTest.

Best Practices für die Verwendung von Spans

Je nach Bedarf gibt es mehrere speichereffiziente Möglichkeiten, Text in einer TextView zu setzen.

Spans anhängen oder trennen, ohne den zugrunde liegenden Text zu ändern

TextView.setText() enthält mehrere Überladungen, die Übergänge unterschiedlich behandeln. Mit dem folgenden Code können Sie beispielsweise ein Spannable-Textobjekt festlegen:

Kotlin

textView.setText(spannableObject)

Java

textView.setText(spannableObject);

Wenn Sie diese Überladung von setText() aufrufen, erstellt TextView eine Kopie Ihrer Spannable als SpannedString und speichert sie im Arbeitsspeicher als CharSequence. Das bedeutet, dass Text und Spans unveränderlich sind. Wenn Sie Text oder Spans aktualisieren möchten, erstellen Sie ein neues Spannable-Objekt und rufen Sie setText() noch einmal auf. Dadurch werden das Layout noch einmal vermessen und neu gezeichnet.

Wenn Sie angeben möchten, dass die Spans veränderbar sein müssen, können Sie stattdessen setText(CharSequence text, TextView.BufferType type) verwenden, wie im folgenden Beispiel gezeigt:

Kotlin

textView.setText(spannable, BufferType.SPANNABLE)
val spannableText = textView.text as Spannable
spannableText.setSpan(
     ForegroundColorSpan(color),
     8, spannableText.length,
     SPAN_INCLUSIVE_INCLUSIVE
)

Java

textView.setText(spannable, BufferType.SPANNABLE);
Spannable spannableText = (Spannable) textView.getText();
spannableText.setSpan(
     new ForegroundColorSpan(color),
     8, spannableText.getLength(),
     SPAN_INCLUSIVE_INCLUSIVE);

In diesem Beispiel führt der Parameter BufferType.SPANNABLE dazu, dass die TextView eine SpannableString erstellt. Das vom TextView aufbewahrte CharSequence-Objekt enthält jetzt veränderbares Markup und unveränderlichen Text. Wenn Sie die Span aktualisieren möchten, rufen Sie den Text als Spannable ab und aktualisieren Sie die Spans dann nach Bedarf.

Wenn Sie Bereiche anhängen, trennen oder neu positionieren, wird das TextView automatisch aktualisiert, um die Änderung am Text widerzuspiegeln. Wenn Sie ein internes Attribut einer vorhandenen Span ändern, rufen Sie invalidate() auf, um anzeigebezogene Änderungen vorzunehmen, oder requestLayout(), um metrikbezogene Änderungen vorzunehmen.

Text in einer TextView mehrmals festlegen

In einigen Fällen, z. B. bei der Verwendung eines RecyclerView.ViewHolder, möchten Sie möglicherweise einen TextView wiederverwenden und den Text mehrmals festlegen. Unabhängig davon, ob Sie BufferType festgelegt haben, wird standardmäßig eine Kopie des CharSequence-Objekts erstellt und im Arbeitsspeicher gehalten.TextView So sind alle TextView-Änderungen beabsichtigt. Sie können das ursprüngliche CharSequence-Objekt nicht aktualisieren, um den Text zu ändern. Das bedeutet, dass jedes Mal, wenn Sie neuen Text festlegen, die TextView ein neues Objekt erstellt.

Wenn Sie diesen Prozess besser steuern und die zusätzliche Objekterstellung vermeiden möchten, können Sie Ihre eigene Spannable.Factory implementieren und newSpannable() überschreiben. Anstatt ein neues Textobjekt zu erstellen, können Sie das vorhandene CharSequence in ein Spannable umwandeln und zurückgeben, wie im folgenden Beispiel gezeigt:

Kotlin

val spannableFactory = object : Spannable.Factory() {
    override fun newSpannable(source: CharSequence?): Spannable {
        return source as Spannable
    }
}

Java

Spannable.Factory spannableFactory = new Spannable.Factory(){
    @Override
    public Spannable newSpannable(CharSequence source) {
        return (Spannable) source;
    }
};

Sie müssen textView.setText(spannableObject, BufferType.SPANNABLE) verwenden, wenn Sie den Text festlegen. Andernfalls wird die Quelle CharSequence als Spanned-Instanz erstellt und kann nicht in Spannable umgewandelt werden. Dadurch wird in newSpannable() eine ClassCastException geworfen.

Nachdem Sie newSpannable() überschrieben haben, weisen Sie die TextView an, die neue Factory zu verwenden:

Kotlin

textView.setSpannableFactory(spannableFactory)

Java

textView.setSpannableFactory(spannableFactory);

Lege das Spannable.Factory-Objekt einmal fest, direkt nachdem du eine Referenz zu deiner TextView erhalten hast. Wenn Sie ein RecyclerView verwenden, legen Sie das Factory-Objekt fest, wenn Sie die Aufrufe zum ersten Mal steigern. So wird verhindert, dass ein zusätzliches Objekt erstellt wird, wenn RecyclerView ein neues Element an ViewHolder bindet.

Interne Span-Attribute ändern

Wenn Sie nur ein internes Attribut einer veränderbaren Span ändern möchten, z. B. die Aufzählungszeichenfarbe in einer benutzerdefinierten Aufzählungszeichen-Span, können Sie den Overhead vermeiden, der durch das mehrmalige Aufrufen von setText() entsteht. Speichern Sie dazu beim Erstellen der Span eine Referenz darauf. Wenn Sie die Span ändern möchten, können Sie die Referenz ändern und dann je nach Art des geänderten Attributs invalidate() oder requestLayout() auf die TextView aufrufen.

Im folgenden Codebeispiel hat eine benutzerdefinierte Aufzählung die Standardfarbe Rot, die zu Grau wechselt, wenn eine Schaltfläche angetippt wird:

Kotlin

class MainActivity : AppCompatActivity() {

    // Keeping the span as a field.
    val bulletSpan = BulletPointSpan(color = Color.RED)

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        val spannable = SpannableString("Text is spantastic")
        // Setting the span to the bulletSpan field.
        spannable.setSpan(
            bulletSpan,
            0, 4,
            Spanned.SPAN_INCLUSIVE_INCLUSIVE
        )
        styledText.setText(spannable)
        button.setOnClickListener {
            // Change the color of the mutable span.
            bulletSpan.color = Color.GRAY
            // Color doesn't change until invalidate is called.
            styledText.invalidate()
        }
    }
}

Java

public class MainActivity extends AppCompatActivity {

    private BulletPointSpan bulletSpan = new BulletPointSpan(Color.RED);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        SpannableString spannable = new SpannableString("Text is spantastic");
        // Setting the span to the bulletSpan field.
        spannable.setSpan(bulletSpan, 0, 4, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        styledText.setText(spannable);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // Change the color of the mutable span.
                bulletSpan.setColor(Color.GRAY);
                // Color doesn't change until invalidate is called.
                styledText.invalidate();
            }
        });
    }
}

Android KTX-Erweiterungsfunktionen verwenden

Android KTX enthält auch Erweiterungsfunktionen, die die Arbeit mit Spans erleichtern. Weitere Informationen finden Sie in der Dokumentation zum Paket androidx.core.text.