SpannableString: co to jest, klasy stylów i jak działa

Autor: IT Sectr Opublikowano: 2026-07-23 Czas czytania: 10 min

SpannableString to klasa Android SDK z pakietu android.text, która pozwala stosować wiele stylów do różnych fragmentów tego samego ciągu tekstowego w TextView. W przeciwieństwie do znaczników HTML, SpannableString działa na poziomie obiektów Span, zarządzając wizualnym wyświetlaniem tekstu: kolorem, rozmiarem, krojem, podkreśleniem i elementami interaktywnymi. Według Google Developers, SpannableString jest używany w systemowych komponentach Androida do formatowania linków. Jest to podstawowy sposób stylizacji tekstu bez korzystania z zewnętrznych bibliotek.

Najważniejsze

  • SpannableString — klasa Android do stylizacji tekstu przez obiekty Span, niezmienny po utworzeniu
  • Spanny dzielą się na CharacterStyle (pojedyncze znaki) i ParagraphStyle (całe akapity)
  • ForegroundColorSpan zmienia kolor tekstu, StyleSpan odpowiada za pogrubienie i kursywę
  • SpannableStringBuilder — zmienna wersja do dynamicznego budowania tekstu z obsługą Editable
  • Span flags określają zachowanie spanna przy wstawianiu i usuwaniu tekstu na granicach zakresu

Co to jest SpannableString?

SpannableString to klasa Androida implementująca interfejs Spannable, która przechowuje tekst wraz z zestawem obiektów Span zarządzających wizualnym wyświetlaniem. W przeciwieństwie do zwykłego String, SpannableString pozwala przypisać atrybuty stylów do konkretnych zakresów znaków: sprawić, że część tekstu będzie czerwona, zwiększyć czcionkę w nagłówku lub dodać klikalny link w akapicie.

Klasa znajduje się w pakiecie android.text i jest dostępna od API Level 1. SpannableString jest niezmienny — po utworzeniu jego struktura jest ustalona, a zmiana tekstu wymaga utworzenia nowego obiektu. Do dynamicznego edytowania używa się SpannableStringBuilder, który obsługuje wstawianie i usuwanie znaków bez utraty stylów.

Różnica od CharSequence

CharSequence to podstawowy interfejs dla danych tekstowych, który implementują String, StringBuilder i SpannableString. Główna różnica między SpannableString a String polega na obsłudze przypisywania dowolnych obiektów do podciągu. TextView rozpoznaje interfejs Spannable i podczas rysowania stosuje obiekty span do odpowiednich fragmentów tekstu. Jeśli przekażesz zwykły String do TextView, żadne style nie zostaną zastosowane.

Budowa wewnętrzna

SpannableString przechowuje tekst w postaci tablicy char[] i osobną tablicę obiektów span z metadanymi o pozycji początkowej i końcowej. Przy wywołaniu setSpan(what, start, end, flags) obiekt what jest zapisywany na liście wraz z informacją o zakresie. Podczas rysowania TextView kolejno stosuje wszystkie spanny znajdujące się w wyświetlanym zakresie, wywołując metody updateDrawState i updateMeasureState.

Typy spannów: CharacterStyle i ParagraphStyle

CharacterStyle — klasa bazowa dla spannów wpływających na pojedyncze znaki niezależnie od ich położenia w wierszach. Należą do nich ForegroundColorSpan (kolor tekstu), RelativeSizeSpan (względny rozmiar), StyleSpan (pogrubienie i kursywa), UnderlineSpan (podkreślenie) i inne. Spanny Character są stosowane do każdego znaku w określonym zakresie oddzielnie.

ParagraphStyle — interfejs dla spannów wpływających na całe akapity. Najbardziej znany przedstawiciel to AlignmentSpan, który wyrównuje cały akapit do lewej, środka lub prawej. Spanny Paragraph muszą obejmować cały akapit, w przeciwnym razie Android ignoruje ich zastosowanie. To ograniczenie wynika z tego, że wyrównanie lub wcięcie ma sens tylko dla całego bloku tekstu.

Span flags

Span flags to cztery stałe określające zachowanie spanna przy wstawianiu lub usuwaniu tekstu na granicach jego zakresu. SPAN_EXCLUSIVE_EXCLUSIVE pozostawia spann aktywnym tylko wewnątrz oryginalnych granic, SPAN_INCLUSIVE_INCLUSIVE rozszerza go przy dodawaniu tekstu do granic. SPAN_EXCLUSIVE_INCLUSIVE i SPAN_INCLUSIVE_EXCLUSIVE dają mieszane zachowanie dla początku i końca zakresu.

FlagaWstawianie z lewejWstawianie z prawej
SPAN_EXCLUSIVE_EXCLUSIVEnie obejmujenie obejmuje
SPAN_INCLUSIVE_INCLUSIVEobejmujeobejmuje
SPAN_EXCLUSIVE_INCLUSIVEnie obejmujeobejmuje
SPAN_INCLUSIVE_EXCLUSIVEobejmujenie obejmuje

Prawidłowy wybór flagi jest krytyczny dla tekstu Editable w EditText, gdzie użytkownik może wstawiać i usuwać znaki. Dla TextView tylko do odczytu zwykle używa się SPAN_EXCLUSIVE_EXCLUSIVE — styl jest stosowany tylko do oryginalnego zakresu i nie rozszerza się przy programowych zmianach.

Główne klasy spannów w Android SDK

Android SDK dostarcza ponad 25 wbudowanych klas spannów, pokrywających większość zadań stylizacji tekstu. Każda klasa implementuje interfejs CharacterStyle lub ParagraphStyle i przyjmuje parametry przez konstruktor. Wszystkie klasy znajdują się w pakiecie android.text.style i są dostępne bez podłączania dodatkowych zależności.

Spanny koloru i tła

ForegroundColorSpan ustawia kolor tekstu dla określonego zakresu, przyjmując kolor w formacie int. BackgroundColorSpan koloruje tło pod tekstem, co jest przydatne do podświetlania zapytań wyszukiwania. AbsoluteSizeSpan ustawia dokładny rozmiar czcionki w pikselach, RelativeSizeSpan — mnożnik względem podstawowego rozmiaru tekstu w TextView.

Spanny kroju

StyleSpan przyjmuje stałe Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC lub BOLD_ITALIC i zmienia krój znaków. UnderlineSpan dodaje podkreślenie, StrikethroughSpan — przekreślenie. SuperscriptSpan i SubscriptSpan tworzą indeks górny i dolny. TypefaceSpan pozwala ustawić niestandardowy font przez obiekt Typeface dla zakresu tekstu.

Spanny interaktywne

ClickableSpan — klasa abstrakcyjna do tworzenia klikalnych fragmentów tekstu. Po naciśnięciu wywoływana jest metoda onClick(). Aby kliknięcia działały, TextView musi mieć setMovementMethod(LinkMovementMethod.getInstance()). URLSpan — podklasa ClickableSpan dla hiperłączy z automatycznym otwieraniem przeglądarki. ClickableSpan często łączy się z ForegroundColorSpan, aby wizualnie wyróżnić link na niebiesko.

kotlin
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()

Bez LinkMovementMethod kliknięcia na URLSpan nie będą obsłużone. MovementMethod odpowiada za przechwytywanie zdarzeń dotykowych i wyszukiwanie ClickableSpan w pozycji dotknięcia. Kolorowy spann sprawia, że link jest widoczny dla użytkownika.

Jak używać SpannableString w kodzie

Praca z SpannableString zaczyna się od utworzenia instancji z ciągu tekstowego i kolejnego stosowania spannów przez metodę setSpan(). Metoda przyjmuje cztery parametry: obiekt spanna, pozycję początkową, pozycję końcową i flagi. Po ustawieniu wszystkich spannów obiekt jest przekazywany do TextView przez setText().

Kolor i rozmiar tekstu

Stwórzmy ciąg, w którym pierwsze słowo będzie czerwone i powiększone. W tym celu używamy ForegroundColorSpan dla koloru i RelativeSizeSpan dla skali. Oba spanny są stosowane do tego samego zakresu niezależnie od siebie — kolejność wywołania setSpan nie ma znaczenia.

kotlin
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

Znaki od pozycji 0 do 9 otrzymują oba style jednocześnie. TextView automatycznie zastosuje wszystkie spanny podczas rysowania — nie są wymagane żadne dodatkowe wywołania. RelativeSizeSpan z mnożnikiem 1.5f zwiększy rozmiar czcionki o 50% względem podstawowego.

Łączenie z tagami HTML

Html.fromHtml() tworzy obiekt Spanned z ciągu HTML, ale zestaw obsługiwanych tagów jest ograniczony. SpannableString daje pełną kontrolę nad każdym atrybutem bez ograniczeń HTML. Jeśli potrzebujesz przekształcić HTML na spanny z późniejszym dodaniem niestandardowych stylów, możesz użyć Html.fromHtml() jako podstawy, a następnie uzupełnić spannami przez setSpan().

kotlin
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

Rezultat — tekst „Ważne:“ będzie pogrubiony (z HTML) i czerwony (ze spanna). Taki sposób jest wygodny przy pracy z treścią serwerową, gdzie część formatowania jest zadana przez HTML, a część dodawana po stronie klienta programowo.

Łączenie wielu spannów

SpannableString pozwala nakładać nieograniczoną liczbę spannów na ten sam lub nakładający się zakres. Łączenie spannów to kluczowa zaleta w porównaniu z HTML, gdzie zagnieżdżone tagi mogą powodować konflikty. Spanny są niezależne i stosowane sekwencyjnie podczas rysowania.

Na przykład można sprawić, że fragment tekstu będzie jednocześnie pogrubiony, czerwony i klikalny. W tym celu tworzy się trzy spanny — StyleSpan, ForegroundColorSpan i ClickableSpan — i każdy stosuje się do tego samego zakresu. Kolejność stosowania nie wpływa na wynik, ponieważ każdy spann odpowiada za swoją cechę tekstu.

Nakładanie się spannów różnych typów

Jeśli spanny różnych typów nakładają się częściowo, każdy działa niezależnie w swoich granicach. ForegroundColorSpan na zakresie 0–10 i StyleSpan(BOLD) na zakresie 5–15 dadzą pogrubiony czerwony tekst na fragmencie 5–10 i tylko pogrubiony na 10–15. Nie powstają żadne konflikty, ponieważ każdy spann zmienia swój atrybut podczas rysowania.

Pobieranie listy spannów

Metoda getSpans(int start, int end, Class type) zwraca tablicę spannów znajdujących się w określonym zakresie. Jest to przydatne do sprawdzenia, jakie style już zostały zastosowane, lub do usunięcia konkretnych spannów. Za pomocą nextSpanTransition() można iterować po granicach zmian spannów — to podstawa działania niestandardowych TextView, które muszą wiedzieć, gdzie zmienia się styl.

SpannableStringBuilder do budowania tekstu

SpannableStringBuilder to klasa do stopniowego budowania stylizowanego tekstu z możliwością wstawiania, zastępowania i usuwania fragmentów. W przeciwieństwie do SpannableString, który jest tworzony z gotowego ciągu i niezmienny, Builder pozwala dodawać części tekstu sekwencyjnie i na bieżąco przypisywać im style. Jest to idealny wybór dla komunikatów złożonych: logów, czatów, nagłówków wiadomości z dynamicznymi etykietami.

Builder implementuje interfejsy Spannable i Editable, co czyni go kompatybilnym z EditText. Użytkownik może edytować tekst, a style zachowują się i poprawnie przesuwają przy wstawianiu nowych znaków. SpannableString, będąc niezmiennym, nie nadaje się do pól edytowalnych.

Łańcuch operacji

Metoda append() zwraca samego buildera, co pozwala budować łańcuch wywołań. Po dodaniu tekstu stosuje się spanny przez setSpan(). Pozycje są określane względem bieżącej długości buildera. Dostępne są także insert() i replace() dla bardziej precyzyjnej kontroli nad zawartością.

kotlin
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

Tekst „Nowy “ jest pokolorowany na niebiesko, a „komentarz“ — na szaro. Przy wstawianiu dodatkowych znaków między nimi spann nie obejmie nowego tekstu dzięki flagom EXCLUSIVE. Builder automatycznie koryguje wewnętrzne indeksy przy modyfikacji.

Wydajność i optymalizacja spannów

Używanie dużej liczby spannów wpływa na wydajność rysowania TextView. Każdy spann wywołuje metodę updateDrawState() lub updateMeasureState() przy każdym przerysowaniu. Zaleca się ograniczenie liczby spannów na jeden TextView do 50–100 dla komfortowej pracy na urządzeniach średniej klasy. Spanny zmieniające rozmiar tekstu (RelativeSizeSpan, AbsoluteSizeSpan) wymagają przeliczenia layoutu przy każdej zmianie, co jest znacznie droższe niż spanny tylko dla koloru lub podkreślenia.

TextAppearanceSpan

TextAppearanceSpan pozwala zastosować cały zestaw stylów z zasobu xml android:textAppearance jedną operacją setSpan(). Zamiast trzech osobnych spannów (kolor, rozmiar, czcionka) używa się jednego TextAppearanceSpan z odnośnikiem do stylu. Zmniejsza to liczbę obiektów i upraszcza utrzymanie — zmiana stylu w zasobie automatycznie stosuje się do wszystkich tekstów, w których używany jest ten spann.

Ponowne wykorzystanie obiektów

Tworzenie nowej instancji spanna dla każdego setSpan() prowadzi do dodatkowego obciążenia garbage collectora. Optymalnie jest tworzyć stałe obiekty spannów, jeśli są stosowane wielokrotnie. Na przykład ForegroundColorSpan(Color.RED) można zapisać w companion object i ponownie używać. Jednak spanny ze stanem (ClickableSpan z różnymi handlerami) muszą być tworzone indywidualnie dla każdego przypadku.

Pomiar i profilowanie

Do diagnozowania problemów z wydajnością spannów używaj Layout Inspector w Android Studio i profilera GPU. Jeśli TextView z dużą liczbą spannów zauważalnie zwalnia podczas przewijania, rozważ zastąpienie części spannów statycznymi stylami przez TextAppearanceSpan lub zmniejszenie liczby spannów przez połączenie atrybutów w niestandardowych implementacjach UpdateAppearance.

Często zadawane pytania

Czym SpannableString różni się od zwykłego String?

String — niezmienna sekwencja znaków bez obsługi stylów. SpannableString przechowuje te same znaki, ale dodatkowo zawiera tablicę obiektów Span z informacją o formatowaniu. TextView określa typ przekazanego CharSequence i stosuje spanny do odpowiednich zakresów podczas rysowania. String ignoruje wszelkie atrybuty stylów i jest wyświetlany jako plain text.

Jak usunąć wszystkie spanny z SpannableString?

Metoda removeSpan(Object span) usuwa konkretny spann. Aby całkowicie wyczyścić, wywołaj getSpans(0, length, Object::class.java), który zwraca tablicę wszystkich spannów, a następnie każdy usuń przez removeSpan. Alternatywnie utwórz nowy SpannableString(text.toString()) bez spannów. SpannableStringBuilder ma metodę clear(), która usuwa zarówno tekst, jak i spanny.

Czy SpannableString działa w EditText?

SpannableString działa w EditText, ale dla tekstu edytowalnego preferowany jest SpannableStringBuilder, implementujący Editable. EditText wymaga interfejsu Editable do śledzenia zmian. Jeśli przekażesz SpannableString do EditText, tekst wyświetli się ze stylami, ale podczas edycji Android przekształci go w Editable, co może zresetować część spannów.

Czy można używać SpannableString w Compose?

W Jetpack Compose spanny Android SDK nie są używane bezpośrednio. Zamiast tego Compose udostępnia AnnotatedString — własny odpowiednik SpannableString z podobnymi możliwościami: SpanStyle dla stylów pojedynczych znaków i ParagraphStyle dla akapitów. Konwersja SpannableString na AnnotatedString jest możliwa przez buildAnnotatedString z iteracją po spannach.

Jak utworzyć niestandardowy Span?

Utwórz klasę dziedziczącą po CharacterStyle i nadpisz metodę updateDrawState(TextPaint tp). Wewnątrz metody zmień właściwości TextPaint: kolor, grubość linii, efekty. Do zmian metrycznych użyj UpdateLayout lub MetricAffectingSpan. Niestandardowe spanny stosuje się przez setSpan() tak samo jak wbudowane.

Podsumowanie

  • SpannableString — klasa Android do stylizacji tekstu przez obiekty Span z przypisaniem do zakresów znaków
  • CharacterStyle wpływa na pojedyncze znaki, ParagraphStyle — na całe akapity
  • Główne spanny: ForegroundColorSpan, StyleSpan, RelativeSizeSpan, URLSpan, ClickableSpan
  • SpannableStringBuilder — zmienna wersja do dynamicznego budowania tekstu z Editable
  • Łączenie spannów jest bezpieczne — każdy spann zmienia swój atrybut niezależnie
  • Span flags zarządzają zachowaniem spanna przy wstawianiu tekstu na granicach
  • Optymalizacja: używaj TextAppearanceSpan dla grup stylów i ograniczaj liczbę spannów do 100

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również