Spans

Schreiben Sie jetzt
Jetpack Compose ist das empfohlene UI-Toolkit für Android. Hier erfahren Sie, wie Sie Text in „Schreiben“ verwenden.

Spans sind leistungsstarke Markup-Objekte, mit denen Sie Text auf Zeichen- oder Absatzebene gestalten können. Durch das Anhängen von Spans an Textobjekte können Sie Text auf verschiedene Arten ändern, z. B. durch Hinzufügen von Farbe, Anpassen des Textes, Skalieren der Textgröße und Benutzerdefiniertes Zeichnen von Text. Außerdem können mit Spans die TextPaint-Eigenschaften geändert, auf einem Canvas gezeichnet werden 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 benutzerdefinierte Stile anzuwenden.

Span erstellen und anwenden

Zum Erstellen von Spans können Sie eine der in der folgenden Tabelle aufgeführten Klassen verwenden. Die Klassen unterscheiden sich je nachdem, ob der Text selbst änderbar ist, ob das Text-Markup ä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 Intervallstruktur

Alle drei Klassen erweitern die Spanned-Schnittstelle. SpannableString und SpannableStringBuilder erweitern außerdem die Spannable-Schnittstelle.

So entscheiden Sie, welche Version Sie verwenden möchten:

  • Wenn Sie den Text oder das Markup nach dem Erstellen nicht ändern, verwenden Sie SpannedString.
  • Wenn Sie eine kleine Anzahl von Spans an ein einzelnes Textobjekt anhängen müssen und der Text selbst schreibgeschützt 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 eine große Anzahl von Spans an ein Textobjekt anhängen möchten, unabhängig davon, ob der Text selbst schreibgeschützt ist, verwenden Sie SpannableStringBuilder.

Rufen Sie zum Anwenden eines Spans setSpan(Object _what_, int _start_, int _end_, int _flags_) für ein Spannable-Objekt auf. Der Parameter what bezieht sich auf den Span, den Sie auf den Text anwenden, und die Parameter start und end geben den Teil des Textes an, auf den Sie den Span anwenden.

Wenn Sie Text innerhalb der Grenzen eines Spans einfügen, wird der Span automatisch so erweitert, dass er den eingefügten Text enthält. Wird Text an den Span-Grenzen, d. h. an den Indexen start oder end, eingefügt, bestimmt der Parameter flags, ob der Span so erweitert wird, dass er den eingefügten Text enthält. Verwenden Sie das Flag Spannable.SPAN_EXCLUSIVE_INCLUSIVE, um eingefügten Text einzuschließen, und Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, um den eingefügten Text 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 ForegroundColorSpan formatiert.

Da der Span mit Spannable.SPAN_EXCLUSIVE_INCLUSIVE festgelegt wird, wird er so erweitert, dass er an den Span-Grenzen eingefügten Text enthält, 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 der Span bei Verwendung von SPAN_EXCLUSIVE_INCLUSIVE mehr Text enthält.
Abbildung 2. Bei Verwendung von Spannable.SPAN_EXCLUSIVE_INCLUSIVE wird der Span um zusätzlichen Text erweitert.

Sie können mehrere Spans an denselben Text anhängen. Das folgende Beispiel zeigt, wie fett und roter Text erstellt wird:

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, das einen Text mit mehreren Spans zeigt: „ForegroundColorSpan(Color.RED)“ und „StyleSpan(BOLD)“
Abbildung 3: Text mit mehreren Spans: ForegroundColorSpan(Color.RED) und StyleSpan(BOLD).

Android-Span-Typen

Android bietet über 20 Span-Typen im Paket android.text.style. Android kategorisiert Spans hauptsächlich auf zwei Arten:

  • Auswirkungen des Spans auf Text: Ein Span kann die Textdarstellung oder Textmesswerte beeinflussen.
  • Span-Bereich: Einige Spans können auf einzelne Zeichen angewendet werden, während andere auf einen ganzen Absatz angewendet werden müssen.
Ein Bild, auf dem verschiedene Span-Kategorien zu sehen sind
Abbildung 4. Kategorien von Android-Spans

In den folgenden Abschnitten werden diese Kategorien ausführlicher beschrieben.

Spans, die sich auf die Textdarstellung auswirken

Einige Spannen, die auf Zeichenebene angewendet werden, beeinflussen die Textdarstellung, z. B. ändern Sie die Text- oder Hintergrundfarbe und fügen Unterstreichen oder Durchstreichen hinzu. Diese Spans erweitern die Klasse CharacterStyle.

Das folgende Codebeispiel zeigt, wie Sie den Text mit einem UnderlineSpan unterstreichen:

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, auf dem zu sehen ist, wie Text mit einem „UnderlineSpan“ unterstrichen wird
Abbildung 5: Text unterstrichen mit UnderlineSpan.

Spans, die sich nur auf die Textdarstellung auswirken, lösen eine Neuzeichnung des Textes ohne Neuberechnung des Layouts aus. Diese Spans implementieren UpdateAppearance und erweitern CharacterStyle. Abgeleitete CharacterStyle-Klassen definieren, wie Text gezeichnet wird, indem sie Zugriff zum Aktualisieren von TextPaint gewähren.

Spans, die sich auf Textmesswerte auswirken

Andere Spans auf Zeichenebene wirken sich auf Textmesswerte wie Zeilenhöhe und Textgröße aus. Diese Spans erweitern die Klasse MetricAffectingSpan.

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

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. Der Text wurde mit einem RelativeSizeSpan vergrößert.

Wenn Sie eine Spanne anwenden, die sich auf Textmesswerte auswirkt, misst ein beobachtendes Objekt den Text noch einmal, um das richtige Layout und Rendering zu gewährleisten. Wenn Sie beispielsweise die Textgröße ändern, können Wörter in verschiedenen Zeilen erscheinen. Das Anwenden des vorherigen Spans löst eine erneute Messung, eine Neuberechnung des Textlayouts und eine Neuzeichnung des Textes aus.

Spans, die sich auf Textmesswerte auswirken, erweitern die Klasse MetricAffectingSpan, eine abstrakte Klasse, mit der Unterklassen definieren können, wie sich der Span auf die Textmessung auswirkt, indem er Zugriff auf TextPaint gewährt. Da MetricAffectingSpan CharacterSpan erweitert, beeinflussen Unterklassen die Darstellung des Textes auf Zeichenebene.

Spans, die sich auf Absätze auswirken

Ein Bereich kann sich auch auf den Text auf Absatzebene auswirken, z. B. zum Ändern der Ausrichtung oder des Rands eines Textblocks. ParagraphStyle wird für Spans implementiert, die ganze Absätze betreffen. Um diese Spans zu verwenden, hängen Sie sie an den gesamten Absatz an, ohne das Zeilenende am Ende. Wenn Sie versuchen, eine Absatzspanne auf etwas anderes als einen ganzen Absatz anzuwenden, wendet Android den Span überhaupt nicht an.

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

Abbildung 7. In Android enden Absätze mit einem Zeilenumbruchzeichen (\n).

Im folgenden Codebeispiel wird ein QuoteSpan auf einen Absatz angewendet. Wenn Sie die Span an eine andere Position als den Anfang oder das Ende eines Absatzes anhängen, wendet Android den Stil überhaupt nicht an.

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);
Bild, das ein Beispiel für QuoteSpan zeigt
Abbildung 8. Ein QuoteSpan, der auf einen Absatz angewendet wird.

Benutzerdefinierte Spans erstellen

Wenn Sie mehr Funktionen benötigen, als in den vorhandenen Android-Spans verfügbar sind, können Sie benutzerdefinierte Spans implementieren. Wenn Sie eine eigene Span implementieren, entscheiden Sie, ob sie sich auf den Text auf Zeichen- oder Absatzebene auswirkt und ob er sich auf das Layout oder die Darstellung des Texts auswirkt. So können Sie besser bestimmen, welche Basisklassen Sie erweitern können und welche Schnittstellen Sie gegebenenfalls implementieren müssen. Die folgende Tabelle dient als Referenz:

Szenario Klasse oder Benutzeroberfläche
Die Spanne wirkt sich auf den Text auf Zeichenebene aus. CharacterStyle
Die Spanne beeinflusst die Textdarstellung. UpdateAppearance
Die Spanne wirkt sich auf die Textmesswerte aus. UpdateLayout
Die Spanne wirkt sich auf den Text auf Absatzebene aus. ParagraphStyle

Wenn Sie beispielsweise einen benutzerdefinierten Span implementieren müssen, der 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. Mit dem folgenden Code wird ein benutzerdefinierter Span erstellt, der RelativeSizeSpan erweitert und den Callback updateDrawState überschreibt, um die Farbe von 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. Den gleichen Effekt können Sie erzielen, wenn Sie RelativeSizeSpan und ForegroundColorSpan auf den Text anwenden.

Span-Nutzung testen

Über die Schnittstelle Spanned können Sie sowohl Spans festlegen als auch Spans aus Text abrufen. Implementieren Sie dabei einen Android JUnit-Test, um zu prüfen, ob die richtigen Spans an den richtigen Orten hinzugefügt werden. Die Beispiel-App für Textstile enthält eine Span, bei der Aufzählungspunkte durch Anhängen von BulletPointSpan mit Markup ausgezeichnet werden. Das folgende Codebeispiel zeigt, wie Sie testen können, 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 auf GitHub unter MarkdownBuilderTest.

Benutzerdefinierte Spans testen

Prüfen Sie beim Testen von Spans, ob TextPaint die erwarteten Änderungen enthält und ob die richtigen Elemente in der Canvas angezeigt werden. Stellen Sie sich beispielsweise eine benutzerdefinierte Span-Implementierung vor, bei der Text ein Aufzählungspunkt vorangestellt wird. Für den Aufzählungspunkt ist eine bestimmte Größe und Farbe festgelegt und zwischen dem linken Rand des gezeichneten Bereichs und dem Aufzählungspunkt befindet sich eine Lücke.

Sie können das Verhalten dieser Klasse testen, indem Sie einen AndroidJUnit-Test implementieren. Achten Sie dabei auf Folgendes:

  • Wenn Sie die Spanne korrekt anwenden, wird ein Aufzählungspunkt in der angegebenen Größe und Farbe auf dem Canvas angezeigt und der korrekte Abstand zwischen dem linken Rand und dem Aufzählungspunkt ist vorhanden.
  • Wenn Sie den Span nicht anwenden, wird keines der benutzerdefinierten Funktionsweisen angezeigt.

Die Implementierung dieser Tests finden Sie im TextStyling-Beispiel auf GitHub.

Sie können Canvas-Interaktionen testen, indem Sie das Canvas simulieren, das Mock-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 Ihren Anforderungen gibt es mehrere speichereffiziente Möglichkeiten, um Text in einem TextView festzulegen.

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

TextView.setText() enthält mehrere Überlastungen, die Spans unterschiedlich verarbeiten. Sie können beispielsweise das Textobjekt Spannable mit dem folgenden Code festlegen:

Kotlin

textView.setText(spannableObject)

Java

textView.setText(spannableObject);

Beim Aufrufen dieser Überlastung von setText() erstellt TextView eine Kopie von Spannable als SpannedString und speichert sie als CharSequence im Arbeitsspeicher. Das bedeutet, dass Ihr Text und die Spans unveränderlich sind. Wenn Sie also den Text oder die Spans aktualisieren müssen, erstellen Sie ein neues Spannable-Objekt und rufen Sie setText() noch einmal auf. Dadurch wird auch das Layout noch einmal gemessen und neu gezeichnet.

Um anzugeben, dass die Spans ä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 sorgt der Parameter BufferType.SPANNABLE dafür, dass TextView eine SpannableString erstellt. Das von TextView beibehaltene Objekt CharSequence hat jetzt änderbares Markup und unveränderlichen Text. Rufen Sie zum Aktualisieren eines Spans den Text als Spannable ab und aktualisieren Sie die Spans dann nach Bedarf.

Wenn Sie Spans anhängen, trennen oder neu positionieren, wird TextView automatisch aktualisiert, um die Änderung im Text widerzuspiegeln. Wenn Sie ein internes Attribut eines vorhandenen Spans ändern, rufen Sie invalidate() auf, um die Darstellung zu ändern, oder requestLayout(), um messwertbezogene Änderungen vorzunehmen.

Text in TextView mehrmals festlegen

In einigen Fällen, z. B. bei Verwendung eines RecyclerView.ViewHolder, können Sie TextView wiederverwenden und den Text mehrmals festlegen. Unabhängig davon, ob Sie BufferType festlegen, erstellt TextView standardmäßig eine Kopie des CharSequence-Objekts und speichert es im Speicher. Dadurch werden alle TextView-Aktualisierungen beabsichtigt. Sie können das ursprüngliche CharSequence-Objekt nicht aktualisieren, um den Text zu aktualisieren. Das bedeutet, dass TextView jedes Mal, wenn Sie neuen Text festlegen, ein neues Objekt erstellt.

Wenn Sie diesen Prozess besser steuern und die zusätzliche Erstellung von Objekten vermeiden möchten, können Sie Ihr eigenes Spannable.Factory implementieren und newSpannable() überschreiben. Anstatt ein neues Textobjekt zu erstellen, können Sie das vorhandene CharSequence als 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. In diesem Fall gibt newSpannable() eine ClassCastException aus.

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

Kotlin

textView.setSpannableFactory(spannableFactory)

Java

textView.setSpannableFactory(spannableFactory);

Legen Sie das Spannable.Factory-Objekt einmal fest, direkt nachdem Sie einen Verweis auf die TextView erhalten haben. Wenn Sie ein RecyclerView verwenden, legen Sie das Factory-Objekt fest, wenn Sie Ihre Ansichten zum ersten Mal aufblähen. Dadurch wird vermieden, dass zusätzliche Objekte erstellt werden, wenn RecyclerView ein neues Element an ViewHolder bindet.

Interne Span-Attribute ändern

Wenn Sie nur ein internes Attribut eines änderbaren Spans ändern müssen, z. B. die Farbe der Aufzählungszeichen in einem benutzerdefinierten Aufzählungspunkt, können Sie den Aufwand durch das mehrfache Aufrufen von setText() vermeiden, indem Sie einen Verweis auf die erstellte Span beibehalten. Wenn Sie den Span ändern müssen, können Sie den Verweis ändern und dann invalidate() oder requestLayout() für TextView aufrufen, je nachdem, welchen Attributtyp Sie geändert haben.

Im folgenden Codebeispiel hat eine benutzerdefinierte Implementierung von Aufzählungspunkten die Standardfarbe Rot, die sich in Grau ändert, wenn auf eine Schaltfläche getippt 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();
            }
        });
    }
}

Funktionen der Android KTX-Erweiterung verwenden

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