Реалізація підтримки масштабування шрифтів в Android
Android дозволяє користувачу змінити масштаб шрифту в Settings → Display → Font Size. Діапазон: від 0.85x до 2.0x на більшості стоків (Samsung One UI дозволяє до 1.6x через стандартний UI, але fontScale в Configuration може досягати 2.0x). sp одиниці — масштабуються; dp — ні.
Основні поломи
sp vs dp для тексту
android:textSize="16sp" — правильно, шрифт масштабується. android:textSize="16dp" — не масштабується. Хардкод в dp — одна з найчастіших помилок, яку Lint не завжди ловить (він попереджує, але не як Error).
У Compose: fontSize = 16.sp — масштабується. fontSize = 16.dp — Lint виддасть помилку компіляції починаючи з Compose 1.3. Але TextUnit.Unspecified у кастомних компонентах — знову проблема.
Фіксована висота контейнерів
Найпоширеніша проблема: android:layout_height="48dp" на TextView або контейнері з текстом. При fontScale = 2.0 16sp текст займає ~64dp висоти. Контейнер в 48dp — обрізка.
Правильно: wrap_content для висоти всіх текстових елементів. Для ConstraintLayout — убрати фіксовану висоту у ланцюжків з текстом.
minHeight можна залишити для touch target (мінімум 48dp за Material Design) — але лише через minHeight, не height.
Рядки з вставками
String.format("Ви заробили %d очків", points) — при довгому числі рядок видовжується. Але проблема не в числі, а в тому, що вокруг нього фіксований layout. ConstraintLayout з wrap_content справляється; вкладені LinearLayout з gravity — ні.
Compose: LocalDensity та масштаб
У Jetpack Compose LocalDensity містить і density (DPI), і fontScale. Якщо вручну конвертувати розміри через density.density без врахування fontScale — кастомні компоненти з TextUnit-розрахунками не масштабуються.
with(LocalDensity.current) { 16.sp.toPx() } — повертає пікселі вже з врахуванням fontScale. Якщо використовувати це значення як висоту Canvas — все добре. Якщо ігнорувати результат та встановити фіксований height в Modifier — ні.
Нетипичный кейс: відключення масштабування для окремих елементів
Іноді дизайн вимагає, щоб конкретний елемент (логотип з текстом, декоративна надпис) не масштабувався. У Compose: CompositionLocalProvider(LocalDensity provides Density(density = LocalDensity.current.density, fontScale = 1f)) — переопределяет fontScale всередину підтерева на 1.0. Це допустимо для декоративних елементів, але не для функціональних текстів.
У XML: android:textSize="16dp" — технічно працює, але Lint ругається. Краще обмотати в стиль з суфіксом NonScalable щоб це було явно.
Тестування
adb shell settings put system font_scale 2.0 — мгновенно змінює масштаб без перезапуску пристрою. Після перевірки: adb shell settings put system font_scale 1.0.
Android Studio Device Emulator: Pixel 6 з fontScale 2.0 — перший стенд для перевірки layout. Реальний Samsung з One UI — другий стенд, там поведінка відрізняється.
Espresso snapshot-тесты з InstrumentationRegistry.getInstrumentation().targetContext.resources.configuration.fontScale = 2f — для регресійного контролю.
Термін: 2-3 дні. Вартість розраховується індивідуально.







