SpannableString este o clasă din Android SDK din pachetul android.text care permite aplicarea mai multor stiluri la diferite porțiuni ale unui șir de text în TextView. Spre deosebire de marcarea HTML, SpannableString funcționează la nivelul obiectelor Span, gestionând afișarea vizuală a textului: culoare, dimensiune, font, subliniere și elemente interactive. Conform Google Developers, SpannableString este utilizat în componentele de sistem Android pentru formatarea linkurilor. Este principala metodă de stilizare a textului fără a conecta biblioteci terțe.
Principalele
SpannableString este o clasă Android care implementează interfața Spannable, stocând textul împreună cu un set de obiecte Span ce gestionează afișarea vizuală. Spre deosebire de String obișnuit, SpannableString permite atașarea atributelor de stil la intervale specifice de caractere: faceți o parte din text roșie, măriți fontul în titlu sau adăugați un link clickabil în interiorul paragrafului.
Clasa se află în pachetul android.text și este disponibilă de la API Level 1. SpannableString este nemodificabil — după creare, structura sa se fixează, iar înlocuirea textului necesită crearea unui nou obiect. Pentru editarea dinamică se folosește SpannableStringBuilder, care suportă inserarea și ștergerea caracterelor fără pierderea stilurilor.
CharSequence este interfața de bază pentru datele text, implementată de String, StringBuilder și SpannableString. Diferența principală dintre SpannableString și String constă în suportul pentru atașarea obiectelor arbitrare la subșiruri. TextView recunoaște interfața Spannable și la randare aplică obiectele span la porțiunile corespunzătoare ale textului. Dacă se transmite un String obișnuit la TextView, nu se vor aplica stiluri.
SpannableString stochează textul ca un tablou char[] și un tablou separat de obiecte span cu metadate despre poziția de început și sfârșit. La apelarea setSpan(what, start, end, flags), obiectul what este salvat în listă împreună cu informațiile despre interval. La randare, TextView aplică secvențial toate span-urile care se încadrează în intervalul afișat, apelând metodele updateDrawState și updateMeasureState.
CharacterStyle este clasa de bază pentru span-urile care afectează caracterele individuale, indiferent de poziția lor în rânduri. Acestea includ ForegroundColorSpan (culoarea textului), RelativeSizeSpan (dimensiunea relativă), StyleSpan (bold și italic), UnderlineSpan (subliniere) și altele. Character-span-urile se aplică fiecărui caracter din intervalul specificat în mod individual.
ParagraphStyle este interfața pentru span-urile care afectează paragrafe întregi. Cel mai cunoscut reprezentant este AlignmentSpan, care aliniază întregul paragraf la stânga, centru sau dreapta. Paragraph-span-urile trebuie să acopere întregul paragraf, altfel Android ignoră aplicarea lor. Această restricție se datorează faptului că alinierea sau indentarea are sens doar pentru un bloc întreg de text.
Span flags sunt patru constante care determină comportamentul span-ului la inserarea sau ștergerea textului la granițele intervalului său. SPAN_EXCLUSIVE_EXCLUSIVE menține span-ul activ doar în interiorul granițelor originale, SPAN_INCLUSIVE_INCLUSIVE îl extinde la adăugarea textului la granițe. SPAN_EXCLUSIVE_INCLUSIVE și SPAN_INCLUSIVE_EXCLUSIVE oferă un comportament mixt pentru începutul și sfârșitul intervalului.
| Flag | Inserare stânga | Inserare dreapta |
|---|---|---|
| SPAN_EXCLUSIVE_EXCLUSIVE | nu include | nu include |
| SPAN_INCLUSIVE_INCLUSIVE | include | include |
| SPAN_EXCLUSIVE_INCLUSIVE | nu include | include |
| SPAN_INCLUSIVE_EXCLUSIVE | include | nu include |
Alegerea corectă a flag-ului este critică pentru textul Editable în EditText, unde utilizatorul poate insera și șterge caractere. Pentru TextView doar citire se folosește de obicei SPAN_EXCLUSIVE_EXCLUSIVE — stilul se aplică doar intervalului original și nu se extinde la modificări programatice.
Android SDK oferă peste 25 de clase încorporate de span-uri, acoperind majoritatea sarcinilor de stilizare a textului. Fiecare clasă implementează interfața CharacterStyle sau ParagraphStyle și acceptă parametri prin constructor. Toate clasele se află în pachetul android.text.style și sunt disponibile fără a conecta dependențe suplimentare.
ForegroundColorSpan stabilește culoarea textului pentru intervalul specificat, acceptând culoarea în format int. BackgroundColorSpan colorează fundalul sub text, util pentru evidențierea interogărilor de căutare. AbsoluteSizeSpan stabilește dimensiunea exactă a fontului în pixeli, RelativeSizeSpan — un multiplicator față de dimensiunea de bază a textului în TextView.
StyleSpan acceptă constantele Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC sau BOLD_ITALIC și modifică aspectul caracterelor. UnderlineSpan adaugă subliniere, StrikethroughSpan — tăiere. SuperscriptSpan și SubscriptSpan creează indici superiori și inferiori. TypefaceSpan permite setarea unui font personalizat prin obiectul Typeface pentru un interval de text.
ClickableSpan este o clasă abstractă pentru crearea porțiunilor clickabile de text. La apăsare se apelează metoda onClick(). Pentru ca click-urile să funcționeze, TextView trebuie să aibă setMovementMethod(LinkMovementMethod.getInstance()). URLSpan este o subclasă a ClickableSpan pentru hiperlinkuri cu deschiderea automată a browserului. ClickableSpan este adesea combinat cu ForegroundColorSpan pentru a evidenția vizual linkul cu albastru.
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()
Fără LinkMovementMethod, click-urile pe URLSpan nu vor fi procesate. MovementMethod răspunde de interceptarea evenimentelor tactile și căutarea ClickableSpan în poziția atingerii. Span-ul color face linkul vizibil pentru utilizator.
Lucrul cu SpannableString începe cu crearea unei instanțe dintr-un șir text și aplicarea succesivă a span-urilor prin metoda setSpan(). Metoda acceptă patru parametri: obiectul span, poziția de început, poziția de sfârșit și flag-urile. După setarea tuturor span-urilor, obiectul se transmite la TextView prin setText().
Vom crea un șir unde primul cuvânt va fi roșu și de dimensiune mărită. Pentru aceasta se folosește ForegroundColorSpan pentru culoare și RelativeSizeSpan pentru scară. Ambele span-uri se aplică aceluiași interval independent unul de celălalt — ordinea apelării setSpan nu contează.
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
Caracterele de la poziția 0 la 9 vor primi ambele stiluri simultan. TextView aplică automat toate span-urile la randare — nu sunt necesare apeluri suplimentare. RelativeSizeSpan cu multiplicatorul 1.5f va mări dimensiunea fontului cu 50% față de cea de bază.
Html.fromHtml() creează un obiect Spanned dintr-un șir HTML, dar setul de tag-uri suportate este limitat. SpannableString oferă control complet asupra fiecărui atribut fără limitările HTML. Dacă este necesară convertirea HTML în span-uri cu adăugarea ulterioară de stiluri personalizate, se poate folosi Html.fromHtml() ca bază, apoi completa cu span-uri prin 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
Rezultatul — textul „Important:“ va fi bold (din HTML) și roșu (din span). Această abordare este convenabilă la lucrul cu conținut server, unde o parte din formatare este setată prin HTML, iar o parte se adaugă pe partea clientului programatic.
SpannableString permite aplicarea unui număr nelimitat de span-uri pe același interval sau pe intervale suprapuse. Combinarea span-urilor este un avantaj cheie față de marcarea HTML, unde tag-urile imbricate pot intra în conflict. Span-urile sunt independente și se aplică secvențial la randare.
De exemplu, se poate face o porțiune de text simultan bold, roșie și clickabilă. Pentru aceasta se creează trei span-uri — StyleSpan, ForegroundColorSpan și ClickableSpan — și fiecare se aplică aceluiași interval. Ordinea aplicării nu afectează rezultatul, deoarece fiecare span răspunde pentru propria caracteristică a textului.
Dacă span-urile de diferite tipuri se suprapun parțial, fiecare funcționează independent în granițele sale. ForegroundColorSpan pe intervalul 0-10 și StyleSpan(BOLD) pe intervalul 5-15 vor da text roșu bold pe porțiunea 5-10 și doar bold pe 10-15. Nu apar conflicte, deoarece fiecare span își modifică propriul atribut la randare.
Metoda getSpans(int start, int end, Class type) returnează un tablou de span-uri care se încadrează în intervalul specificat. Aceasta este utilă pentru verificarea stilurilor aplicate sau pentru ștergerea span-urilor specifice. Cu nextSpanTransition() se poate itera pe granițele de schimbare a span-urilor — aceasta este baza de funcționare a TextView-urilor personalizate care trebuie să știe unde se schimbă stilul.
SpannableStringBuilder este o clasă pentru construirea pas cu pas a textului stilizat cu posibilitatea de inserare, înlocuire și ștergere a fragmentelor. Spre deosebire de SpannableString, care este creat dintr-un șir gata și este nemodificabil, Builder permite adăugarea succesivă a părților de text și atribuirea stilurilor pe parcurs. Este alegerea ideală pentru mesaje compuse: loguri, chat-uri, titluri de știri cu etichete dinamice.
Builder implementează interfețele Spannable și Editable, ceea ce îl face compatibil cu EditText. Utilizatorul poate edita textul, iar stilurile se păstrează și se deplasează corect la inserarea noilor caractere. SpannableString, fiind nemodificabil, nu este potrivit pentru câmpuri editabile.
Metoda append() returnează builder-ul însuși, permițând construirea unui lanț de apeluri. După adăugarea textului, span-urile se aplică prin setSpan(). Pozițiile se indică relativ la lungimea curentă a builder-ului. Insert() și replace() sunt de asemenea disponibile pentru un control mai precis asupra conținutului.
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
Textul „Nou “ este colorat cu albastru, iar „comentariu“ cu gri. La inserarea de caractere suplimentare între ele, span-ul nu va afecta noul text datorită flag-urilor EXCLUSIVE. Builder corectează automat indecșii interni la modificare.
Utilizarea unui număr mare de span-uri afectează performanța de randare a TextView. Fiecare span apelează metoda updateDrawState() sau updateMeasureState() la fiecare reandomare. Se recomandă limitarea numărului de span-uri pe un TextView la 50-100 pentru o funcționare confortabilă pe dispozitivele de segment mediu. Span-urile care modifică dimensiunea textului (RelativeSizeSpan, AbsoluteSizeSpan) necesită recalcularea layout-ului la fiecare schimbare, ceea ce este semnificativ mai costisitor decât span-urile doar pentru culoare sau subliniere.
TextAppearanceSpan permite aplicarea unui set întreg de stiluri din resursa xml android:textAppearance printr-o singură operație setSpan(). În loc de trei span-uri separate (culoare, dimensiune, font) se folosește un singur TextAppearanceSpan cu referință la stil. Aceasta reduce numărul de obiecte și simplifică întreținerea — modificarea stilului în resursă se aplică automat la toate textele care folosesc acest span.
Crearea unei noi instanțe de span pentru fiecare setSpan() duce la o încărcare suplimentară a colectorului de gunoi. Optimal este să creați obiecte constante de span dacă sunt aplicate în mod repetat. De exemplu, ForegroundColorSpan(Color.RED) poate fi salvat în companion object și reutilizat. Totuși, span-urile cu stare (ClickableSpan cu handleri diferiți) trebuie create individual pentru fiecare caz.
Pentru diagnosticarea problemelor de performanță ale span-urilor utilizați Layout Inspector în Android Studio și profilatorul GPU. Dacă TextView cu un număr mare de span-uri încetinește vizibil la scroll, luați în considerare înlocuirea unei părți din span-uri cu stiluri statice prin TextAppearanceSpan sau reducerea numărului de span-uri prin combinarea atributelor în implementări personalizate UpdateAppearance.
Întrebări frecvente
String este o secvență nemodificabilă de caractere fără suport pentru stiluri. SpannableString stochează aceleași caractere, dar suplimentar conține un tablou de obiecte Span cu informații de formatare. TextView determină tipul CharSequence transmis și aplică span-urile la intervalele corespunzătoare la randare. String ignoră orice atribute de stil și se afișează ca text simplu.
Metoda removeSpan(Object span) șterge un span specific. Pentru curățarea completă, apelați getSpans(0, length, Object::class.java), care returnează un tablou cu toate span-urile, apoi ștergeți fiecare prin removeSpan. Alternativ, creați un nou SpannableString(text.toString()) fără span-uri. SpannableStringBuilder are metoda clear() care șterge atât textul, cât și span-urile.
SpannableString funcționează în EditText, dar pentru text editabil se preferă SpannableStringBuilder care implementează Editable. EditText necesită interfața Editable pentru urmărirea modificărilor. Dacă se transmite SpannableString la EditText, textul se va afișa cu stiluri, dar la editare Android îl convertește în Editable, ceea ce poate reseta o parte din span-uri.
În Jetpack Compose, span-urile Android SDK nu se folosesc direct. În schimb, Compose oferă AnnotatedString — propriul său echivalent al SpannableString cu capacități similare: SpanStyle pentru stiluri ale caracterelor individuale și ParagraphStyle pentru paragrafe. Conversia SpannableString în AnnotatedString este posibilă prin buildAnnotatedString cu iterarea peste span-uri.
Creați o clasă care moștenește CharacterStyle și suprascrieți metoda updateDrawState(TextPaint tp). În interiorul metodei, modificați proprietățile TextPaint: culoarea, grosimea liniei, efectele. Pentru modificări metrice utilizați UpdateLayout sau MetricAffectingSpan. Span-urile personalizate se aplică prin setSpan() la fel ca și cele încorporate.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și