Нажатие

Многие компоненты поддерживают нажатия и клики и включают лямбда-выражение onClick. Например, вы можете создать кликабельный элемент Surface, который включает все элементы Material Design, подходящие для взаимодействия с поверхностями:

Surface(onClick = { /* handle click */ }) {
    Text("Click me!", Modifier.padding(24.dp))
}

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

Жест

Описание

Нажмите

Указатель перемещается вниз, а затем вверх

Двойное касание

Указатель перемещается вниз, вверх, вниз, вверх

Нажать и удерживать

Указатель опускается и удерживается дольше

Пресса

Указатель опускается

реагировать на нажатие или клик;

clickable – часто используемый модификатор, который позволяет composable-функции реагировать на нажатия или клики. Этот модификатор также добавляет дополнительные функции, такие как поддержка фокуса, наведения указателя мыши и стилуса, а также настраиваемый визуальный индикатор при нажатии. Модификатор реагирует на "клики" в широком смысле этого слова – не только на нажатия мыши или пальца, но и на события клика при вводе с клавиатуры или использовании сервисов специальных возможностей.

Представьте себе сетку изображений, где изображение показывается в полноэкранном режиме, когда пользователь нажимает на него:

Чтобы реализовать такое поведение, добавьте модификатор clickable к каждому элементу сетки:

@Composable
private fun ImageGrid(photos: List<Photo>) {
    var activePhotoId by rememberSaveable { mutableStateOf<Int?>(null) }
    LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
        items(photos, { it.id }) { photo ->
            ImageItem(
                photo,
                Modifier.clickable { activePhotoId = photo.id }
            )
        }
    }
    if (activePhotoId != null) {
        FullScreenImage(
            photo = photos.first { it.id == activePhotoId },
            onDismiss = { activePhotoId = null }
        )
    }
}

Модификатор clickable также добавляет следующие возможности:

  • interactionSource и indication, которые по умолчанию рисуют волну, когда пользователь нажимает на компонент. Подробнее о том, как настроить эти параметры, рассказывается на странице Обработка действий пользователя.
  • Позволяет службам специальных возможностей взаимодействовать с элементом, задавая семантическую информацию.
  • Поддерживает взаимодействие с помощью клавиатуры или джойстика, позволяя выбирать элементы и нажимать Enter или центр d-pad.
  • Сделайте элемент интерактивным, чтобы он реагировал на наведение на него курсора мыши или стилуса.

Нажмите и удерживайте, чтобы открыть контекстное меню

combinedClickable позволяет добавить поведение при двойном нажатии или долгом нажатии в дополнение к обычному нажатию. Вы можете использовать combinedClickable, чтобы показывать контекстное меню, когда пользователь нажимает на изображение в сетке и удерживает его:

var contextMenuPhotoId by rememberSaveable { mutableStateOf<Int?>(null) }
val haptics = LocalHapticFeedback.current
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
    items(photos, { it.id }) { photo ->
        ImageItem(
            photo,
            Modifier
                .combinedClickable(
                    onClick = { activePhotoId = photo.id },
                    onLongClick = {
                        haptics.performHapticFeedback(HapticFeedbackType.LongPress)
                        contextMenuPhotoId = photo.id
                    },
                    onLongClickLabel = stringResource(R.string.open_context_menu)
                )
        )
    }
}
if (contextMenuPhotoId != null) {
    PhotoActionsSheet(
        photo = photos.first { it.id == contextMenuPhotoId },
        onDismissSheet = { contextMenuPhotoId = null }
    )
}

Рекомендуется добавлять виброотклик при долгом нажатии на элементы. Именно поэтому в фрагменте кода есть вызов performHapticFeedback.

Как закрыть компонент, нажав на экран

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

Давайте рассмотрим экран сведений об изображении. Фон должен быть полупрозрачным, и пользователь должен иметь возможность закрыть экран с подробной информацией, нажав на него:

В этом случае фон не должен иметь никаких визуальных индикаторов взаимодействия, не должен реагировать на наведение указателя, не должен быть фокусируемым и должен реагировать на события клавиатуры и специальные возможности иначе, чем обычная composable-функция. Вместо того чтобы пытаться адаптировать поведение clickable, можно перейти на более низкий уровень абстракции и напрямую использовать модификатор pointerInput в сочетании с методом detectTapGestures:

@Composable
private fun Scrim(onClose: () -> Unit, modifier: Modifier = Modifier) {
    val strClose = stringResource(R.string.close)
    Box(
        modifier
            // handle pointer input
            .pointerInput(onClose) { detectTapGestures { onClose() } }
            // handle accessibility services
            .semantics(mergeDescendants = true) {
                contentDescription = strClose
                onClick {
                    onClose()
                    true
                }
            }
            // handle physical keyboard input
            .onKeyEvent {
                if (it.key == Key.Escape) {
                    onClose()
                    true
                } else {
                    false
                }
            }
            // draw scrim
            .background(Color.DarkGray.copy(alpha = 0.75f))
    )
}

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

Нажмите дважды, чтобы увеличить

Иногда в clickable и combinedClickable недостаточно информации, чтобы правильно отреагировать на взаимодействие. Например, композируемым функциям может потребоваться доступ к позиции в границах композируемой функции, где произошло взаимодействие.

Давайте снова посмотрим на экран сведений об изображении. Рекомендуем сделать так, чтобы изображение можно было увеличить, дважды нажав на него:

Как видно на видео, увеличение происходит в месте нажатия. Результат будет разным, если увеличить левую или правую часть изображения. Мы можем использовать модификатор pointerInput в сочетании с detectTapGestures, чтобы включить позицию нажатия в расчет:

var zoomed by remember { mutableStateOf(false) }
var zoomOffset by remember { mutableStateOf(Offset.Zero) }
Image(
    painter = rememberAsyncImagePainter(model = photo.highResUrl),
    contentDescription = null,
    modifier = modifier
        .pointerInput(Unit) {
            detectTapGestures(
                onDoubleTap = { tapOffset ->
                    zoomOffset = if (zoomed) Offset.Zero else
                        calculateOffset(tapOffset, size)
                    zoomed = !zoomed
                }
            )
        }
        .graphicsLayer {
            scaleX = if (zoomed) 2f else 1f
            scaleY = if (zoomed) 2f else 1f
            translationX = zoomOffset.x
            translationY = zoomOffset.y
        }
)