Gli intervalli sono potenti oggetti di markup che puoi utilizzare per definire lo stile del testo
a livello di carattere o di paragrafo. Collegare sezioni a oggetti di testo, per modificare
il testo in vari modi, ad esempio aggiungendo colore, rendendo il testo cliccabile,
scalare la dimensione del testo e disegnarlo in modo personalizzato. Gli intervalli possono anche
modifica le proprietà di TextPaint
, disegna su un
Canvas
e modifica il layout del testo.
Android offre diversi tipi di intervalli che coprono una varietà di testi comuni modelli di stile. 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 che il testo sia mutabile o meno è modificabile e quale struttura di dati sottostante contiene i dati degli intervalli.
Classe | Testo modificabile | Markup modificabile | Struttura dei dati |
---|---|---|---|
SpannedString |
No | No | Matrice lineare |
SpannableString |
No | Sì | Matrice lineare |
SpannableStringBuilder |
Sì | Sì | Albero degli intervalli |
Tutti e tre i corsi estendono la Spanned
a riga di comando. SpannableString
e SpannableStringBuilder
estendono anche
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 sezioni a un singolo oggetto di testo e
il testo è di sola lettura, usa
SpannableString
. - Se devi modificare il testo dopo la creazione e devi allegare intervalli a
il testo, usa
SpannableStringBuilder
. - Se è necessario collegare un numero elevato di intervalli a un oggetto di testo,
per stabilire se il testo è di sola lettura, usa
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 che stai
applicabile al testo, mentre i parametri start e end indicano la parte
del testo a cui applichi l'intervallo.
Se inserisci del testo all'interno dei confini di una sezione, questa si espande automaticamente in
il testo inserito. Quando inserisci il testo nello intervallo
confini, ovvero in corrispondenza degli indici start o end, i flag
determina se l'intervallo si espande per includere il testo inserito. Utilizza le funzionalità di
il
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
per includere il testo inserito e utilizzare
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
per escludere il testo inserito.
L'esempio seguente mostra come collegare un
ForegroundColorSpan
a un
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 );
Poiché l'intervallo viene impostato utilizzando Spannable.SPAN_EXCLUSIVE_INCLUSIVE
, l'intervallo
si espande in modo da includere il testo inserito ai confini dell'intervallo, come mostrato in
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)");
Puoi allegare più sezioni allo stesso testo. L'esempio seguente mostra come per 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 );
Tipi di intervallo Android
Android offre oltre 20 tipi di intervalli android.text.style. Android classifica gli intervalli in due modi principali:
- In che modo l'intervallo influisce sul testo: un intervallo può influire sull'aspetto del testo o sul testo metriche di valutazione.
- Ambito di intervallo: alcune sezioni possono essere applicate a singoli caratteri, mentre altre deve essere applicata a un intero paragrafo.
Nelle sezioni seguenti vengono descritte queste categorie in modo più dettagliato.
Intervalli che influiscono sull'aspetto del testo
Alcuni intervalli che si applicano a livello di carattere influiscono sull'aspetto del testo, ad esempio:
cambiare il colore del testo o dello sfondo e aggiungere sottolineature o barrature. Questi
estendono
CharacterStyle
.
L'esempio di codice 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);
Gli intervalli che influiscono solo sull'aspetto del testo attivano un nuovo disegno del testo senza
attivando un ricalcolo del layout. Questi intervalli implementano
UpdateAppearance
ed estendi
CharacterStyle
.
Le sottoclassi CharacterStyle
definiscono come disegnare il testo fornendo accesso a
aggiorna TextPaint
.
Intervalli che influiscono sulle metriche di testo
Altri intervalli che si applicano a livello di carattere influiscono sulle metriche del testo, come le righe
l'altezza e le dimensioni del testo. Questi intervalli estendono
MetricAffectingSpan
.
Il codice di esempio riportato di seguito crea un
RelativeSizeSpan
che
aumenta la dimensione 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);
L'applicazione di un intervallo che influisce sulle metriche di testo fa sì che un oggetto "Osservazione" misurare nuovamente il testo per verificare che il layout e il rendering siano corretti, ad esempio modificando le dimensioni del testo potrebbero far apparire le parole su righe diverse. Applicazione della versione precedente attiva una nuova misurazione, un ricalcolo del layout del testo e un il testo.
Gli intervalli che influiscono sulle metriche del testo estendono la classe MetricAffectingSpan
, un
una classe astratta che consente alle sottoclassi di definire in che modo l'intervallo influisce sulla misurazione del testo
fornendo l'accesso al TextPaint
. Poiché MetricAffectingSpan
si estende
CharacterSpan
, le sottoclassi influiscono sull'aspetto del testo in corrispondenza del carattere
livello.
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. Intervalli che interessano interi paragrafi
implementare ParagraphStyle
. A
utilizzi queste sezioni, le alleghi all'intero paragrafo, escludendo la fine
una nuova riga. Se provi ad applicare un'estensione di paragrafo a qualcosa di diverso
un intero paragrafo, Android non applica affatto l'intervallo.
La Figura 8 mostra in che modo Android separa i paragrafi nel testo.
Il codice di esempio riportato di seguito applica un
QuoteSpan
a un paragrafo. Tieni presente che
se colleghi l'intervallo a qualsiasi posizione diversa dall'inizio o dalla fine di un
, 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);
Creazione di intervalli personalizzati
Se hai bisogno di più funzionalità rispetto a quelle fornite nella versione attuale puoi implementare un intervallo personalizzato. Quando implementi il tuo intervallo, decidi se l'intervallo influisce sul testo a livello di carattere o di paragrafo. ma anche se influiscono sul layout o sull'aspetto del testo. Questo ti aiuta determinare quali classi base puoi estendere e quali interfacce potresti aver bisogno da 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 le dimensioni del testo e
colore, estendi RelativeSizeSpan
. Tramite l'ereditarietà, RelativeSizeSpan
estende CharacterStyle
e implementa le due interfacce Update
. Dal momento che
offre già callback per updateDrawState
e updateMeasureState
,
puoi ignorare questi callback per implementare il comportamento personalizzato. La
il seguente codice crea un intervallo personalizzato che si estende RelativeSizeSpan
esegue l'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 illustra come creare un intervallo personalizzato. Puoi ottenere lo stesso
applicando RelativeSizeSpan
e ForegroundColorSpan
al testo.
Utilizzo intervallo di test
L'interfaccia Spanned
consente di impostare e recuperare intervalli da
testo. Durante il test, implementa una JUnit Android
test per verificare che vengano aggiunti gli intervalli corretti
nelle posizioni corrette. Esempio di Stile di testo
app
contiene un intervallo che applica il markup agli elenchi puntati allegando
BulletPointSpan
al testo. Il seguente esempio di codice mostra come eseguire il test
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, vedi MarkdownBuilderTest su GitHub.
Test di intervalli personalizzati
Durante il test degli intervalli, verifica che TextPaint
contenga i valori previsti
modifiche e che in Canvas
vengano visualizzati gli elementi corretti. Per
Ad esempio, considera un'implementazione di un intervallo personalizzato che anteponga un punto elenco a
del testo. Il punto elenco ha una dimensione e un colore specificati e c'è uno spazio vuoto
tra il margine sinistro dell'area disegnabile e il punto elenco.
Puoi testare il comportamento di questa classe implementando un test AndroidJUnit, verificando quanto segue:
- Se applichi correttamente l'intervallo, viene fornito un punto elenco delle dimensioni specificate e compare il colore sulla tela e deve esserci uno spazio adeguato tra la parte sinistra a un margine e a un elenco puntato.
- Se non applichi l'intervallo, non viene visualizzato alcun comportamento personalizzato.
Puoi vedere l'implementazione di questi test nel TextStyling di esempio su GitHub.
Puoi testare le interazioni con Canvas simulando la tela, trasmettendo quella
oggetto
drawLeadingMargin()
e verificando che i metodi corretti siano richiamati
parametri.
Puoi trovare altri campioni di test di intervallo in BulletPointSpanTest.
Best practice per l'utilizzo degli intervalli
Esistono diversi modi efficienti di memoria per impostare il testo in un TextView
, a seconda
in base alle tue esigenze.
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
imposta 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 dei tuoi
Spannable
come SpannedString
e lo mantiene in memoria come CharSequence
.
Ciò significa che il testo e le sezioni sono immutabili, quindi quando devi
aggiorna il testo o gli intervalli, crea un nuovo oggetto Spannable
e richiama
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,
BufferType.SPANNABLE
fa sì che TextView
crei un SpannableString
, mentre
L'oggetto CharSequence
conservato da TextView
ora ha un markup modificabile e
e immutabile. Per aggiornare l'intervallo, recupera il testo come Spannable
e poi
aggiornare gli intervalli in base alle esigenze.
Quando alleghi, scolleghi o riposiziona gli intervalli, TextView
automaticamente
si aggiornano per riflettere la modifica al testo. Se modifichi un attributo interno
di un intervallo esistente, chiama invalidate()
per apportare modifiche relative all'aspetto oppure
requestLayout()
per apportare modifiche correlate alle metriche.
Imposta il testo in una TextView più volte
In alcuni casi, ad esempio quando si utilizza un
RecyclerView.ViewHolder
,
puoi riutilizzare TextView
e impostare il testo più volte. Di
predefinito, a prescindere dalla configurazione di BufferType
, TextView
crea
una copia dell'oggetto CharSequence
e la contiene in memoria. In questo modo
TextView
aggiornamenti intenzionali: non puoi aggiornare l'originale
CharSequence
per aggiornare il testo. Questo significa che ogni volta che imposti
di testo, TextView
crea un nuovo oggetto.
Se vuoi avere un maggiore controllo su questo processo ed evitare l'oggetto aggiuntivo
puoi implementare le soluzioni
Spannable.Factory
e override
newSpannable()
.
Anziché creare un nuovo oggetto di testo, puoi trasmettere e restituire l'oggetto esistente
CharSequence
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 usare textView.setText(spannableObject, BufferType.SPANNABLE)
quando
impostare il testo. In caso contrario, l'origine CharSequence
viene creata come Spanned
dell'istanza e non può essere trasmesso a Spannable
, causando la generazione di un messaggio di errore da parte di newSpannable()
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 al tuo
TextView
. Se utilizzi un RecyclerView
, imposta l'oggetto Factory
quando
innanzitutto ad aumentare le visualizzazioni. In questo modo si evita la creazione di oggetti aggiuntivi
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
in un intervallo di punti personalizzato, puoi evitare l'overhead di chiamare
setText()
più volte mantenendo un riferimento all'intervallo al momento della creazione.
Quando devi modificare l'intervallo, puoi modificare il riferimento e richiamare
invalidate()
o requestLayout()
su TextView
, a seconda del tipo di
che hai modificato.
Nel codice di esempio che segue, un'implementazione di un elenco puntato 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 dell'estensione Android KTX
Android KTX contiene anche funzioni di estensione che semplificano l'utilizzo degli intervalli è più facile. Per saperne di più, consulta la documentazione relativa a androidx.core.text pacchetto.