Поддержка масштабируемого контента

Реализуйте жест сведения и разведения пальцев, чтобы пользователи могли масштабировать контент в приложении. Это стандартный и единообразный для всей платформы способ улучшить доступность, позволяющий пользователям интуитивно настраивать размер текста и элементов интерфейса в соответствии со своими потребностями. Вы можете настроить масштабирование в приложении, чтобы пользователям было проще находить нужные функции, чем при использовании системных инструментов, таких как лупа.

Выберите стратегию масштабирования

Стратегии, описанные в этом руководстве, заставляют пользовательский интерфейс перекомпоновываться и реорганизовываться в соответствии с шириной экрана. Это значительно повышает доступность, устраняя необходимость в горизонтальном панорамировании и разочаровывающем "зигзагообразном" движении, которое в противном случае потребовалось бы для чтения длинных строк текста.

Дополнительная информация. Исследования подтверждают, что пользователям со слабым зрением гораздо удобнее читать контент и перемещаться по нему, если он переформатируется, а не требует панорамирования в двух измерениях. Подробнее о сравнении панорамирования и адаптивного контента на мобильных устройствах…

Масштабировать все элементы или только текстовые элементы

В таблице ниже показано, как работают разные стратегии масштабирования.

Стратегия Масштабирование плотности Масштабирование шрифта

Алгоритм работы

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

Применяется только к текстовым элементам. Общий макет и нетекстовые компоненты остаются прежнего размера.

Что масштабируется

Все визуальные элементы: текст, компоненты (кнопки, значки), изображения и интервалы в макете (отступы, поля).

Только текст

Демонстрация

Рекомендации

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

Тип интерфейса

Рекомендуемая стратегия

Рассуждения

Макеты с большим объемом текста

Примеры: новостные статьи, мессенджеры.

Плотность или масштабирование шрифта

Масштабирование плотности предпочтительно для масштабирования всей области контента, включая встроенные изображения.

Масштабирование шрифта – это простая альтернатива, если нужно масштабировать только текст.

Визуально структурированные макеты

Примеры: магазины приложений, ленты социальных сетей.

Масштабирование плотности

Сохраняет визуальные связи между изображениями и текстом в каруселях и сетках. Благодаря этому не требуется горизонтальное панорамирование, которое может конфликтовать с вложенными элементами прокрутки.

Как распознавать жесты масштабирования в Jetpack Compose

Чтобы поддерживать масштабируемый пользователем контент, сначала необходимо обнаружить жесты с несколькими касаниями. В Jetpack Compose это можно сделать с помощью Modifier.transformable.

Модификатор transformable – это высокоуровневый API, который предоставляет дельту zoomChange с момента последнего события жеста. Это упрощает логику обновления состояния до прямого накопления (например, scale *= zoomChange), что делает его идеальным для стратегий адаптивного масштабирования, описанных в этом руководстве.

Примеры реализации

В примерах ниже показано, как реализовать стратегии масштабирования плотности и шрифта.

Масштабирование плотности

Этот подход масштабирует базовый density области пользовательского интерфейса. В результате все измерения, основанные на макете, включая отступы, интервалы и размеры компонентов, масштабируются, как если бы изменился размер или разрешение экрана. Поскольку размер текста также зависит от плотности, он также масштабируется пропорционально. Эта стратегия эффективна, когда вы хотите равномерно увеличить все элементы в определенной области, сохраняя общий визуальный ритм и пропорции вашего пользовательского интерфейса.

private class DensityScalingState(
    // Note: For accessibility, typical min/max values are ~0.75x and ~3.5x.
    private val minScale: Float = 0.75f,
    private val maxScale: Float = 3.5f,
    private val currentDensity: Density
) {
    val transformableState = TransformableState { zoomChange, _, _ ->
        scaleFactor.floatValue =
            (scaleFactor.floatValue * zoomChange).coerceIn(minScale, maxScale)
    }
    val scaleFactor = mutableFloatStateOf(1f)
    fun scaledDensity(): Density {
        return Density(
            currentDensity.density * scaleFactor.floatValue,
            currentDensity.fontScale
        )
    }
}

@Composable
fun DensityScalingSample() {
    val currentDensity = LocalDensity.current
    val scaleState =
        remember(currentDensity) { DensityScalingState(currentDensity = currentDensity) }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .transformable(state = scaleState.transformableState),
        contentAlignment = Alignment.TopCenter
    ) {
        CompositionLocalProvider(
            LocalDensity provides scaleState.scaledDensity()
        ) {
            DemoCard()
        }
    }
}

Масштабирование шрифта

Эта стратегия более целенаправленная, поскольку изменяется только фактор fontScale. В результате увеличиваются или уменьшаются только текстовые элементы, а все остальные компоненты макета, такие как контейнеры, отступы и значки, остаются фиксированного размера. Эта стратегия хорошо подходит для улучшения читаемости текста в приложениях, где много читают.

class FontScaleState(
    // Note: For accessibility, typical min/max values are ~0.75x and ~3.5x.
    private val minScale: Float = 0.75f,
    private val maxScale: Float = 3.5f,
    private val currentDensity: Density
) {
    val transformableState = TransformableState { zoomChange, _, _ ->
        scaleFactor.floatValue =
            (scaleFactor.floatValue * zoomChange).coerceIn(minScale, maxScale)
    }
    val scaleFactor = mutableFloatStateOf(1f)
    fun scaledFont(): Density {
        return Density(
            currentDensity.density,
            currentDensity.fontScale * scaleFactor.floatValue
        )
    }
}

@Composable
fun FontScalingSample() {
    val currentDensity = LocalDensity.current
    val scaleState = remember { FontScaleState(currentDensity = currentDensity) }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .transformable(state = scaleState.transformableState),
        contentAlignment = Alignment.TopCenter
    ) {
        CompositionLocalProvider(
            LocalDensity provides scaleState.scaledFont()
        ) {
            DemoCard()
        }
    }
}

Общий демоинтерфейс

Это общая composable-функция DemoCard, которая используется в обоих примерах выше, чтобы показать разницу в масштабировании.

@Composable
private fun DemoCard() {
    Card(
        modifier = Modifier
            .width(360.dp)
            .padding(16.dp),
        shape = RoundedCornerShape(12.dp)
    ) {
        Column(
            modifier = Modifier.padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            Text("Demo Card", style = MaterialTheme.typography.headlineMedium)
            var isChecked by remember { mutableStateOf(true) }
            Row(verticalAlignment = Alignment.CenterVertically) {
                Text("Demo Switch", Modifier.weight(1f), style = MaterialTheme.typography.bodyLarge)
                Switch(checked = isChecked, onCheckedChange = { isChecked = it })
            }
            Row(verticalAlignment = Alignment.CenterVertically) {
                Icon(Icons.Filled.Person, "Icon", Modifier.size(32.dp))
                Spacer(Modifier.width(8.dp))
                Text("Demo Icon", style = MaterialTheme.typography.bodyLarge)
            }
            Row(
                Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                Box(
                    Modifier
                        .width(100.dp)
                        .weight(1f)
                        .height(80.dp)
                        .background(Color.Blue)
                )
                Box(
                    Modifier
                        .width(100.dp)
                        .weight(1f)
                        .height(80.dp)
                        .background(Color.Red)
                )
            }
            Text(
                "Demo Text: Lorem ipsum dolor sit amet, consectetur adipiscing elit," +
                    " sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
                style = MaterialTheme.typography.bodyMedium,
                textAlign = TextAlign.Justify
            )
        }
    }
}

Советы и рекомендации

Чтобы сделать приложение более удобным и доступным, следуйте рекомендациям ниже.

  • Добавьте элементы управления масштабом, не требующие жестов. Некоторым пользователям может быть трудно выполнять жесты. Чтобы помочь таким пользователям, добавьте альтернативный способ настройки или сброса масштаба, не требующий жестов.
  • Учитывайте разные масштабы. Протестируйте интерфейс с учетом масштабирования в приложении и системных настроек шрифта и экрана. Убедитесь, что макеты приложения адаптируются правильно, без сбоев, наложения или скрытия контента. Подробнее о том, как создавать адаптивные макеты…