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 flags определяют поведение спана при вставке и удалении текста на границах диапазона

Что такое 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 (подчёркивание) и другие. Character-спаны применяются к каждому символу в указанном диапазоне по отдельности.

ParagraphStyle — интерфейс для спанов, влияющих на целые абзацы. Наиболее известный представитель — AlignmentSpan, который выравнивает весь абзац по левому краю, центру или правому краю. Paragraph-спаны должны охватывать весь абзац целиком, иначе Android игнорирует их применение. Это ограничение связано с тем, что выравнивание или отступ имеют смысл только для целого блока текста.

Span flags

Span flags — это четыре константы, определяющие поведение спана при вставке или удалении текста на границах его диапазона. 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, где пользователь может вставлять и удалять символы. Для read-only 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 игнорирует любые стилевые атрибуты и отображается как plain text.

Как удалить все спаны из 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 flags управляют поведением спана при вставке текста на границах
  • Оптимизация: используйте TextAppearanceSpan для групп стилей и ограничивайте количество спанов до 100

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

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