Что такое spanable элементы в Android
Что проверяют
Знаете ли вы, что текст в Android это не строка, а строка с разметкой, и умеете ли стилизовать часть текста, не разбивая экран на лишние View. Вопрос обычно вырастает из практики: подсветить подстроку, вставить иконку в середину предложения, сделать кликабельным один фрагмент.
Как отвечать
Сначала иерархия: CharSequence, дальше Spanned (разметка есть, менять нельзя), Spannable (текст неизменяем, разметку менять можно), Editable (меняется и то, и другое). Отсюда классы SpannedString, SpannableString и SpannableStringBuilder. Разница между двумя последними не в скорости, а в изменяемости самого текста.
Разметка это объекты-спаны, навешенные на диапазон: StyleSpan, ForegroundColorSpan, RelativeSizeSpan, ImageSpan, ClickableSpan, URLSpan.
val text = SpannableStringBuilder("Осталось 3 дня")
text.setSpan(
StyleSpan(Typeface.BOLD),
9, 10,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
textView.text = textЧетвёртый аргумент задаёт поведение границ при вставке текста: включающий и исключающий края решают, попадёт ли дописанный рядом символ под спан. Для готовой строки разницы нет, для поля ввода она решает всё.
Полезно упомянуть удобные построители из androidx.core, разбор размеченных строк через HtmlCompat и то, что в Compose ту же задачу решает AnnotatedString.
Где спотыкаются
Вешают ClickableSpan и удивляются, что нажатие не приходит. Нужен способ обработки перемещений по тексту, а он же делает кликабельным весь TextView целиком и ломает обычное нажатие на карточке списка.
Пересобирают разметку при каждой привязке ячейки списка. Для длинного текста это заметно в прокрутке: готовую размеченную строку считают один раз при подготовке данных и хранят рядом с моделью.
Держат внутри спана ссылку на экран или на обработчик, который его захватывает, и получают утечку, потому что размеченный текст живёт дольше экрана. Внутрь передают то, что переживает поворот: идентификатор элемента и вызов во ViewModel.