SpannableString: що це, класи стилів та як працює

Автор: IT Sectr Опубліковано: 2026-07-23 Час читання: 10 хв

SpannableString — це клас Android SDK з пакета android.text, який дозволяє застосовувати множинні стилі до різних ділянок одного текстового рядка в TextView. На відміну від HTML-розмітки, SpannableString працює на рівні об'єктів Span, керуючи візуальним відображенням тексту: кольором, розміром, накресленням, підкресленням та інтерактивними елементами. За даними Google Developers, SpannableString використовується в системних компонентах Android для форматування посилань. Це основний спосіб стилізації тексту без підключення сторонніх бібліотек.

Головне

  • SpannableString — клас Android для стилізації тексту через об'єкти Span, незмінний після створення
  • Спани поділяються на CharacterStyle (окремі символи) та ParagraphStyle (цілі абзаци)
  • ForegroundColorSpan змінює колір тексту, StyleSpan відповідає за жирність та курсив
  • SpannableStringBuilder — змінювана версія для динамічної побудови тексту з підтримкою Editable
  • Прапорці Span визначають поведінку спану при вставці та видаленні тексту на межах діапазону

Що таке SpannableString?

SpannableString — це клас Android, що реалізує інтерфейс Spannable, який зберігає текст разом із набором Span-об'єктів, керуючих візуальним відображенням. На відміну від звичайної String, SpannableString дозволяє прив'язати стильові атрибути до конкретних діапазонів символів: зробити частину тексту червоною, збільшити шрифт у заголовку або додати клікабельне посилання всередині абзацу.

Клас знаходиться в пакеті android.text та доступний з API Level 1. SpannableString незмінний — після створення його структура фіксується, і заміна тексту вимагає створення нового об'єкта. Для динамічного редагування використовується SpannableStringBuilder, який підтримує вставку та видалення символів без втрати стилів.

Відмінність від CharSequence

CharSequence — це базовий інтерфейс для текстових даних, який реалізують String, StringBuilder та SpannableString. Основна відмінність SpannableString від String полягає в підтримці прив'язки довільних об'єктів до підрядка. TextView розпізнає інтерфейс Spannable і при відображенні застосовує span-об'єкти до відповідних ділянок тексту. Якщо передати звичайний String у TextView, жодні стилі не будуть застосовані.

Внутрішня будова

SpannableString зберігає текст у вигляді масиву char[] та окремий масив span-об'єктів із метаданими про початкову та кінцеву позиції. При виклику setSpan(what, start, end, flags) об'єкт what зберігається у списку разом з інформацією про діапазон. При відображенні TextView послідовно застосовує всі спани, що потрапляють у діапазон, що відображається, викликаючи методи updateDrawState та updateMeasureState.

Типи спанів: CharacterStyle та ParagraphStyle

CharacterStyle — базовий клас для спанів, що впливають на окремі символи незалежно від їхнього положення в рядках. Сюди входять ForegroundColorSpan (колір тексту), RelativeSizeSpan (відносний розмір), StyleSpan (жирність та курсив), UnderlineSpan (підкреслення) та інші. Характерні спани застосовуються до кожного символу у вказаному діапазоні окремо.

ParagraphStyle — інтерфейс для спанів, що впливають на цілі абзаци. Найбільш відомий представник — AlignmentSpan, який вирівнює весь абзац по лівому краю, центру або правому краю. Параграфні спани повинні охоплювати весь абзац повністю, інакше Android ігнорує їх застосування. Це обмеження пов'язане з тим, що вирівнювання або відступ мають сенс лише для цілого блоку тексту.

Прапорці Span

Прапорці Span — це чотири константи, що визначають поведінку спану при вставці або видаленні тексту на межах його діапазону. SPAN_EXCLUSIVE_EXCLUSIVE залишає спан активним лише всередині початкових меж, SPAN_INCLUSIVE_INCLUSIVE розширює його при додаванні тексту до меж. SPAN_EXCLUSIVE_INCLUSIVE та SPAN_INCLUSIVE_EXCLUSIVE дають змішану поведінку для початку та кінця діапазону.

ПрапорецьВставка ліворучВставка праворуч
SPAN_EXCLUSIVE_EXCLUSIVEне включаєне включає
SPAN_INCLUSIVE_INCLUSIVEвключаєвключає
SPAN_EXCLUSIVE_INCLUSIVEне включаєвключає
SPAN_INCLUSIVE_EXCLUSIVEвключаєне включає

Правильний вибір прапорця критичний для Editable тексту в EditText, де користувач може вставляти та видаляти символи. Для TextView лише для читання зазвичай використовують SPAN_EXCLUSIVE_EXCLUSIVE — стиль застосовується лише до початкового діапазону та не розширюється при програмних змінах.

Основні класи спанів в Android SDK

Android SDK надає більше 25 вбудованих класів спанів, що покривають більшість завдань стилізації тексту. Кожен клас реалізує інтерфейс CharacterStyle або ParagraphStyle та приймає параметри через конструктор. Всі класи знаходяться в пакеті android.text.style та доступні без підключення додаткових залежностей.

Спани для кольору та фону

ForegroundColorSpan задає колір тексту для вказаного діапазону, приймаючи колір у форматі int. BackgroundColorSpan фарбує фон під текстом, що корисно для підсвічування пошукових запитів. AbsoluteSizeSpan задає точний розмір шрифту в пікселях, RelativeSizeSpan — множник відносно базового розміру тексту в TextView.

Спани для накреслення

StyleSpan приймає константи Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC або BOLD_ITALIC та змінює накреслення символів. UnderlineSpan додає підкреслення, StrikethroughSpan — закреслення. SuperscriptSpan та SubscriptSpan створюють надрядковий та підрядковий індекси. TypefaceSpan дозволяє задати кастомний шрифт через об'єкт Typeface для діапазону тексту.

Інтерактивні спани

ClickableSpan — абстрактний клас для створення клікабельних ділянок тексту. При натисканні викликається метод onClick(). Щоб кліки працювали, TextView повинен мати setMovementMethod(LinkMovementMethod.getInstance()). URLSpan — підклас ClickableSpan для гіперпосилань з автоматичним відкриттям браузера. ClickableSpan часто комбінують із ForegroundColorSpan, щоб візуально виділити посилання синім кольором.

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

Без LinkMovementMethod кліки по URLSpan не будуть оброблені. MovementMethod відповідає за перехоплення сенсорних подій та пошук ClickableSpan у позиції дотику. Кольоровий спан робить посилання видимим для користувача.

Як використовувати SpannableString в коді

Робота з SpannableString починається зі створення екземпляра з текстового рядка та послідовного застосування спанів через метод setSpan(). Метод приймає чотири параметри: об'єкт спану, початкову позицію, кінцеву позицію та прапорці. Після встановлення всіх спанів об'єкт передається в TextView через setText().

Колір та розмір тексту

Створимо рядок, де перше слово буде червоного кольору та збільшеного розміру. Для цього використовується ForegroundColorSpan для кольору та RelativeSizeSpan для масштабу. Обидва спани застосовуються до одного діапазону незалежно один від одного — порядок виклику setSpan не має значення.

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

Символи з позиції 0 по 9 отримають обидва стилі одночасно. TextView автоматично застосує всі спани при відображенні — жодних додаткових викликів не потрібно. RelativeSizeSpan з множником 1.5f збільшить розмір шрифту на 50% відносно базового.

Комбінування з HTML-тегами

Html.fromHtml() створює Spanned об'єкт із HTML-рядка, але набір підтримуваних тегів обмежений. SpannableString дає повний контроль над кожним атрибутом без обмежень HTML. Якщо потрібно перетворити HTML на спани з подальшим додаванням кастомних стилів, можна використати Html.fromHtml() як основу, а потім доповнити спанами через 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

Результат — текст «Важно:» буде жирним (з HTML) та червоним (із спану). Такий підхід зручний при роботі з серверним контентом, де частина форматування задається HTML, а частина додається на стороні клієнта програмно.

Комбінування кількох спанів

SpannableString дозволяє накладати необмежену кількість спанів на один і той самий або перекривний діапазон. Комбінування спанів — ключова перевага перед HTML-розміткою, де вкладені теги можуть конфліктувати. Спани незалежні та застосовуються послідовно при відображенні.

Наприклад, можна зробити ділянку тексту одночасно жирною, червоною та клікабельною. Для цього створюються три спани — StyleSpan, ForegroundColorSpan та ClickableSpan — і кожен застосовується до одного діапазону. Порядок застосування не впливає на результат, оскільки кожен спан відповідає за свою характеристику тексту.

Перекриття спанів різних типів

Якщо спани різних типів перекриваються частково, кожен працює незалежно у своїх межах. ForegroundColorSpan на діапазоні 0–10 та StyleSpan(BOLD) на діапазоні 5–15 дадуть жирний червоний текст на ділянці 5–10 та лише жирний на 10–15. Жодних конфліктів не виникає, оскільки кожен спан змінює свій атрибут при відображенні.

Отримання списку спанів

Метод getSpans(int start, int end, Class type) повертає масив спанів, що потрапляють у вказаний діапазон. Це корисно для перевірки, які стилі вже застосовані, або для видалення конкретних спанів. За допомогою nextSpanTransition() можна ітеруватися по межах зміни спанів — це основа роботи кастомних TextView, яким потрібно знати, де змінюється стиль.

SpannableStringBuilder для побудови тексту

SpannableStringBuilder — це клас для покрокової побудови стилізованого тексту з можливістю вставки, заміни та видалення фрагментів. На відміну від SpannableString, який створюється з готового рядка та є незмінним, Builder дозволяє додавати частини тексту послідовно та на льоту присвоювати їм стилі. Це ідеальний вибір для складених повідомлень: логів, чатів, новинних заголовків з динамічними мітками.

Builder реалізує інтерфейси Spannable та Editable, що робить його сумісним з EditText. Користувач може редагувати текст, а стилі зберігаються та коректно зміщуються при вставці нових символів. SpannableString, будучи незмінним, не підходить для редагованих полів.

Ланцюжок операцій

Метод append() повертає сам builder, що дозволяє вибудовувати ланцюжок викликів. Після додавання тексту застосовуються спани через setSpan(). Позиції вказуються відносно поточної довжини builder. Insert() та replace() також доступні для більш точного контролю над вмістом.

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

Текст «Новий » пофарбовано синім, а «коментар» — сірим. При вставці додаткових символів між ними спан не зачепить новий текст завдяки прапорцям EXCLUSIVE. Builder автоматично коригує внутрішні індекси при модифікації.

Продуктивність та оптимізація спанів

Використання великої кількості спанів впливає на продуктивність відображення TextView. Кожен спан викликає метод updateDrawState() або updateMeasureState() при кожному перемальовуванні. Рекомендується обмежувати кількість спанів на один TextView до 50–100 для комфортної роботи на пристроях середнього сегмента. Спани, що змінюють розмір тексту (RelativeSizeSpan, AbsoluteSizeSpan), вимагають перерахунку layout при кожній зміні, що значно дорожче, ніж спани лише для кольору або підкреслення.

TextAppearanceSpan

TextAppearanceSpan дозволяє застосувати цілий набір стилів із xml-ресурсу android:textAppearance однією операцією setSpan(). Замість трьох окремих спанів (колір, розмір, шрифт) використовується один TextAppearanceSpan із посиланням на стиль. Це знижує кількість об'єктів та спрощує підтримку — зміна стилю в ресурсі автоматично застосовується до всіх текстів, де використовується цей спан.

Повторне використання об'єктів

Створення нового екземпляра спану для кожного setSpan() призводить до додаткового навантаження на збирач сміття. Оптимально створювати константні об'єкти спанів, якщо вони застосовуються багаторазово. Наприклад, ForegroundColorSpan(Color.RED) можна зберегти в companion object та перевикористовувати. Однак спани зі станом (ClickableSpan з різними обробниками) повинні створюватися індивідуально для кожного випадку.

Вимірювання та профілювання

Для діагностики проблем з продуктивністю спанів використовуйте Layout Inspector в Android Studio та профілювальник GPU. Якщо TextView з великою кількістю спанів помітно гальмує при прокручуванні, розгляньте заміну частини спанів на статичні стилі через TextAppearanceSpan або скорочення кількості спанів шляхом об'єднання атрибутів у кастомні реалізації UpdateAppearance.

Часті запитання

Чим SpannableString відрізняється від звичайного String?

String — незмінна послідовність символів без підтримки стилів. SpannableString зберігає ті самі символи, але додатково містить масив Span-об'єктів з інформацією про форматування. TextView визначає тип переданого CharSequence та застосовує спани до відповідних діапазонів при відображенні. String ігнорує будь-які стильові атрибути та відображається як звичайний текст.

Як видалити всі спани з SpannableString?

Метод removeSpan(Object span) видаляє конкретний спан. Для повного очищення викличте getSpans(0, length, Object::class.java), який повертає масив усіх спанів, і потім кожен видаліть через removeSpan. Альтернативно створіть новий SpannableString(text.toString()) без спанів. SpannableStringBuilder має метод clear(), який видаляє і текст, і спани.

Чи працює SpannableString в EditText?

SpannableString працює в EditText, але для редагованого тексту краще використовувати SpannableStringBuilder, який реалізує Editable. EditText потребує інтерфейс Editable для відстеження змін. Якщо передати SpannableString в EditText, текст відобразиться зі стилями, але при редагуванні Android перетворить його на Editable, що може скинути частину спанів.

Чи можна використовувати SpannableString в Compose?

В Jetpack Compose спани Android SDK не використовуються безпосередньо. Натомість Compose надає AnnotatedString — власний аналог SpannableString з аналогічними можливостями: SpanStyle для стилів окремих символів та ParagraphStyle для абзаців. Конвертація SpannableString в AnnotatedString можлива через buildAnnotatedString з ітерацією по спанах.

Як створити кастомний Span?

Створіть клас, що успадковує від CharacterStyle, та перевизначте метод updateDrawState(TextPaint tp). Всередині методу змініть властивості TextPaint: колір, товщину лінії, ефекти. Для метричних змін використовуйте UpdateLayout або MetricAffectingSpan. Кастомні спани застосовуються через setSpan() так само, як і вбудовані.

Підсумки

  • SpannableString — клас Android для стилізації тексту через Span-об'єкти з прив'язкою до діапазонів символів
  • CharacterStyle впливає на окремі символи, ParagraphStyle — на цілі абзаци
  • Основні спани: ForegroundColorSpan, StyleSpan, RelativeSizeSpan, URLSpan, ClickableSpan
  • SpannableStringBuilder — змінювана версія для динамічної побудови тексту з Editable
  • Комбінування спанів безпечне — кожен спан змінює свій атрибут незалежно
  • Прапорці Span керують поведінкою спану при вставці тексту на межах
  • Оптимізація: використовуйте TextAppearanceSpan для груп стилів та обмежуйте кількість спанів до 100

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також