Spans

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

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 Text auf verschiedene Arten ändern, z. B. Farbe hinzufügen, Text anklickbar machen, die Textgröße skalieren und Text auf benutzerdefinierte Weise zeichnen. Spans können auch TextPaint-Eigenschaften ändern, auf ein Canvas verweisen und das Textlayout ändern.

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

Spanne erstellen und anwenden

Zum Erstellen eines Zeitraums können Sie eine der in der folgenden Tabelle aufgeführten Klassen verwenden. Die Klassen unterscheiden sich danach, ob der Text selbst oder die Textformatierung geändert werden kann und welche zugrunde liegende Datenstruktur die Spannen-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 Spanned-Schnittstelle. SpannableString und SpannableStringBuilder erweitern auch die Spannable-Schnittstelle.

So entscheiden Sie, welche Option Sie verwenden sollten:

  • 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 der Erstellung ändern und Spans an den Text anhängen müssen, verwenden Sie SpannableStringBuilder.
  • Wenn Sie einem Textobjekt eine große Anzahl von Spannen anhängen müssen, unabhängig davon, ob der Text selbst schreibgeschützt ist, verwenden Sie SpannableStringBuilder.

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

Wenn Sie Text innerhalb der Grenzen eines Spans einfügen, wird der Span automatisch erweitert, um den eingefügten Text einzuschließen. Wenn Sie Text an den Spannenbegrenzungen einfügen, d. h. an den Start- oder Ende-Indizes, wird durch den Parameter flags festgelegt, ob die Spanne erweitert wird, um den eingefügten Text einzuschließen. Verwenden Sie das Flag Spannable.SPAN_EXCLUSIVE_INCLUSIVE, um eingefügten Text einzuschließen, und Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, um ihn auszuschließen.

Das folgende Beispiel zeigt, wie Sie eine ForegroundColorSpan an einen String anhängen:

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, der teilweise rot ist.
Abbildung 1. Text, der mit einem ForegroundColorSpan formatiert wurde.

Da der Bereich mit Spannable.SPAN_EXCLUSIVE_INCLUSIVE festgelegt wird, wird er erweitert, um eingefügten Text an den Bereichsgrenzen einzuschließen, 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 Bereich mehr Text umfasst, wenn SPAN_EXCLUSIVE_INCLUSIVE verwendet wird.
Abbildung 2. Der Bereich wird erweitert, um zusätzlichen Text einzuschließen, wenn Spannable.SPAN_EXCLUSIVE_INCLUSIVE verwendet wird.

Sie können demselben Text mehrere Spannen zuweisen. Im folgenden Beispiel wird gezeigt, wie Sie fett und rot formatierten 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 einem Text mit mehreren Spans: `ForegroundColorSpan(Color.RED)` und `StyleSpan(BOLD)`
Abbildung 3. Text mit mehreren Spannen: ForegroundColorSpan(Color.RED) und StyleSpan(BOLD).

Android-Spantypen

Android bietet über 20 Span-Typen im Paket android.text.style. Android kategorisiert Zeiträume auf zwei primäre Arten:

  • Wie sich der Bereich auf Text auswirkt: Ein Bereich kann sich auf die Darstellung oder die Messwerte von Text auswirken.
  • Bereich: Einige Spans können auf einzelne Zeichen angewendet werden, andere müssen auf einen ganzen Absatz angewendet werden.
Bild mit verschiedenen Zeitspannenkategorien
Abbildung 4. Kategorien von Android-Spans.

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

Spans, die sich auf die Darstellung von Text auswirken

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

Das folgende Codebeispiel zeigt, wie Sie ein UnderlineSpan anwenden, um den Text zu 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, das zeigt, wie Text mit einem „UnderlineSpan“ unterstrichen wird
Abbildung 5: Text, der mit einem UnderlineSpan unterstrichen wurde.

Spans, die sich nur auf die Darstellung von Text auswirken, lösen ein erneutes Rendern des Texts aus, ohne dass das Layout neu berechnet wird. Diese Spannen implementieren UpdateAppearance und erweitern CharacterStyle. CharacterStyle-Unterklassen definieren, wie Text gezeichnet wird, indem sie Zugriff zum Aktualisieren von TextPaint bieten.

Spannen, die sich auf Textmesswerte auswirken

Andere Spannen, 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, das 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: Text, der mit einem RelativeSizeSpan vergrößert wurde.

Wenn Sie einen Bereich anwenden, der sich auf Textmesswerte auswirkt, muss das beobachtende Objekt den Text neu messen, um das Layout und Rendering zu korrigieren. Wenn Sie beispielsweise die Textgröße ändern, werden Wörter möglicherweise in anderen Zeilen angezeigt. Durch Anwenden des vorherigen Spans wird eine erneute Messung, Neuberechnung des Textlayouts und erneutes Rendern des Texts ausgelöst.

Spans, die sich auf Textmesswerte auswirken, erweitern die Klasse MetricAffectingSpan. Das ist eine abstrakte Klasse, mit der Unterklassen definieren können, wie sich der Span auf die Textmessung auswirkt. Dazu wird Zugriff auf TextPaint gewährt. Da MetricAffectingSpan von CharacterStyle abgeleitet wird, wirken sich Unterklassen auf die Darstellung des Texts auf Zeichenebene aus.

Spannen, die sich auf Absätze auswirken

Ein Bereich kann sich auch auf Text auf Absatzebene auswirken, z. B. die Ausrichtung oder den Rand eines Textblocks ändern. Spans, die sich auf ganze Absätze auswirken, implementieren ParagraphStyle. Um diese Spans zu verwenden, hängen Sie sie an den gesamten Absatz an, mit Ausnahme des abschließenden Zeilenumbruchzeichens. Wenn Sie versuchen, einen Absatz-Span auf etwas anderes als einen ganzen Absatz anzuwenden, wird der Span von Android überhaupt nicht angewendet.

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 den Bereich an einer anderen Position als am Anfang oder Ende eines Absatzes anfügen, 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 wurde.

Benutzerdefinierte Spans erstellen

Wenn Sie mehr Funktionen benötigen, als in den vorhandenen Android-Spans bereitgestellt werden, können Sie eine benutzerdefinierte Span-Implementierung erstellen. Wenn Sie einen eigenen Span implementieren, müssen Sie entscheiden, ob er sich auf Text auf Zeichen- oder auf Absatzebene auswirkt und ob er das Layout oder die Darstellung des Texts beeinflusst. So können Sie leichter ermitteln, welche Basisklassen Sie erweitern können und welche Schnittstellen Sie möglicherweise implementieren müssen. Verwenden Sie die folgende Tabelle als Referenz:

Szenario Klasse oder Schnittstelle
Der Bereich wirkt sich auf Text auf Zeichenebene aus. CharacterStyle
Der Bereich wirkt sich auf die Darstellung des Texts aus. UpdateAppearance
Der Zeitraum wirkt sich auf Textmesswerte aus. UpdateLayout
Der Bereich wirkt sich auf Text auf Absatzebene aus. ParagraphStyle

Wenn Sie beispielsweise einen benutzerdefinierten Span implementieren müssen, der die Textgröße und -farbe ändert, erweitern Sie RelativeSizeSpan. Durch die Vererbung erweitert RelativeSizeSpan CharacterStyle und implementiert die beiden Update-Schnittstellen. Da diese Klasse bereits Callbacks für updateDrawState und updateMeasureState bietet, können Sie diese Callbacks überschreiben, um Ihr benutzerdefiniertes Verhalten zu implementieren. Mit dem folgenden Code wird ein benutzerdefinierter Bereich erstellt, der RelativeSizeSpan erweitert und den updateDrawState-Callback ü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 veranschaulicht, wie Sie einen benutzerdefinierten Zeitraum erstellen. Sie können denselben Effekt erzielen, indem Sie dem Text ein RelativeSizeSpan und ein ForegroundColorSpan zuweisen.

Testzeitraum

Mit der Spanned-Schnittstelle können Sie sowohl Spannen festlegen als auch Spannen 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 Span, mit dem Markup auf Aufzählungszeichen angewendet wird, indem dem Text BulletPointSpan angehängt wird. Das folgende Codebeispiel zeigt, wie Sie testen können, ob die Aufzählungszeichen 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 Zeiträume testen

Prüfen Sie beim Testen von Spannen, ob TextPaint die erwarteten Änderungen enthält und ob die richtigen Elemente auf Ihrer Canvas angezeigt werden. Stellen Sie sich beispielsweise eine benutzerdefinierte Spannenimplementierung vor, die einem Text einen Aufzählungszeichen voranstellt. Das Aufzählungszeichen hat eine bestimmte Größe und Farbe und es gibt einen Abstand zwischen dem linken Rand des Zeichenbereichs und dem Aufzählungszeichen.

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

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

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

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

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

Best Practices für die Verwendung von Spans

Je nach Bedarf gibt es mehrere speichereffiziente Möglichkeiten, Text in einem TextView festzulegen.

Spanne anfügen oder trennen, ohne den zugrunde liegenden Text zu ändern

TextView.setText() enthält mehrere Überladungen, die Spannen unterschiedlich verarbeiten. 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 von Spannable als SpannedString und speichert sie als CharSequence im Arbeitsspeicher. Das bedeutet, dass Ihr Text und die Spannen unveränderlich sind. Wenn Sie den Text oder die Spannen aktualisieren müssen, erstellen Sie ein neues Spannable-Objekt und rufen Sie setText() noch einmal auf. Dadurch wird auch das Layout neu gemessen und neu gezeichnet.

Um anzugeben, dass die Spannen 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 bewirkt der Parameter BufferType.SPANNABLE, dass TextView ein SpannableString erstellt. Das von TextView beibehaltene CharSequence-Objekt hat jetzt veränderliches Markup und unveränderlichen Text. Um den Bereich zu aktualisieren, rufen Sie den Text als Spannable ab und aktualisieren Sie die Bereiche nach Bedarf.

Wenn Sie Spannen anfügen, trennen oder neu positionieren, wird die TextView automatisch aktualisiert, um die Änderung am Text widerzuspiegeln. Wenn Sie ein internes Attribut eines vorhandenen Spans ändern, rufen Sie invalidate() auf, um Änderungen am Erscheinungsbild vorzunehmen, oder requestLayout(), um Änderungen an Messwerten vorzunehmen.

Text in einem TextView-Element mehrmals festlegen

In einigen Fällen, z. B. bei Verwendung eines RecyclerView.ViewHolder, möchten Sie möglicherweise ein 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 sie im Arbeitsspeicher. Dadurch sind alle TextView-Aktualisierungen beabsichtigt. Sie können das ursprüngliche CharSequence-Objekt nicht aktualisieren, um den Text zu aktualisieren. Das bedeutet, dass bei jedem Festlegen von neuem Text mit TextView ein neues Objekt erstellt wird.

Wenn Sie mehr Kontrolle über diesen Prozess haben 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, was dazu führt, dass newSpannable() eine ClassCastException auslöst.

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 Ihr TextView erhalten haben. Wenn Sie ein RecyclerView verwenden, legen Sie das Factory-Objekt fest, wenn Sie die Aufrufe zum ersten Mal erhöhen. So wird vermieden, dass zusätzliche Objekte erstellt werden, wenn durch RecyclerView ein neues Element an ViewHolder gebunden wird.

Interne Span-Attribute ändern

Wenn Sie nur ein internes Attribut eines veränderlichen Spans ändern müssen, z. B. die Aufzählungszeichenfarbe in einem benutzerdefinierten Aufzählungszeichen-Span, können Sie den Aufwand durch mehrere Aufrufe von setText() vermeiden, indem Sie eine Referenz auf den Span beibehalten, während er erstellt wird. Wenn Sie den Bereich ändern müssen, können Sie die Referenz ändern und dann invalidate() oder requestLayout() für das TextView aufrufen, je nachdem, welchen Attributtyp Sie geändert haben.

Im folgenden Codebeispiel hat eine benutzerdefinierte Aufzählungszeichen-Implementierung 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();
            }
        });
    }
}

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.