Les segments sont de puissants objets d'annotation que vous pouvez utiliser pour appliquer un style au texte au niveau
au niveau du caractère ou du paragraphe. En associant des objets Span à des objets texte, vous pouvez modifier
du texte de différentes manières, y compris
en ajoutant de la couleur, en rendant le texte cliquable,
la mise à l'échelle de la taille du texte et
le dessin du texte de manière personnalisée. Les segments peuvent également
modifier les propriétés de TextPaint
, dessiner sur
Canvas
, puis modifier la mise en page du texte.
Android propose plusieurs types de segments qui couvrent divers textes courants des modèles de style. Vous pouvez également créer vos propres segments pour appliquer un style personnalisé.
Créer et appliquer un segment
Pour créer un segment, vous pouvez utiliser l'une des classes répertoriées dans le tableau suivant. Les classes diffèrent selon que le texte lui-même est modifiable ou non, le balisage est modifiable et quelle structure de données sous-jacente contient les données de segment.
Classe | Texte modifiable | Balisage modifiable | Structure des données |
---|---|---|---|
SpannedString |
Non | Non | Tableau linéaire |
SpannableString |
Non | Oui | Tableau linéaire |
SpannableStringBuilder |
Oui | Oui | Arborescence à intervalles |
Les trois classes étendent Spanned
de commande. SpannableString
et SpannableStringBuilder
étendent également
Spannable
.
Voici comment faire votre choix:
- Si vous ne modifiez pas le texte ni le balisage après la création, utilisez
SpannedString
- Si vous devez associer un petit nombre de segments à un seul objet texte et que
le texte lui-même est en lecture seule, utilisez
SpannableString
. - Si vous devez modifier du texte après la création et que vous devez joindre des objets Span
le texte, utilisez
SpannableStringBuilder
. - Si vous devez associer un grand nombre de segments à un objet texte, quel que soit
pour savoir si le texte est en lecture seule, utilisez
SpannableStringBuilder
.
Pour appliquer un segment, appelez setSpan(Object _what_, int _start_, int _end_, int
_flags_)
sur un objet Spannable
. Le paramètre what fait référence au segment dont vous
au texte. Les paramètres start et end indiquent la partie
du texte auquel vous appliquez le segment.
Si vous insérez du texte à l'intérieur des limites d'un span, celui-ci se développe automatiquement au format
afin d'inclure le texte inséré. Lorsque vous insérez du texte à l'intervalle
des limites, c'est-à-dire aux index start ou end, les indicateurs
détermine si l'élément span doit se développer pour inclure le texte inséré. Utilisez
la
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
pour inclure le texte inséré et utilisez
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
pour exclure le texte inséré.
L'exemple suivant montre comment associer un
ForegroundColorSpan
en
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 );
Comme l'objet Span est défini à l'aide de Spannable.SPAN_EXCLUSIVE_INCLUSIVE
,
se développe pour inclure le texte inséré au-delà des limites de l'espace, comme indiqué dans les
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)");
Vous pouvez joindre plusieurs segments au même texte. L'exemple suivant montre comment pour 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 );
Types de segments Android
Android fournit plus de 20 types de segments dans le android.text.style. Android classe les segments de deux manières principales:
- Effet du segment: ce segment peut influer sur l'apparence du texte ou du texte métriques.
- Champ d'application des segments: certains segments peuvent être appliqués à des caractères individuels, tandis que d'autres doit s'appliquer à un paragraphe entier.
Les sections suivantes décrivent ces catégories plus en détail.
Segments qui affectent l'apparence du texte
Certains segments qui s'appliquent au niveau du caractère affectent l'apparence du texte. Par exemple :
modifier la couleur du texte ou de l'arrière-plan, et souligner ou barrer le texte. Ces
étendues étendent
CharacterStyle
.
L'exemple de code suivant montre comment appliquer un élément UnderlineSpan
pour souligner des caractères.
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);
Les segments qui affectent uniquement l'apparence du texte déclenchent une refonte du texte sans
ce qui déclenche un recalcul de la mise en page. Ces segments mettent en œuvre
UpdateAppearance
et étendre
CharacterStyle
Les sous-classes CharacterStyle
définissent comment dessiner du texte en donnant accès à
mettre à jour TextPaint
.
Segments ayant une incidence sur les métriques textuelles
D'autres segments qui s'appliquent au niveau des caractères ont une incidence sur les métriques textuelles, comme les lignes
la hauteur et la taille du texte. Ces segments étendent
MetricAffectingSpan
.
L'exemple de code suivant crée
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);
Si vous appliquez un segment qui affecte les métriques de texte, l'objet observé Mesurez à nouveau le texte pour obtenir une mise en page et un rendu corrects (par exemple, en modifiant la taille du texte peut entraîner l'apparition des mots sur des lignes différentes. En appliquant ce qui précède l'intervalle déclenche une nouvelle mesure, le recalcul de la mise en page du texte et le redessin le texte.
Les segments qui affectent les métriques de texte étendent la classe MetricAffectingSpan
, une
classe abstraite qui permet aux sous-classes de définir l'impact du segment sur la mesure du texte
en donnant accès à TextPaint
. Puisque MetricAffectingSpan
s'étend
CharacterSpan
, les sous-classes affectent l'apparence du texte au niveau du caractère
d'application.
Segments qui affectent les paragraphes
Un segment peut également affecter du texte au niveau du paragraphe, par exemple en modifiant la
l'alignement ou la marge
d'un bloc de texte. Segments qui affectent des paragraphes entiers
implémenter ParagraphStyle
À
utilisez ces segments, vous les associez à la totalité du paragraphe, à l'exception de la fin
de ligne. Si vous essayez d'appliquer un segment de paragraphe à autre chose
un paragraphe entier, Android n'applique pas du tout le segment.
La figure 8 montre comment Android sépare les paragraphes dans le texte.
L'exemple de code suivant applique
QuoteSpan
en paragraphe. Notez que
si vous attachez l'étendue à d'autres positions que le début ou la fin d'un
le style n'est pas appliqué par Android.
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);
Créer des segments personnalisés
Si vous avez besoin de plus de fonctionnalités que celles offertes par la version vous pouvez implémenter un segment personnalisé. Lorsque vous implémentez votre propre segment, si votre espace affecte le texte au niveau des caractères ou au niveau du paragraphe, et également s'il affecte la mise en page ou l'apparence du texte. Vous pourrez ainsi Déterminer les classes de base que vous pouvez étendre et les interfaces dont vous pourriez avoir besoin à mettre en œuvre. Utilisez le tableau suivant pour référence:
Scénario | Classe ou interface |
---|---|
Le segment affecte le texte au niveau des caractères. | CharacterStyle |
Le segment a une incidence sur l'apparence du texte. | UpdateAppearance |
Le segment a une incidence sur les métriques textuelles. | UpdateLayout |
L'étendue affecte le texte au niveau du paragraphe. | ParagraphStyle |
Par exemple, si vous devez implémenter un segment personnalisé qui modifie la taille du texte et
couleur, étendez RelativeSizeSpan
. Par héritage, RelativeSizeSpan
étend CharacterStyle
et implémente les deux interfaces Update
. Étant donné que
fournit déjà des rappels pour updateDrawState
et updateMeasureState
,
vous pouvez ignorer ces rappels pour implémenter votre comportement personnalisé. La
le code suivant crée un segment 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 segment personnalisé. Vous pouvez obtenir le même résultat
en appliquant RelativeSizeSpan
et ForegroundColorSpan
au texte.
Tester l'utilisation des segments
L'interface Spanned
vous permet à la fois de définir et de récupérer des objets Span
texte. Lors des tests, implémentez un Android JUnit.
Test pour vérifier que les bons segments ont été ajoutés
aux bons emplacements. L'exemple de style de texte
l'appli
contient un segment qui applique le balisage aux puces en joignant
BulletPointSpan
au texte. L'exemple de code suivant montre comment tester
si les puces apparaissent 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 plus d'exemples de tests, consultez MarkdownBuilderTest sur GitHub
Tester les segments personnalisés
Lorsque vous testez les segments, vérifiez que TextPaint
contient les valeurs attendues
modifications et que les éléments corrects apparaissent sur votre Canvas
. Pour
Prenons l'exemple d'une implémentation de segment personnalisé qui ajoute une puce au début
du texte. La puce a une taille et une couleur spécifiées, et il y a un écart
entre la marge gauche de la zone du drawable et la puce.
Vous pouvez tester le comportement de cette classe en implémentant un test AndroidJUnit. en vérifiant les éléments suivants:
- Si vous appliquez correctement l'étendue, une puce de la taille spécifiée et couleur apparaît sur la toile, et l'espace approprié existe entre la partie gauche la marge et la puce.
- Si vous n'appliquez pas le segment, aucun comportement personnalisé ne s'affiche.
Vous pouvez voir la mise en œuvre de ces tests dans la section TextStyling exemple sur GitHub.
Vous pouvez tester les interactions du canevas en vous moquant du canevas, en lui transmettant
à l'objet
drawLeadingMargin()
et en vérifiant que les méthodes appropriées sont appelées avec la bonne méthode
paramètres.
Vous trouverez d'autres exemples de tests de segments dans BulletPointSpanTest :
Bonnes pratiques d'utilisation des objets Span
Il existe plusieurs méthodes économes en mémoire pour définir du texte dans un élément TextView
, selon
en fonction de vos besoins.
Associer ou dissocier un segment sans modifier le texte sous-jacent
TextView.setText()
contient plusieurs surcharges qui gèrent les segments différemment. Par exemple, vous pouvez
Définissez 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 le conserve en mémoire en tant que CharSequence
.
Cela signifie que votre texte et les objets Span ne peuvent pas être modifiés. Ainsi, lorsque vous devez
modifiez le texte ou les segments, créez un objet Spannable
et appelez
setText()
, ce qui déclenche également une nouvelle mesure et un redessin de la
mise en page.
Pour indiquer que les segments doivent être modifiables, vous pouvez utiliser à la place
setText(CharSequence text, TextView.BufferType
type)
,
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,
BufferType.SPANNABLE
oblige TextView
à créer un SpannableString
, et le paramètre
L'objet CharSequence
conservé par TextView
comporte désormais un balisage modifiable et
texte immuable. Pour mettre à jour le segment, récupérez le texte en tant que Spannable
, puis
et mettre à jour les segments si nécessaire.
Lorsque vous associez, détachez ou repositionnez des segments, le TextView
est automatiquement
pour refléter les modifications apportées au texte. Si vous modifiez un attribut interne
d'un segment existant, 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 plusieurs fois dans un TextView
Dans certains cas, par exemple lors de l'utilisation d'un
RecyclerView.ViewHolder
,
vous pouvez réutiliser un TextView
et définir le texte plusieurs fois. Par
par défaut, que vous définissiez BufferType
ou non, TextView
crée
une copie de l'objet CharSequence
et la conserve en mémoire. Ainsi, toutes
TextView
mise à jour intentionnelle (vous ne pouvez pas mettre à jour la version d'origine)
CharSequence
pour mettre à jour le texte. Autrement dit, chaque fois que vous définissez
du texte, TextView
crée un objet.
Si vous souhaitez avoir plus de contrôle sur ce processus et éviter l'objet supplémentaire
vous pouvez implémenter vos propres modèles
Spannable.Factory
et ignorer
newSpannable()
Au lieu de créer un objet textuel, vous pouvez caster et renvoyer le texte existant
CharSequence
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
pour définir le texte. Sinon, la source CharSequence
est créée en tant que Spanned
.
et ne peuvent pas être castées en Spannable
. newSpannable()
génère alors une
ClassCastException
Après avoir remplacé newSpannable()
, demandez à 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 d'abord vos vues. Cela permet d'éviter la création d'objets supplémentaires
RecyclerView
associe un nouvel élément à votre ViewHolder
.
Modifier les attributs de segment internes
Si vous ne devez modifier qu'un attribut interne d'un segment modifiable, tel que le
dans une liste à puces personnalisée, ce qui vous évite d'avoir à appeler
setText()
plusieurs fois en conservant une référence au segment lors de sa création.
Lorsque vous devez modifier l'objet Span, vous pouvez modifier la référence, puis appeler
invalidate()
ou requestLayout()
sur le TextView
, selon le type de
que vous avez modifié.
Dans l'exemple de code suivant, une implémentation personnalisée avec puce couleur par défaut du rouge qui devient gris lorsque l'utilisateur appuie sur un bouton:
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 permettent d'utiliser des objets Span. plus facile. Pour en savoir plus, consultez la documentation androidx.core.text. d'un package.