SpannableString è una classe Android SDK del pacchetto android.text che consente di applicare più stili a diverse parti di una singola stringa di testo in TextView. A differenza del markup HTML, SpannableString lavora a livello di oggetti Span, controllando la visualizzazione del testo: colore, dimensione, carattere, sottolineatura ed elementi interattivi. Secondo Google Developers, SpannableString viene utilizzato nei componenti di sistema Android per formattare i collegamenti. È il modo principale per stilizzare il testo senza usare librerie di terze parti.
Punti Chiave
SpannableString è una classe Android che implementa l'interfaccia Spannable, memorizzando il testo insieme a un insieme di oggetti Span che controllano la visualizzazione. A differenza di una String normale, SpannableString consente di allegare attributi di stile a intervalli specifici di caratteri: rendere rossa una parte del testo, aumentare la dimensione del carattere in un'intestazione o aggiungere un link cliccabile all'interno di un paragrafo.
La classe si trova nel pacchetto android.text ed è disponibile dall'API Level 1. SpannableString è immutabile — una volta creata, la sua struttura è fissa e la sostituzione del testo richiede la creazione di un nuovo oggetto. Per la modifica dinamica si usa SpannableStringBuilder, che supporta l'inserimento e la cancellazione di caratteri senza perdere gli stili.
CharSequence è l'interfaccia di base per i dati testuali, implementata da String, StringBuilder e SpannableString. La differenza principale tra SpannableString e String è il supporto per allegare oggetti arbitrari a una sottostringa. TextView riconosce l'interfaccia Spannable e applica gli oggetti span ai segmenti di testo corrispondenti durante il rendering. Se passi una String normale a TextView, nessuno stile verrà applicato.
SpannableString memorizza il testo come array char[] e un array separato di oggetti span con metadati sulle posizioni di inizio e fine. Quando viene chiamato setSpan(what, start, end, flags), l'oggetto what viene salvato nell'elenco insieme alle informazioni sull'intervallo. Durante il rendering, TextView applica sequenzialmente tutti gli span che rientrano nell'intervallo visualizzato, chiamando i metodi updateDrawState e updateMeasureState.
CharacterStyle è la classe base per gli span che influenzano caratteri individuali indipendentemente dalla loro posizione nelle righe. Questo include ForegroundColorSpan (colore del testo), RelativeSizeSpan (dimensione relativa), StyleSpan (grassetto e corsivo), UnderlineSpan (sottolineatura) e altri. Gli span di carattere vengono applicati a ciascun carattere nell'intervallo specificato individualmente.
ParagraphStyle è un'interfaccia per gli span che influenzano paragrafi interi. Il rappresentante più noto è AlignmentSpan, che allinea l'intero paragrafo a sinistra, centro o destra. Gli span di paragrafo devono coprire l'intero paragrafo; altrimenti, Android ignora la loro applicazione. Questa limitazione è dovuta al fatto che l'allineamento o il rientro hanno senso solo per un blocco di testo completo.
I flag di Span sono quattro costanti che definiscono il comportamento dello span quando il testo viene inserito o cancellato ai confini del suo intervallo. SPAN_EXCLUSIVE_EXCLUSIVE mantiene lo span attivo solo all'interno dei confini originali, SPAN_INCLUSIVE_INCLUSIVE lo estende quando il testo viene aggiunto ai confini. SPAN_EXCLUSIVE_INCLUSIVE e SPAN_INCLUSIVE_EXCLUSIVE forniscono un comportamento misto per l'inizio e la fine dell'intervallo.
| Flag | Inserimento a Sinistra | Inserimento a Destra |
|---|---|---|
| SPAN_EXCLUSIVE_EXCLUSIVE | non include | non include |
| SPAN_INCLUSIVE_INCLUSIVE | include | include |
| SPAN_EXCLUSIVE_INCLUSIVE | non include | include |
| SPAN_INCLUSIVE_EXCLUSIVE | include | non include |
Scegliere il flag corretto è fondamentale per il testo Editable in EditText, dove l'utente può inserire e cancellare caratteri. Per TextView di sola lettura, si usa tipicamente SPAN_EXCLUSIVE_EXCLUSIVE — lo stile si applica solo all'intervallo originale e non si espande con modifiche programmatiche.
L'SDK Android fornisce oltre 25 classi di span integrate che coprono la maggior parte delle attività di stilizzazione del testo. Ogni classe implementa l'interfaccia CharacterStyle o ParagraphStyle e accetta parametri tramite il suo costruttore. Tutte le classi si trovano nel pacchetto android.text.style e sono disponibili senza aggiungere dipendenze extra.
ForegroundColorSpan imposta il colore del testo per un intervallo specificato, accettando un colore in formato int. BackgroundColorSpan colora lo sfondo dietro il testo, utile per evidenziare i risultati di ricerca. AbsoluteSizeSpan imposta una dimensione esatta del carattere in pixel, RelativeSizeSpan applica un moltiplicatore relativo alla dimensione base del testo in TextView.
StyleSpan accetta le costanti Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC o BOLD_ITALIC e modifica il carattere dei simboli. UnderlineSpan aggiunge la sottolineatura, StrikethroughSpan aggiunge la barratura. SuperscriptSpan e SubscriptSpan creano apice e pedice. TypefaceSpan consente di impostare un carattere personalizzato tramite un oggetto Typeface per un intervallo di testo.
ClickableSpan è una classe astratta per creare segmenti di testo cliccabili. Al tocco, viene chiamato il metodo onClick(). Affinché i clic funzionino, TextView deve avere setMovementMethod(LinkMovementMethod.getInstance()). URLSpan è una sottoclasse di ClickableSpan per i collegamenti ipertestuali con apertura automatica del browser. ClickableSpan è spesso combinato con ForegroundColorSpan per evidenziare visivamente il collegamento in blu.
val spannable = SpannableString("Open developer.android.com")
spannable.setSpan(
URLSpan("https://developer.android.com"),
9, 31, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
spannable.setSpan(
ForegroundColorSpan(Color.BLUE),
9, 31, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
textView.text = spannable
textView.movementMethod = LinkMovementMethod.getInstance()
Senza LinkMovementMethod, i clic su URLSpan non verranno elaborati. MovementMethod è responsabile dell'intercettazione degli eventi tattili e della ricerca del ClickableSpan nella posizione del tocco. Lo span di colore rende il collegamento visibile all'utente.
Lavorare con SpannableString inizia creando un'istanza da una stringa di testo e applicando sequenzialmente gli span tramite il metodo setSpan(). Il metodo accetta quattro parametri: l'oggetto span, la posizione iniziale, la posizione finale e i flag. Dopo aver impostato tutti gli span, l'oggetto viene passato a TextView tramite setText().
Creiamo una stringa in cui la prima parola sia rossa e ingrandita. Per questo, usiamo ForegroundColorSpan per il colore e RelativeSizeSpan per la scala. Entrambi gli span vengono applicati allo stesso intervallo in modo indipendente — l'ordine delle chiamate setSpan non ha importanza.
val text = "Header: remaining text"
val spannable = SpannableString(text)
val colorSpan = ForegroundColorSpan(Color.RED)
val sizeSpan = RelativeSizeSpan(1.5f)
spannable.setSpan(colorSpan, 0, 9, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
spannable.setSpan(sizeSpan, 0, 9, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.text = spannable
I caratteri dalla posizione 0 alla 9 ricevono entrambi gli stili simultaneamente. TextView applica automaticamente tutti gli span durante il rendering — non sono necessarie chiamate aggiuntive. RelativeSizeSpan con un moltiplicatore di 1.5f aumenta la dimensione del carattere del 50% rispetto alla base.
Html.fromHtml() crea un oggetto Spanned da una stringa HTML, ma l'insieme di tag supportati è limitato. SpannableString dà il controllo completo su ogni attributo senza le limitazioni dell'HTML. Se è necessario convertire HTML in span e poi aggiungere stili personalizzati, puoi usare Html.fromHtml() come base e poi integrare con span tramite setSpan().
val htmlText = Html.fromHtml(
"<b>Important:</b> check the data",
Html.FROM_HTML_MODE_LEGACY
)
val spannable = SpannableString(htmlText)
spannable.setSpan(
ForegroundColorSpan(Color.RED),
0, 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
textView.text = spannable
Il risultato — il testo “Importante:” sarà in grassetto (dall'HTML) e rosso (dallo span). Questo approccio è comodo quando si lavora con contenuti server dove parte della formattazione è specificata in HTML e parte viene aggiunta lato client programmaticamente.
SpannableString consente di applicare un numero illimitato di span allo stesso intervallo o a intervalli sovrapposti. Combinare gli span è un vantaggio chiave rispetto al markup HTML, dove i tag annidati possono entrare in conflitto. Gli span sono indipendenti e vengono applicati sequenzialmente durante il rendering.
Ad esempio, puoi rendere un segmento di testo simultaneamente grassetto, rosso e cliccabile. Per farlo, crea tre span — StyleSpan, ForegroundColorSpan e ClickableSpan — e applica ciascuno allo stesso intervallo. L'ordine di applicazione non influisce sul risultato, poiché ogni span è responsabile del proprio attributo di testo.
Se span di diversi tipi si sovrappongono parzialmente, ciascuno funziona indipendentemente all'interno dei propri confini. ForegroundColorSpan sull'intervallo 0–10 e StyleSpan(BOLD) sull'intervallo 5–15 daranno testo rosso grassetto sul segmento 5–10 e solo grassetto su 10–15. Non sorgono conflitti perché ogni span modifica il proprio attributo durante il rendering.
Il metodo getSpans(int start, int end, Class type) restituisce un array di span all'interno dell'intervallo specificato. Questo è utile per verificare quali stili sono già applicati o per rimuovere span specifici. Con nextSpanTransition(), puoi iterare sui confini di cambiamento degli span — questa è la base per implementazioni personalizzate di TextView che devono sapere dove lo stile cambia.
SpannableStringBuilder è una classe per la costruzione passo-passo di testo stilizzato con la possibilità di inserire, sostituire ed eliminare frammenti. A differenza di SpannableString, che viene creato da una stringa pronta ed è immutabile, Builder consente di aggiungere parti di testo sequenzialmente e assegnare stili al volo. È ideale per messaggi compositi: log, chat, titoli di notizie con etichette dinamiche.
Builder implementa le interfacce Spannable e Editable, rendendolo compatibile con EditText. L'utente può modificare il testo e gli stili vengono preservati e spostati correttamente quando vengono inseriti nuovi caratteri. SpannableString, essendo immutabile, non è adatto per campi modificabili.
Il metodo append() restituisce il builder stesso, consentendo il concatenamento dei metodi. Dopo aver aggiunto il testo, gli span vengono applicati tramite setSpan(). Le posizioni sono specificate rispetto alla lunghezza corrente del builder. Insert() e replace() sono anche disponibili per un controllo più preciso del contenuto.
val builder = SpannableStringBuilder()
.append("New ")
.append("comment")
val blue = ForegroundColorSpan(Color.BLUE)
val gray = ForegroundColorSpan(Color.GRAY)
builder.setSpan(blue, 0, 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
builder.setSpan(gray, 6, 17, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.text = builder
Il testo “Nuovo ” è colorato in blu e “commento” in grigio. Inserendo caratteri aggiuntivi tra di loro, lo span non influenzerà il nuovo testo grazie ai flag EXCLUSIVE. Builder regola automaticamente gli indici interni durante la modifica.
L'uso di un gran numero di span influisce sulle prestazioni di rendering di TextView. Ogni span chiama il metodo updateDrawState() o updateMeasureState() a ogni ridisegno. Si consiglia di limitare il numero di span per TextView a 50–100 per prestazioni confortevoli su dispositivi di fascia media. Gli span che modificano la dimensione del testo (RelativeSizeSpan, AbsoluteSizeSpan) richiedono il ricalcolo del layout a ogni modifica, il che è significativamente più costoso degli span solo colore o sottolineatura.
TextAppearanceSpan consente di applicare un intero set di stili da una risorsa XML android:textAppearance con una singola chiamata setSpan(). Invece di tre span separati (colore, dimensione, carattere), viene usato un singolo TextAppearanceSpan con un riferimento allo stile. Questo riduce il numero di oggetti e semplifica la manutenzione — la modifica dello stile nella risorsa si applica automaticamente a tutti i testi che usano questo span.
Creare una nuova istanza di span per ogni setSpan() aggiunge carico extra al garbage collector. È ottimale creare oggetti span costanti se vengono usati ripetutamente. Ad esempio, ForegroundColorSpan(Color.RED) può essere memorizzato in un oggetto companion e riutilizzato. Tuttavia, gli span con stato (ClickableSpan con diversi gestori) devono essere creati individualmente per ogni caso.
Per diagnosticare problemi di prestazioni degli span, usa Layout Inspector in Android Studio e il profiler GPU. Se una TextView con molti span rallenta notevolmente durante lo scorrimento, considera di sostituire alcuni span con stili statici tramite TextAppearanceSpan o di ridurre il numero di span combinando attributi in implementazioni personalizzate di UpdateAppearance.
Domande Frequenti
String è una sequenza immutabile di caratteri senza supporto per gli stili. SpannableString memorizza gli stessi caratteri ma contiene inoltre un array di oggetti Span con informazioni di formattazione. TextView determina il tipo di CharSequence passato e applica gli span agli intervalli corrispondenti durante il rendering. String ignora qualsiasi attributo di stile e viene visualizzato come testo semplice.
Il metodo removeSpan(Object span) rimuove uno span specifico. Per una pulizia completa, chiama getSpans(0, length, Object::class.java), che restituisce un array di tutti gli span, e poi rimuovi ciascuno tramite removeSpan. In alternativa, crea un nuovo SpannableString(text.toString()) senza span. SpannableStringBuilder ha un metodo clear() che rimuove sia il testo che gli span.
SpannableString funziona in EditText, ma per testo modificabile è preferibile SpannableStringBuilder, che implementa Editable. EditText richiede l'interfaccia Editable per tracciare le modifiche. Se passi SpannableString a EditText, il testo verrà visualizzato con gli stili, ma durante la modifica Android lo convertirà in Editable, il che potrebbe reimpostare alcuni span.
In Jetpack Compose, gli span di Android SDK non vengono usati direttamente. Invece, Compose fornisce AnnotatedString, il suo equivalente di SpannableString con capacità simili: SpanStyle per gli stili di caratteri individuali e ParagraphStyle per i paragrafi. La conversione da SpannableString ad AnnotatedString è possibile tramite buildAnnotatedString con iterazione sugli span.
Crea una classe che estende CharacterStyle e sovrascrivi il metodo updateDrawState(TextPaint tp). All'interno del metodo, modifica le proprietà di TextPaint: colore, spessore del tratto, effetti. Per modifiche metriche, usa UpdateLayout o MetricAffectingSpan. Gli span personalizzati vengono applicati tramite setSpan() proprio come quelli integrati.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche