Gli span sono potenti oggetti di markup che puoi utilizzare per definire lo stile del testo a livello di carattere o paragrafo. Se colleghi gli span agli oggetti di testo, puoi modificare
il testo in vari modi, ad esempio aggiungendo colore, rendendolo cliccabile,
scalando le dimensioni del testo e disegnandolo in modo personalizzato. Gli intervalli possono anche
modificare le proprietà TextPaint
, disegnare su un
Canvas
e modificare il layout del testo.
Android fornisce diversi tipi di intervalli che coprono una serie di pattern di stile del testo comuni. Puoi anche creare i tuoi intervalli per applicare uno stile personalizzato.
Creare e applicare un intervallo
Per creare uno span, puoi utilizzare una delle classi elencate nella tabella seguente. Le classi differiscono a seconda che il testo stesso sia modificabile, che il markup del testo sia modificabile e di quale struttura di dati sottostante contenga i dati dello span.
Classe | Testo modificabile | Markup modificabile | Struttura dei dati |
---|---|---|---|
SpannedString |
No | No | Array lineare |
SpannableString |
No | Sì | Array lineare |
SpannableStringBuilder |
Sì | Sì | Albero degli intervalli |
Tutte e tre le classi estendono l'interfaccia Spanned
. SpannableString
e SpannableStringBuilder
estendono anche l'interfaccia
Spannable
.
Ecco come decidere quale utilizzare:
- Se non modifichi il testo o il markup dopo la creazione, utilizza
SpannedString
. - Se devi collegare un numero ridotto di intervalli a un singolo oggetto di testo e
il testo stesso è di sola lettura, utilizza
SpannableString
. - Se devi modificare il testo dopo la creazione e devi collegare gli intervalli al testo, utilizza
SpannableStringBuilder
. - Se devi collegare un numero elevato di intervalli a un oggetto di testo, indipendentemente
dal fatto che il testo stesso sia di sola lettura, utilizza
SpannableStringBuilder
.
Per applicare uno span, chiama setSpan(Object _what_, int _start_, int _end_, int
_flags_)
su un oggetto Spannable
. Il parametro what si riferisce allo span che stai
applicando al testo, mentre i parametri start e end indicano la porzione
di testo a cui stai applicando lo span.
Se inserisci del testo all'interno dei limiti di un intervallo, questo si espande automaticamente per
includere il testo inserito. Quando inserisci testo ai limiti
della sequenza, ovvero agli indici iniziale o finale, il parametro flags
determina se la sequenza si espande per includere il testo inserito. Utilizza
il
flag
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
per includere il testo inserito e
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
per escluderlo.
Il seguente esempio mostra come collegare un
ForegroundColorSpan
a una
stringa:
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
.
Poiché l'intervallo è impostato utilizzando Spannable.SPAN_EXCLUSIVE_INCLUSIVE
, l'intervallo
si espande per includere il testo inserito nei limiti dell'intervallo, come mostrato nell'esempio
seguente:
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
.
Puoi allegare più intervalli allo stesso testo. Il seguente esempio mostra come creare un testo in grassetto e rosso:
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)
e
StyleSpan(BOLD)
.
Tipi di span Android
Android fornisce oltre 20 tipi di span nel pacchetto android.text.style. Android classifica gli intervalli in due modi principali:
- In che modo lo span influisce sul testo: uno span può influire sull'aspetto del testo o sulle metriche del testo.
- Ambito degli intervalli: alcuni intervalli possono essere applicati a singoli caratteri, mentre altri devono essere applicati a un intero paragrafo.

Le sezioni seguenti descrivono queste categorie in modo più dettagliato.
Span che influiscono sull'aspetto del testo
Alcuni intervalli che vengono applicati a livello di carattere influiscono sull'aspetto del testo, ad esempio
la modifica del colore del testo o dello sfondo e l'aggiunta di sottolineature o barrature. Questi
span estendono la
classe CharacterStyle
.
Il seguente esempio di codice mostra come applicare un UnderlineSpan
per sottolineare
il testo:
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
.
Gli span che influiscono solo sull'aspetto del testo attivano un nuovo disegno del testo senza
attivare un ricalcolo del layout. Questi intervalli implementano
UpdateAppearance
ed estendono
CharacterStyle
.
Le sottoclassi CharacterStyle
definiscono come disegnare il testo fornendo l'accesso per aggiornare TextPaint
.
Span che influiscono sulle metriche di testo
Altri intervalli che si applicano a livello di carattere influiscono sulle metriche del testo, come l'altezza
della linea e le dimensioni del testo. Questi intervalli estendono la
classe MetricAffectingSpan
.
Il seguente esempio di codice crea un
RelativeSizeSpan
che
aumenta le dimensioni del testo del 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'applicazione di uno span che influisce sulle metriche del testo fa sì che un oggetto di osservazione misuri nuovamente il testo per un layout e un rendering corretti. Ad esempio, la modifica delle dimensioni del testo potrebbe far apparire le parole su righe diverse. L'applicazione dell'intervallo precedente attiva una nuova misurazione, il ricalcolo del layout del testo e il ridisegno del testo.
Gli span che influiscono sulle metriche di testo estendono la classe MetricAffectingSpan
, una
classe astratta che consente alle sottoclassi di definire in che modo lo span influisce sulla misurazione del testo
fornendo l'accesso a TextPaint
. Poiché MetricAffectingSpan
estende
CharacterStyle
, le sottoclassi influiscono sull'aspetto del testo a livello di carattere.
Span che influiscono sui paragrafi
Uno span può influire anche sul testo a livello di paragrafo, ad esempio modificando l'allineamento o il margine di un blocco di testo. Gli intervalli che interessano interi paragrafi
implementano ParagraphStyle
. Per
utilizzare questi intervalli, devi collegarli all'intero paragrafo, escluso il carattere di nuova riga
finale. Se provi ad applicare uno span di paragrafo a un elemento diverso da
un intero paragrafo, Android non applica lo span.
La Figura 8 mostra come Android separa i paragrafi nel testo.

\n
).
Il seguente esempio di codice applica un
QuoteSpan
a un paragrafo. Tieni presente che
se colleghi lo span a una posizione diversa dall'inizio o dalla fine di un
paragrafo, Android non applica lo stile.
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
applicato a un paragrafo.
Creare intervalli personalizzati
Se hai bisogno di più funzionalità di quelle fornite negli intervalli Android esistenti, puoi implementare un intervallo personalizzato. Quando implementi il tuo span, decidi se influisce sul testo a livello di carattere o paragrafo e se influisce sul layout o sull'aspetto del testo. In questo modo puoi determinare quali classi base puoi estendere e quali interfacce potresti dover implementare. Utilizza la seguente tabella come riferimento:
Scenario | Classe o interfaccia |
---|---|
L'intervallo influisce sul testo a livello di carattere. | CharacterStyle |
L'intervallo influisce sull'aspetto del testo. | UpdateAppearance |
L'intervallo influisce sulle metriche del testo. | UpdateLayout |
L'intervallo influisce sul testo a livello di paragrafo. | ParagraphStyle |
Ad esempio, se devi implementare uno span personalizzato che modifichi la dimensione e il colore del testo, estendi RelativeSizeSpan
. Tramite l'ereditarietà, RelativeSizeSpan
estende CharacterStyle
e implementa le due interfacce Update
. Poiché questa
classe fornisce già callback per updateDrawState
e updateMeasureState
,
puoi eseguire l'override di questi callback per implementare il tuo comportamento personalizzato. Il
seguente codice crea uno span personalizzato che si estende RelativeSizeSpan
e
override del callback updateDrawState
per impostare il colore di 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); } }
Questo esempio mostra come creare un intervallo personalizzato. Puoi ottenere lo stesso
effetto applicando RelativeSizeSpan
e ForegroundColorSpan
al testo.
Utilizzo dell'intervallo di test
L'interfaccia Spanned
ti consente sia di impostare gli intervalli sia di recuperarli dal
testo. Durante il test, implementa un test JUnit di Android per verificare che gli span corretti vengano aggiunti nelle posizioni corrette. L'app di esempio Text Styling
contiene un intervallo che applica il markup ai punti elenco allegando
BulletPointSpan
al testo. Il seguente esempio di codice mostra come verificare
se i punti elenco vengono visualizzati come previsto:
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)); }
Per altri esempi di test, consulta MarkdownBuilderTest su GitHub.
Testare gli intervalli personalizzati
Quando testi gli intervalli, verifica che TextPaint
contenga le modifiche previste e che gli elementi corretti vengano visualizzati in Canvas
. Ad esempio, considera un'implementazione di span personalizzata che antepone un punto elenco a
un testo. Il punto elenco ha dimensioni e colore specifici e c'è uno spazio
tra il margine sinistro dell'area disegnabile e il punto elenco.
Puoi testare il comportamento di questa classe implementando un test AndroidJUnit, controllando quanto segue:
- Se applichi correttamente lo span, sul canvas viene visualizzato un punto elenco delle dimensioni e del colore specificati e lo spazio corretto tra il margine sinistro e il punto elenco.
- Se non applichi lo span, non viene visualizzato alcun comportamento personalizzato.
Puoi vedere l'implementazione di questi test nell'esempio TextStyling su GitHub.
Puoi testare le interazioni con il canvas simulando il canvas, passando l'oggetto simulato al metodo drawLeadingMargin()
e verificando che vengano chiamati i metodi corretti con i parametri corretti.
Puoi trovare altri esempi di test di intervallo in BulletPointSpanTest.
Best practice per l'utilizzo degli span
Esistono diversi modi per impostare il testo in un TextView
in modo efficiente in termini di memoria, a seconda delle tue esigenze.
Allegare o staccare un intervallo senza modificare il testo sottostante
TextView.setText()
contiene più overload che gestiscono gli intervalli in modo diverso. Ad esempio, puoi
impostare un oggetto di testo Spannable
con il seguente codice:
Kotlin
textView.setText(spannableObject)
Java
textView.setText(spannableObject);
Quando viene chiamato questo overload di setText()
, TextView
crea una copia di
Spannable
come SpannedString
e la mantiene in memoria come CharSequence
.
Ciò significa che il testo e gli intervalli sono immutabili, quindi quando devi
aggiornare il testo o gli intervalli, crea un nuovo oggetto Spannable
e chiama
di nuovo setText()
, il che attiva anche una nuova misurazione e un nuovo disegno del
layout.
Per indicare che gli intervalli devono essere modificabili, puoi utilizzare
setText(CharSequence text, TextView.BufferType
type)
,
come mostrato nell'esempio seguente:
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 questo esempio, il parametro
BufferType.SPANNABLE
fa sì che TextView
crei un SpannableString
e l'oggetto
CharSequence
conservato da TextView
ora ha un markup modificabile e
un testo immutabile. Per aggiornare l'intervallo, recupera il testo come Spannable
e poi
aggiorna gli intervalli in base alle esigenze.
Quando colleghi, scolleghi o riposizioni gli intervalli, il TextView
viene aggiornato automaticamente per riflettere la modifica al testo. Se modifichi un attributo interno
di un intervallo esistente, chiama invalidate()
per apportare modifiche relative all'aspetto o
requestLayout()
per apportare modifiche relative alle metriche.
Impostare il testo in un TextView più volte
In alcuni casi, ad esempio quando utilizzi un
RecyclerView.ViewHolder
,
potresti voler riutilizzare un TextView
e impostare il testo più volte. Per
impostazione predefinita, indipendentemente dal fatto che tu imposti BufferType
, TextView
crea
una copia dell'oggetto CharSequence
e la mantiene in memoria. In questo modo, tutti gli aggiornamenti di TextView
sono intenzionali: non puoi aggiornare l'oggetto CharSequence
originale per aggiornare il testo. Ciò significa che ogni volta che imposti un nuovo
testo, TextView
crea un nuovo oggetto.
Se vuoi avere un maggiore controllo su questo processo ed evitare la creazione di oggetti aggiuntivi, puoi implementare il tuo Spannable.Factory
ed eseguire l'override di newSpannable()
.
Invece di creare un nuovo oggetto di testo, puoi eseguire il cast e restituire l'oggetto CharSequence
esistente come Spannable
, come mostrato nell'esempio seguente:
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; } };
Devi utilizzare textView.setText(spannableObject, BufferType.SPANNABLE)
quando
imposti il testo. In caso contrario, l'origine CharSequence
viene creata come istanza Spanned
e non può essere convertita in Spannable
, causando la generazione di un errore ClassCastException
da parte di newSpannable()
.
Dopo aver ignorato newSpannable()
, indica a TextView
di utilizzare il nuovo Factory
:
Kotlin
textView.setSpannableFactory(spannableFactory)
Java
textView.setSpannableFactory(spannableFactory);
Imposta l'oggetto Spannable.Factory
una sola volta, subito dopo aver ricevuto un riferimento al tuo
TextView
. Se utilizzi un RecyclerView
, imposta l'oggetto Factory
quando
gonfi per la prima volta le visualizzazioni. In questo modo, si evita la creazione di oggetti aggiuntivi quando
RecyclerView
associa un nuovo elemento a ViewHolder
.
Modificare gli attributi degli span interni
Se devi modificare solo un attributo interno di un intervallo modificabile, ad esempio il
colore del punto elenco in un intervallo di punti elenco personalizzato, puoi evitare il sovraccarico dovuto alla chiamata
di setText()
più volte mantenendo un riferimento all'intervallo durante la creazione.
Quando devi modificare l'intervallo, puoi modificare il riferimento e poi chiamare
invalidate()
o requestLayout()
su TextView
, a seconda del tipo di
attributo che hai modificato.
Nel seguente esempio di codice, un'implementazione di un punto elenco personalizzato ha un colore predefinito rosso che diventa grigio quando viene toccato un pulsante:
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(); } }); } }
Utilizzare le funzioni di estensione Android KTX
Android KTX contiene anche funzioni di estensione che semplificano l'utilizzo degli span. Per saperne di più, consulta la documentazione del pacchetto androidx.core.text.