Стоит пользователю зайти в настройки системы и увеличить размер текста – как весь наш аккуратный UI, бережно отрисованный по макетам из фигмы под экран 411dp со шрифтами 14–16sp, поплывет во все стороны. Дело не только в размере. Пользователь может сделать текст жирнее, добавить контур, изменить масштаб интерфейса, а на отдельных прошивках включить что-то еще. Все это сильно повлияет на реальный вид приложения.
Наша ответственность – нормально отобразиться при любых пользовательских настройках. До Android 14 fontScale менялся по фиксированным ступеням, примерно от 0.85 до 1.30. Потом появился non-linear font scaling до 200%, и теперь UI нужно проверять на действительно крупном масштабе.
В Compose размер текста задаётся в sp, а значит учитывает системные настройки. Увеличится не только компонент Text, но и контейнеры, в которые он вложен: кнопки, поля ввода, табы.
Что можно сделать в UI:
• Гибкие размеры вместо фиксированных. Не задавать жесткую высоту там, где текст потенциально вырастет. Фиксированная высота задавит контент: строки станут выше, текст не влезет, элементы обрежутся. height лучше заменить на heightIn. То же касается горизонтальных компоновок. При увеличенном тексте два Button внутри Row перестанут помещаться по ширине. В таких случаях Row лучше заменить на FlowRow, чтобы элементы переносились ниже.
• Текст нужно проектировать так, чтобы он выдерживал рост и по ширине, и по высоте. Поведение maxLines и overflow лучше продумать заранее. Если используется maxLines = 1, нужен способ показать текст полностью, например через Marquee. Для выравнивания лучше использовать логические направления – TextAlign.Start и TextAlign.End, чтобы интерфейс работал в разных локалях. Внутри чипов и кнопок текст чаще всего стоит центрировать.
• В Raw именно текст должен быть гибким, а служебные элементы – стабильными. Иначе длинная строка слева может вытолкнуть иконку справа за край экрана.
Здесь поможет .weight(1f, fill = false) – текст займет только доступное место и не сломает соседние элементы.
• Кнопки, поля ввода и контейнеры растут вместе с контентом. Если высота TextField жестко ограничена, ввод станет неудобным или невозможным. Для SnackbarHost иногда есть смысл переопределить maxLines. Если в Text есть inlineContent, его размеры тоже нужно контролировать отдельно.
• Отступы и скролл становятся обязательными. На обычном масштабе места хватит всем. На крупном сломается в первую очередь. Поэтому нужны вертикальные интервалы 8dp, боковые паддинги 16dp, contentPadding у списков и WindowInsets, чтобы контент не уезжал под клавиатуру. Без скролла часть экрана может стать недоступной.
• И еще много других сценариев, которые ты обнаружишь, если протыкаешь интерфейс приложения под лупой.
Когда UI адаптирован, его можно проверить с увеличенным шрифтом, через @Preview(fontScale = 1.5f). Для длинных строк подставлять LoremIpsum.
Я выкрутил fontScale на 200% и проверил все установленные приложения. Опыт довольно мрачный. Заботиться о слепошарых пользователях не принято.
Однако есть немного магии, чтобы не заниматься ручной адаптацией. Можно ограничить рост размера текста внутри приложения. С точки зрения доступности решение спорное, но для сохранения рабочего UI вполне надежное. Делается это оборачиванием части UI или всей темы в CompositionLocalProvider:
val base = LocalDensity.current
CompositionLocalProvider(
LocalDensity provides Density(
density = base.density,
fontScale = min(base.fontScale, 1.15f)
)
) {
content()
}
С этим кодом текст внутри приложения не будет расти выше заданного лимита – 1.15f. Это верхняя граница fontScale, которую ты допускаешь для данного участка UI. Если у пользователя fontScale = 1.0, ничего не изменится. Если 1.3 – значение будет ограничено до 1.15.