Как реализовать отображение спойлеров в Android TextView
Что проверяют
Знаете ли вы Spannable API и умеете ли применить эффект к части текста, не ломая остальную разметку. Следом обычно спрашивают, как нарисовать анимированный «шум» поверх скрытого текста и как состояние спойлера переживает переиспользование ячеек в RecyclerView.
Как отвечать
Спойлер это диапазон внутри текста, поэтому решается он спаном, а не отдельным TextView и не подменой строки целиком:
- простой вариант: свой наследник
ClickableSpanс флагомrevealed. ВupdateDrawState(ds: TextPaint)для скрытого состояния задайтеds.bgColorиds.colorодним цветом, тогда текст превращается в сплошную плашку, и уберите подчёркивание черезds.isUnderlineText = false; - клик:
ClickableSpanработает только приtextView.movementMethod = LinkMovementMethod.getInstance(). Подсветку нажатия убираетhighlightColor = Color.TRANSPARENT. ВonClickпереключите флаг и перерисуйтеwidget.invalidate(); - эффект с частицами: в своём
TextViewили оверлее берите границы диапазона черезlayout.getSelectionPath(start, end, path)илиgetLineForOffsetсgetPrimaryHorizontal. Спойлер может занимать несколько строк. Анимацию крутитValueAnimator; - Compose:
AnnotatedStringс аннотацией на диапазоне, клик черезLinkAnnotation.Clickable, а область для отрисовки поверх текста даётTextLayoutResult.getPathForRange.
Состояние раскрытия храните в модели сообщения, а не в спане или View: ячейки переиспользуются, и раскрытый спойлер «переедет» в другое сообщение.
Где спотыкаются
По клику заменяют весь текст на другую строку. Теряются остальные спаны, ссылки и форматирование, а спойлер посреди абзаца превращается в отдельный абзац.
Забывают про LinkMovementMethod, и клик не срабатывает. Или ставят его и не замечают, что TextView в элементе списка начинает забирать касания, и нажатие на саму ячейку перестаёт работать.
Прячут текст только визуально. TalkBack прочитает его вслух, выделение и копирование покажут содержимое. Для скрытого состояния нужно подменять текст для специальных возможностей и не давать выделять диапазон.