Gli intervalli sono oggetti di markup potenti che puoi utilizzare per definire il testo a livello di carattere o di paragrafo. Collegando intervalli a oggetti di testo, puoi modificare
il testo in diversi modi, ad esempio aggiungendo colore, rendendo il testo cliccabile,
ridimensionando la dimensione del testo e disegnandolo in modo personalizzato. Gli intervalli possono anche modificare le proprietà di TextPaint
, disegnare su un Canvas
e modificare il layout del testo.
Android offre diversi tipi di intervalli che coprono una varietà di pattern di stili di testo comuni. Puoi anche creare intervalli personalizzati per applicare stili personalizzati.
Creare e applicare un intervallo
Per creare un intervallo, puoi utilizzare una delle classi elencate nella seguente tabella. Le classi variano a seconda del fatto che il testo stesso sia mutabile, del markup del testo e della struttura di dati sottostante che contiene i dati dell'intervallo.
Classe | Testo modificabile | Markup modificabile | Struttura dei dati |
---|---|---|---|
SpannedString |
No | No | Matrice lineare |
SpannableString |
No | Sì | Matrice lineare |
SpannableStringBuilder |
Sì | Sì | Albero degli intervalli |
Tutte e tre le classi estendono l'interfaccia Spanned
. SpannableString
e SpannableStringBuilder
estendono anche l'interfaccia
Spannable
.
Per 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 collegare intervalli al testo, utilizza
SpannableStringBuilder
. - Se devi collegare un numero elevato di intervalli a un oggetto di testo, indipendentemente dal fatto che il testo sia di sola lettura, utilizza
SpannableStringBuilder
.
Per applicare un intervallo, chiama setSpan(Object _what_, int _start_, int _end_, int
_flags_)
su un oggetto Spannable
. Il parametro what si riferisce all'estensione
alla quale stai applicando l'intervallo, mentre i parametri start ed end indicano la parte
del testo a cui stai applicando l'intervallo.
Se inserisci del testo all'interno dei confini di una sezione, questa si espande automaticamente per includere il testo inserito. Quando inserisci il testo ai confini dell'intervallo, ovvero agli indici start o end, il parametro flags determina se l'intervallo 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.
L'esempio seguente mostra come collegare un elemento 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 );
![Un'immagine che mostra un testo grigio, parzialmente rosso.](https://cdn.statically.io/img/developer.android.com/static/develop/ui/views/text-and-emoji/images/spans-fg-color.png?hl=it)
ForegroundColorSpan
.
Poiché l'intervallo viene impostato utilizzando Spannable.SPAN_EXCLUSIVE_INCLUSIVE
, l'intervallo si espande in modo da 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)");
![Un'immagine che mostra in che modo l'intervallo include più testo quando viene utilizzato SPAN_EXCLUSIVE_INCLUSIVE.](https://cdn.statically.io/img/developer.android.com/static/develop/ui/views/text-and-emoji/images/spans-fg-color-2.png?hl=it)
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
.
Puoi allegare più sezioni allo stesso testo. L'esempio seguente mostra come creare 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 );
![Un'immagine che mostra un testo con più sezioni: "ForegroundColorSpan(Color.RED)" e "StyleSpan(BOLD)"](https://cdn.statically.io/img/developer.android.com/static/develop/ui/views/text-and-emoji/images/spans-bold-red.png?hl=it)
ForegroundColorSpan(Color.RED)
e
StyleSpan(BOLD)
.
Tipi di intervallo Android
Android offre più di 20 tipi di intervalli nel pacchetto android.text.style. Android classifica gli intervalli in due modi principali:
- L'impatto dell'intervallo sul testo: un intervallo può influire sull'aspetto del testo o sulle metriche del testo.
- Ambito di intervallo: alcune sezioni possono essere applicate a singoli caratteri, mentre altre devono essere applicate a un intero paragrafo.
![Un'immagine che mostra diverse categorie di intervalli](https://cdn.statically.io/img/developer.android.com/static/develop/ui/views/text-and-emoji/images/spans-span-categories.png?hl=it)
Nelle sezioni seguenti vengono descritte queste categorie in modo più dettagliato.
Intervalli che influiscono sull'aspetto del testo
Alcune sezioni applicate a livello di carattere influiscono sull'aspetto del testo, ad esempio
modificando il colore del testo o dello sfondo e aggiungendo sottolineature o barrature. Questi intervalli estendono la classe CharacterStyle
.
Il codice di esempio seguente 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);
![Un'immagine che mostra come sottolineare il testo utilizzando un "underlineSpan"](https://cdn.statically.io/img/developer.android.com/static/develop/ui/views/text-and-emoji/images/spans-underlinespan.png?hl=it)
UnderlineSpan
.
Gli intervalli che influiscono solo sull'aspetto del testo attivano un ricalcolo 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
.
Intervalli che influiscono sulle metriche di testo
Altri intervalli che si applicano a livello di carattere influiscono sulle metriche del testo, come l'altezza della riga e le dimensioni del testo. Questi intervalli estendono la classe MetricAffectingSpan
.
Il seguente esempio di codice crea una 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);
![Un'immagine che mostra l'utilizzo di relativeSizeSpan](https://cdn.statically.io/img/developer.android.com/static/develop/ui/views/text-and-emoji/images/spans-relativesizespan.png?hl=it)
RelativeSizeSpan
.
L'applicazione di un intervallo che influisce sulle metriche del testo fa sì che un oggetto di osservazione misura nuovamente il testo per verificarne il layout e il rendering corretti. Ad esempio, se modifichi le dimensioni del testo, le parole potrebbero apparire su righe diverse. L'applicazione dell'intervallo precedente attiva una nuova misurazione, un ricalcolo del layout del testo e un nuovo disegno del testo.
Gli intervalli che influiscono sulle metriche del testo estendono la classe MetricAffectingSpan
, una
classe astratta che consente alle sottoclassi di definire in che modo l'intervallo influisce sulla misurazione del testo
fornendo l'accesso a TextPaint
. Poiché MetricAffectingSpan
estende CharacterSpan
, le sottoclassi influiscono sull'aspetto del testo a livello di carattere.
Intervalli che interessano i paragrafi
Una sezione 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 queste sezioni, le colleghi all'intero paragrafo, escludendo il carattere finale
della nuova riga. Se provi ad applicare un intervallo di paragrafo a qualcosa di diverso
da un paragrafo intero, Android non lo applica.
La Figura 8 mostra in che modo Android separa i paragrafi nel testo.
![](https://cdn.statically.io/img/developer.android.com/static/develop/ui/views/text-and-emoji/images/spans-paragraphs.png?hl=it)
\n
).
Il codice di esempio seguente applica QuoteSpan
a un paragrafo. Tieni presente che se alleghi l'intervallo a qualsiasi posizione diversa dall'inizio o dalla fine di un paragrafo, Android non applica alcuno 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);
![Un'immagine che mostra un esempio di QuoteSpan](https://cdn.statically.io/img/developer.android.com/static/develop/ui/views/text-and-emoji/images/spans-quotespan.png?hl=it)
QuoteSpan
applicato a un paragrafo.
Creazione di intervalli personalizzati
Se hai bisogno di più funzionalità rispetto a quelle fornite negli intervalli Android esistenti, puoi implementare un intervallo personalizzato. Quando implementi un intervallo personalizzato, decidi se quest'ultimo influisce sul testo a livello di carattere o di paragrafo, oltre che sul layout o sull'aspetto del testo. Questo ti aiuta a 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 un intervallo 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 comportamento personalizzato. Il seguente codice crea un intervallo personalizzato che estende RelativeSizeSpan
e sostituisce il 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 illustra come creare un intervallo personalizzato. Puoi ottenere lo stesso
effetto applicando RelativeSizeSpan
e ForegroundColorSpan
al testo.
Utilizzo intervallo di test
L'interfaccia Spanned
consente di impostare gli intervalli e di recuperarli dal testo. Durante il test, implementa un test JUnit
per Android per verificare che vengano aggiunti gli intervalli corretti
nelle posizioni corrette. L'app di esempio per gli stili di testo
contiene un intervallo che applica il markup agli elenchi puntati aggiungendo BulletPointSpan
al testo. L'esempio di codice riportato di seguito mostra come verificare
se gli elenchi puntati 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.
Test di intervalli personalizzati
Quando testi gli intervalli, verifica che TextPaint
contenga le modifiche previste
e che in Canvas
siano presenti gli elementi corretti. Ad esempio, prendi in considerazione un'implementazione di un intervallo personalizzato che anteponga un punto elenco a un testo. Il punto elenco ha una dimensione e un colore specifici e c'è uno spazio vuoto tra il margine sinistro dell'area disegnabile e il punto elenco.
Puoi verificare il comportamento di questa classe implementando un test AndroidJUnit, verificando quanto segue:
- Se applichi correttamente l'intervallo, sul canvas verrà visualizzato un punto elenco delle dimensioni e del colore specificati e verrà presente uno spazio appropriato tra il margine sinistro e il punto elenco.
- Se non applichi l'intervallo, non viene visualizzato alcun comportamento personalizzato.
Puoi vedere l'implementazione di questi test nel esempio di TextStyling su GitHub.
Puoi testare le interazioni con Canvas simulando il canvas, passando l'oggetto simulato al metodo drawLeadingMargin()
e verificando che i metodi corretti vengano chiamati con i parametri corretti.
Puoi trovare altri esempi di test di intervallo in BulletPointSpanTest.
Best practice per l'utilizzo degli intervalli
A seconda delle tue esigenze, esistono diversi modi efficienti di memoria per impostare il testo in un TextView
.
Collegare o scollegare un intervallo senza modificare il testo sottostante
TextView.setText()
contiene più sovraccarichi 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 chiami questo sovraccarico di setText()
, TextView
crea una copia di Spannable
come SpannedString
e la conserva 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 richiama
setText()
, il che attiva anche la rimisurazione e il ridisegno 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 markup modificabile e testo immutabile. Per aggiornare l'intervallo, recupera il testo come Spannable
e poi
aggiorna gli intervalli in base alle esigenze.
Quando alleghi, scolleghi o riposiziona le sezioni, TextView
si aggiorna 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.
Imposta il testo in una TextView più volte
In alcuni casi, ad esempio quando utilizzi una RecyclerView.ViewHolder
, potresti voler riutilizzare una TextView
e impostare il testo più volte. Per impostazione predefinita, indipendentemente dalla configurazione di BufferType
, TextView
crea una copia dell'oggetto CharSequence
e la conserva in memoria. Tutti gli aggiornamenti di TextView
sono intenzionali: non puoi aggiornare l'oggetto CharSequence
originale per aggiornare il testo. Questo significa che ogni volta che imposti
un nuovo testo, TextView
crea un nuovo oggetto.
Se vuoi avere maggiore controllo su questo processo ed evitare la creazione di oggetti
aggiuntivi, puoi implementare
Spannable.Factory
ed eseguire l'override di
newSpannable()
.
Invece di creare un nuovo oggetto di testo, puoi trasmettere e restituire l'elemento 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)
durante
l'impostazione del testo. In caso contrario, l'origine CharSequence
viene creata come istanza Spanned
e non può essere trasmessa su Spannable
, perciò newSpannable()
genera un
ClassCastException
.
Dopo aver eseguito l'override di newSpannable()
, chiedi a TextView
di utilizzare il nuovo Factory
:
Kotlin
textView.setSpannableFactory(spannableFactory)
Java
textView.setSpannableFactory(spannableFactory);
Imposta l'oggetto Spannable.Factory
una volta, subito dopo aver ottenuto un riferimento a
TextView
. Se utilizzi un RecyclerView
, imposta l'oggetto Factory
quando
prima aumenti il numero delle visualizzazioni. In questo modo si evita la creazione di oggetti aggiuntivi quando RecyclerView
associa un nuovo elemento a ViewHolder
.
Modifica attributi dell'intervallo interno
Se devi modificare solo un attributo interno di un intervallo modificabile, ad esempio
il colore di un punto elenco in un intervallo di punti elenco personalizzato, puoi evitare che l'overhead chiami
setText()
più volte, mantenendo un riferimento all'intervallo quando viene creato.
Quando devi modificare l'intervallo, puoi modificare il riferimento e chiamare
invalidate()
o requestLayout()
su TextView
, a seconda del tipo di
attributo modificato.
Nell'esempio di codice che segue, un'implementazione personalizzata di un elenco puntato 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 dell'estensione Android KTX
Android KTX contiene anche funzioni di estensione che semplificano l'utilizzo degli intervalli. Per ulteriori informazioni, consulta la documentazione del pacchetto androidx.core.text.