Проблема при выделении текста в TextView на Android 15+

Введение

Порой при разработке мобильного приложения (и в целом любого ПО) можно столкнуться с ошибками, которые не зависят от разработчика. Например, проблема внутри одной из подключенных библиотек. В этом случае логичным решением будет либо найти альтернативу, либо написать своё решение. Однако может быть и так, что проблема гораздо масштабнее и так просто не решается. К таким можно отнести ошибки внутри операционной системы.

ОС Android представляет собой очень сложный технический продукт, над которым работают сотни инженеров. В такой системе довольно сложно отследить все возможные ошибки. Изменения, которые на первый взгляд кажутся правильными, могут в итоге повлечь за собой различные последствия.

С одной из таких проблем мы столкнулись при реализации выделения текста в приложении для чтения.

Примечание: Данная проблема касается версий Android 15 (API 35) и Android 16 (API 36) QPR2 Beta 2, которая на момент написания статьи находится в Platform Stability стадии.

Некорректное выделение текста

При использовании TextView с выравниванием текста по ширине (justificationMode = JUSTIFICATION_MODE_INTER_WORD) возникает следующая проблема: во время выделения текста маркеры и подсветка часто смещаются. В результате выделяется не то слово, которое должно быть, маркеры не совпадают с символами.

К примеру, вот так выглядит выделение текста на Android 14. Слово “целиком” корректно выделено, маркеры находятся по его краям.

А вот так выделение выглядит на Android 15 и Android 16 соответственно.

Тот же текст, то же слово, но теперь выделение и маркеры оказались смещены и не выделяют слово полностью.

В поисках решения этой проблемы были проверены различные подходы:

  • создание кастомного MovementMethod;
  • рисование собственных слоёв поверх TextView;
  • проведение хит-тестов (hit-testing).

Однако ничего из этого не помогло, т.к. проблема оказалась глубже.

Как работает выделение в TextView

Для понимания сути проблемы нужно изучить, как в целом устроено выделение текста внутри TextView.

Как мы знаем, TextView для работы использует стек различных технологий, часть из которых относится к Android SDK, а другая – к ядру, написанному на C++.

В случае с выделением, мы работаем со следующими компонентами TextView:

  • Layout (StaticLayout или DynamicLayout) – отвечает за отображение текста. Он также рассчитывает переносы строк и их выравнивание.
  • LineBreaker – используется для переноса строк и выравнивания по ширине.
  • WordIterator – определяет границы слов.

Когда мы выделяем текст, метод TextView.getOffsetForPosition(x, y) преобразовывает координаты касания в индекс символа.

Затем метод Layout.getSelectionPath(start, end, dest) строит подсветку выделения.

Изменения в Android 15+

В новых версиях Android инженеры из Google обновили работу LineBreaker. Обо всех изменениях в классе можно почитать в репозитории AOSP (Android Open Source Project), самые важные из них представлены ниже:

  • Переработка алгоритма распределения пробелов при выравнивании по ширине.
  • Обновлены ICU-правила переноса.
  • Добавлена поддержка JUSTIFICATION_MODE_INTER_CHARACTER (для CJK-языков).

В результате этих изменений методы TextView.getOffsetForPosition(x, y) и Layout.getPrimaryHorizontal(offset) стали возвращать смещённые координаты для строк, выравненных по ширине.

По этой причине стандартные способы решения не смогли помочь. Кастомный MovementMethod получает данные из Layout, а собственный слой подсветки поверх TextView опирается на Layout.getPrimaryHorizontal(offset). В обоих случаях мы приходим к тому, что работаем с заведомо неверными данными, возвращаемыми системой.

Часто бывает, что ошибка кроется в версии Android от конкретного вендора, однако в данном случае было проверено, что проблема воспроизводится как на эмуляторах AOSP, так и на устройствах Pixel и Samsung.

Из всего вышесказанного можно сделать вывод, что данная ошибка относится к проблемам операционной системы. Кроме того, на Reddit (тут, и тут) Google Issue Tracker (тут, тут и тут)  также можно найти темы, связанные со странным поведением TextView при использовании выравнивания по ширине.

Заключение

Не всегда исправление ошибки напрямую представляется возможным. Проблема может находиться там, где мы (как разработчики) не можем получить доступ.

В этом случае остаётся либо ждать того, что в будущем проблема будет исправлена, либо искать альтернативные способы решения.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *