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

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

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

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.

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

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

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.

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

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.