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 );

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)");

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 );

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.

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);

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);

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.

\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);

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.