Les éléments Span sont des objets de balisage puissants que vous pouvez utiliser pour styliser le texte au niveau des caractères ou des paragraphes. En associant des étendues à des objets texte, vous pouvez modifier le texte de différentes manières, par exemple en ajoutant de la couleur, en rendant le texte cliquable, en ajustant la taille du texte et en dessinant le texte de manière personnalisée. Les étendues peuvent également modifier les propriétés TextPaint
, dessiner sur un Canvas
et modifier la mise en page du texte.
Android propose plusieurs types de portées qui couvrent une variété de styles de texte courants. Vous pouvez également créer vos propres balises pour appliquer un style personnalisé.
Créer et appliquer un délai
Pour créer un segment, vous pouvez utiliser l'une des classes listées dans le tableau suivant. Les classes diffèrent selon que le texte lui-même est modifiable, que la balise de texte est modifiable et quelle structure de données sous-jacente contient les données de la plage.
Classe | Texte modifiable | Balisage modifiable | Structure des données |
---|---|---|---|
SpannedString |
Non | Non | Tableau linéaire |
SpannableString |
Non | Oui | Tableau linéaire |
SpannableStringBuilder |
Oui | Oui | Arbre d'intervalles |
Les trois classes étendent l'interface Spanned
. SpannableString
et SpannableStringBuilder
étendent également l'interface Spannable
.
Voici comment choisir laquelle utiliser:
- Si vous ne modifiez pas le texte ni le balisage après la création, utilisez
SpannedString
. - Si vous devez joindre un petit nombre de portées à un seul objet texte et que le texte lui-même est en lecture seule, utilisez
SpannableString
. - Si vous devez modifier le texte après sa création et que vous devez associer des plages au texte, utilisez
SpannableStringBuilder
. - Si vous devez joindre un grand nombre de plages à un objet texte, que le texte lui-même soit en lecture seule ou non, utilisez
SpannableStringBuilder
.
Pour appliquer un délai, appelez setSpan(Object _what_, int _start_, int _end_, int
_flags_)
sur un objet Spannable
. Le paramètre what fait référence à la plage que vous appliquez au texte, et les paramètres start et end indiquent la partie du texte à laquelle vous appliquez la plage.
Si vous insérez du texte dans les limites d'une étendue, celle-ci se développe automatiquement pour inclure le texte inséré. Lorsque vous insérez du texte à la limite de la plage (c'est-à-dire aux indices start ou end), le paramètre flags détermine si la plage s'étend pour inclure le texte inséré. Utilisez l'option Spannable.SPAN_EXCLUSIVE_INCLUSIVE
pour inclure le texte inséré et Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
pour l'exclure.
L'exemple suivant montre comment associer un ForegroundColorSpan
à une chaîne:
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
.
Étant donné que la plage est définie à l'aide de Spannable.SPAN_EXCLUSIVE_INCLUSIVE
, elle s'étend pour inclure le texte inséré aux limites de la plage, comme illustré dans l'exemple suivant:
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
.
Vous pouvez associer plusieurs intervalles au même texte. L'exemple suivant montre comment créer du texte en gras et en rouge:
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)
et
StyleSpan(BOLD)
.
Types de span Android
Android propose plus de 20 types de span dans le package android.text.style. Android catégorise les plages de deux manières principales:
- Impact de la span sur le texte: une span peut affecter l'apparence ou les métriques du texte.
- Portée de la portée: certaines portées peuvent être appliquées à des caractères individuels, tandis que d'autres doivent être appliquées à un paragraphe entier.

Les sections suivantes décrivent ces catégories plus en détail.
Espaces qui affectent l'apparence du texte
Certaines portées qui s'appliquent au niveau des caractères affectent l'apparence du texte, par exemple en modifiant la couleur du texte ou de l'arrière-plan, et en ajoutant des soulignements ou des barres de soulignement. Ces plages étendent la classe CharacterStyle
.
L'exemple de code suivant montre comment appliquer un UnderlineSpan
pour souligner le texte:
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
.
Les étendues qui n'affectent que l'apparence du texte déclenchent un réaffichage du texte sans déclencher un nouveau calcul de la mise en page. Ces plages implémentent UpdateAppearance
et étendent CharacterStyle
.
Les sous-classes CharacterStyle
définissent comment dessiner du texte en fournissant un accès pour mettre à jour le TextPaint
.
Intervalles ayant une incidence sur les métriques de texte
Les autres portées qui s'appliquent au niveau des caractères affectent les métriques de texte, telles que la hauteur de ligne et la taille du texte. Ces plages étendent la classe MetricAffectingSpan
.
L'exemple de code suivant crée un RelativeSizeSpan
qui augmente la taille du texte de 50%:
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
.
L'application d'une étendue qui affecte les métriques de texte entraîne la remise à mesure du texte par un objet d'observation pour une mise en page et un rendu corrects. Par exemple, la modification de la taille du texte peut entraîner l'affichage de mots sur différentes lignes. L'application de la plage précédente déclenche une nouvelle mesure, un nouveau calcul de la mise en page du texte et un nouveau dessin du texte.
Les étendues qui affectent les métriques de texte étendent la classe MetricAffectingSpan
, une classe abstraite qui permet aux sous-classes de définir l'impact de l'étendue sur la mesure du texte en fournissant un accès à TextPaint
. Étant donné que MetricAffectingSpan
étend CharacterStyle
, les sous-classes affectent l'apparence du texte au niveau des caractères.
Intervalles affectant les paragraphes
Une étendue peut également affecter le texte au niveau du paragraphe, par exemple en modifiant l'alignement ou la marge d'un bloc de texte. Les plages qui affectent des paragraphes entiers implémentent ParagraphStyle
. Pour utiliser ces plages, vous devez les associer à l'ensemble du paragraphe, à l'exception du caractère de nouvelle ligne de fin. Si vous essayez d'appliquer une plage de paragraphes à autre chose qu'un paragraphe entier, Android n'applique pas du tout la plage.
La figure 8 montre comment Android sépare les paragraphes dans le texte.

\n
).
L'exemple de code suivant applique un QuoteSpan
à un paragraphe. Notez que si vous associez la span à une position autre que le début ou la fin d'un paragraphe, Android n'applique pas du tout le style.
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
appliqué à un paragraphe.
Créer des délais personnalisés
Si vous avez besoin de plus de fonctionnalités que celles fournies dans les plages Android existantes, vous pouvez implémenter une plage personnalisée. Lorsque vous implémentez votre propre étendue, décidez si elle affecte le texte au niveau des caractères ou des paragraphes, et si elle affecte la mise en page ou l'apparence du texte. Cela vous aide à déterminer les classes de base que vous pouvez étendre et les interfaces que vous devrez peut-être implémenter. Consultez le tableau suivant pour référence:
Scénario | Classe ou interface |
---|---|
Votre étendue affecte le texte au niveau des caractères. | CharacterStyle |
Votre span affecte l'apparence du texte. | UpdateAppearance |
Votre étendue affecte les métriques de texte. | UpdateLayout |
Votre étendue affecte le texte au niveau du paragraphe. | ParagraphStyle |
Par exemple, si vous devez implémenter une étendue personnalisée qui modifie la taille et la couleur du texte, étendez RelativeSizeSpan
. Par héritage, RelativeSizeSpan
étend CharacterStyle
et implémente les deux interfaces Update
. Étant donné que cette classe fournit déjà des rappels pour updateDrawState
et updateMeasureState
, vous pouvez remplacer ces rappels pour implémenter votre comportement personnalisé. Le code suivant crée un délai personnalisé qui étend RelativeSizeSpan
et remplace le rappel updateDrawState
pour définir la couleur de TextPaint
:
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); } }
Cet exemple montre comment créer un délai personnalisé. Vous pouvez obtenir le même effet en appliquant un RelativeSizeSpan
et un ForegroundColorSpan
au texte.
Tester l'utilisation des segments
L'interface Spanned
vous permet à la fois de définir des plages et de les récupérer à partir de texte. Lors des tests, implémentez un test Android JUnit pour vérifier que les plages appropriées sont ajoutées aux emplacements appropriés. L'application exemple de mise en forme du texte contient une étendue qui applique un balisage aux puces en associant BulletPointSpan
au texte. L'exemple de code suivant montre comment vérifier si les puces s'affichent comme prévu:
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)); }
Pour obtenir d'autres exemples de test, consultez MarkdownBuilderTest sur GitHub.
Tester des plages personnalisées
Lorsque vous testez des plages, vérifiez que le TextPaint
contient les modifications attendues et que les éléments appropriés apparaissent sur votre Canvas
. Prenons l'exemple d'une implémentation de span personnalisée qui ajoute une puce au début d'un texte. Le point de liste a une taille et une couleur spécifiées, et il existe un espace entre la marge de gauche de la zone drawable et le point de liste.
Vous pouvez tester le comportement de cette classe en implémentant un test AndroidJUnit, en vérifiant les points suivants:
- Si vous appliquez correctement la balise span, un point de liste de la taille et de la couleur spécifiées apparaît sur le canevas, et l'espace approprié existe entre la marge de gauche et le point de liste.
- Si vous n'appliquez pas la plage, aucun comportement personnalisé ne s'affiche.
Vous pouvez voir l'implémentation de ces tests dans l'exemple TextStyling sur GitHub.
Vous pouvez tester les interactions du canevas en simulant le canevas, en transmettant l'objet simulé à la méthode drawLeadingMargin()
et en vérifiant que les méthodes appropriées sont appelées avec les paramètres appropriés.
Vous trouverez d'autres exemples de tests de span dans BulletPointSpanTest.
Bonnes pratiques pour l'utilisation des étendues
Il existe plusieurs façons d'utiliser efficacement la mémoire pour définir du texte dans un TextView
, en fonction de vos besoins.
Associer ou dissocier une étendue sans modifier le texte sous-jacent
TextView.setText()
contient plusieurs surcharges qui gèrent les plages différemment. Par exemple, vous pouvez définir un objet texte Spannable
avec le code suivant:
Kotlin
textView.setText(spannableObject)
Java
textView.setText(spannableObject);
Lorsque vous appelez cette surcharge de setText()
, TextView
crée une copie de votre Spannable
en tant que SpannedString
et la conserve en mémoire en tant que CharSequence
.
Cela signifie que votre texte et les portées sont immuables. Par conséquent, lorsque vous devez mettre à jour le texte ou les portées, créez un nouvel objet Spannable
et appelez à nouveau setText()
, ce qui déclenche également une nouvelle mesure et un nouveau dessin de la mise en page.
Pour indiquer que les plages doivent être modifiables, vous pouvez utiliser setText(CharSequence text, TextView.BufferType
type)
à la place, comme illustré dans l'exemple suivant:
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);
Dans cet exemple, le paramètre BufferType.SPANNABLE
entraîne la création d'un SpannableString
par TextView
, et l'objet CharSequence
conservé par TextView
comporte désormais une balise modifiable et un texte immuable. Pour mettre à jour la plage, récupérez le texte en tant que Spannable
, puis mettez à jour les plages si nécessaire.
Lorsque vous associez, dissociez ou repositionnez des plages, TextView
est automatiquement mis à jour pour refléter la modification apportée au texte. Si vous modifiez un attribut interne d'une étendue existante, appelez invalidate()
pour apporter des modifications liées à l'apparence ou requestLayout()
pour apporter des modifications liées aux métriques.
Définir du texte dans un TextView plusieurs fois
Dans certains cas, par exemple lorsque vous utilisez un RecyclerView.ViewHolder
, vous pouvez réutiliser un TextView
et définir le texte plusieurs fois. Par défaut, que vous définissiez ou non BufferType
, TextView
crée une copie de l'objet CharSequence
et la conserve en mémoire. Toutes les mises à jour de TextView
sont donc intentionnelles. Vous ne pouvez pas modifier l'objet CharSequence
d'origine pour modifier le texte. Cela signifie que chaque fois que vous définissez un nouveau texte, TextView
crée un nouvel objet.
Si vous souhaitez mieux contrôler ce processus et éviter la création d'objets supplémentaires, vous pouvez implémenter votre propre Spannable.Factory
et remplacer newSpannable()
.
Au lieu de créer un objet texte, vous pouvez caster et renvoyer l'CharSequence
existante en tant que Spannable
, comme illustré dans l'exemple suivant:
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; } };
Vous devez utiliser textView.setText(spannableObject, BufferType.SPANNABLE)
lorsque vous définissez le texte. Sinon, l'CharSequence
source est créée en tant qu'instance Spanned
et ne peut pas être convertie en Spannable
, ce qui entraîne l'émission d'une exception ClassCastException
par newSpannable()
.
Après avoir remplacé newSpannable()
, indiquez à TextView
d'utiliser le nouveau Factory
:
Kotlin
textView.setSpannableFactory(spannableFactory)
Java
textView.setSpannableFactory(spannableFactory);
Définissez l'objet Spannable.Factory
une seule fois, juste après avoir obtenu une référence à votre TextView
. Si vous utilisez un RecyclerView
, définissez l'objet Factory
lorsque vous gonflez vos vues pour la première fois. Cela évite la création d'objets supplémentaires lorsque votre RecyclerView
lie un nouvel élément à votre ViewHolder
.
Modifier les attributs de délai internes
Si vous ne devez modifier qu'un attribut interne d'une étendue modifiable, comme la couleur des puces dans une étendue de puces personnalisée, vous pouvez éviter les frais généraux liés à l'appel de setText()
plusieurs fois en conservant une référence à l'étendue au moment de sa création.
Lorsque vous devez modifier la plage, vous pouvez modifier la référence, puis appeler invalidate()
ou requestLayout()
sur TextView
, en fonction du type d'attribut que vous avez modifié.
Dans l'exemple de code suivant, une implémentation de puces personnalisées a une couleur rouge par défaut qui passe au gris lorsqu'un bouton est enfoncé:
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(); } }); } }
Utiliser les fonctions d'extension Android KTX
Android KTX contient également des fonctions d'extension qui facilitent l'utilisation des objets Span. Pour en savoir plus, consultez la documentation du package androidx.core.text.