В настольном проекте можно обрабатывать различные события мыши: щелчки, перемещения, прокрутку, а также вход и выход из области ввода.
Обработчики щелчков
Обработчики щелчков доступны как в Compose Multiplatform для Android, так и в Compose Multiplatform для настольных приложений, поэтому ваш код будет работать на обеих платформах. Например, вот как настроить простые обработчики щелчков с помощью модификаторов onClick, onDoubleClick и onLongClick:
Модификатор combinedClickable поддерживает только основную кнопку (левую кнопку мыши) и события касания. Если нужно обрабатывать кнопки по-разному, см. раздел Modifier.onClick.
Обработчики перемещения
Чтобы создать обработчик перемещения указателя, который меняет цвет фона окна в зависимости от положения указателя мыши, добавьте следующий код:
Compose Multiplatform для настольных приложений поддерживает обработчики входа указателя в область ввода и выхода из неё. Например, следующий код при наведении изменит стиль шрифта строки:
import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.onPointerEvent import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.singleWindowApplication @OptIn(ExperimentalComposeUiApi::class) fun main() = singleWindowApplication(title = "Mouse enter listeners") { Column( Modifier.background(Color.White), verticalArrangement = Arrangement.spacedBy(10.dp), ) { repeat(10) { index -> var active by remember { mutableStateOf(false) } Text(modifier = Modifier .fillMaxWidth() .background(color = if (active) Color.Green else Color.White) .onPointerEvent(PointerEventType.Enter) { active = true } .onPointerEvent(PointerEventType.Exit) { active = false }, fontSize = 30.sp, fontStyle = if (active) FontStyle.Italic else FontStyle.Normal, text = "Item $index", textAlign = TextAlign.Center ) } } }
Обработчики прокрутки
В следующем примере показано, как увеличивать или уменьшать отображаемое число в зависимости от направления прокрутки мыши:
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.onPointerEvent import androidx.compose.ui.unit.sp import androidx.compose.ui.window.singleWindowApplication @OptIn(ExperimentalComposeUiApi::class) fun main() = singleWindowApplication(title = "Mouse scroll listeners") { var number by remember { mutableFloatStateOf(0f) } Box( Modifier .fillMaxSize() .onPointerEvent(PointerEventType.Scroll) { number += it.changes.first().scrollDelta.y }, contentAlignment = Alignment.Center ) { Text("Scroll to change the number: $number", fontSize = 30.sp) } }
Экспериментальные обработчики onClick
Modifier.onClick предоставляет отдельные обратные вызовы для щелчков, двойных щелчков и длительных щелчков. Он обрабатывает только щелчки, вызванные событиями указателя, и изначально не обрабатывает события click специальных возможностей.
Для каждого onClick можно настроить целевые события указателя с помощью matcher: PointerMatcher и keyboardModifiers: PointerKeyboardModifiers.() -> Boolean:
matcher позволяет выбрать кнопку мыши, щелчок которой должен вызывать событие.
keyboardModifiers позволяет фильтровать события указателя, произошедшие при нажатой указанной клавише.
Также можно объединить несколько модификаторов onClick в цепочку, чтобы обрабатывать разные щелчки с разными условиями сопоставления и модификаторами клавиатуры. В отличие от clickable, onClick не имеет значений Modifier.indication и Modifier.semantics по умолчанию и не вызывает событие щелчка при нажатии Enter. При необходимости добавьте эти модификаторы отдельно. Чтобы обеспечить правильную передачу событий, сначала объявляйте наиболее общие обработчики onClick (с наименьшим количеством условий).
import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.PointerMatcher import androidx.compose.foundation.background import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.size import androidx.compose.foundation.onClick import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.PointerButton import androidx.compose.ui.input.pointer.isAltPressed import androidx.compose.ui.input.pointer.isShiftPressed import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.window.singleWindowApplication @OptIn(ExperimentalFoundationApi::class) fun main() = singleWindowApplication(title = "Mouse clicks") { Column { var topBoxText by remember { mutableStateOf("Click me\nusing LMB or LMB + Shift") } var topBoxCount by remember { mutableIntStateOf(0) } // No indication on interaction Box(modifier = Modifier .size(300.dp, 200.dp) .background(Color.LightGray) // The most generic click handler (without extra conditions) should be the first one .onClick { // Receives all left mouse button clicks except for when Shift is pressed println("Click with primary button") topBoxText = "LMB ${topBoxCount++}" }.onClick( keyboardModifiers = { isShiftPressed } // Accepts clicks only when Shift is pressed ) { // Receives all left mouse button clicks when Shift is pressed println("Click with primary button and shift pressed") topBoxCount++ topBoxText = "LMB + Shift ${topBoxCount++}" } ) { AnimatedContent( targetState = topBoxText, modifier = Modifier.align(Alignment.Center) ) { Text(text = it, textAlign = TextAlign.Center) } } var bottomBoxText by remember { mutableStateOf("Click me\nusing LMB or\nRMB + Alt") } var bottomBoxCount by remember { mutableStateOf(0) } val interactionSource = remember { MutableInteractionSource() } // With indication on interaction Box(modifier = Modifier .size(300.dp, 200.dp) .background(Color.Yellow) .onClick( enabled = true, interactionSource = interactionSource, matcher = PointerMatcher.mouse(PointerButton.Secondary), // Right mouse button keyboardModifiers = { isAltPressed }, // Accepts clicks only when Alt is pressed onLongClick = { // Optional bottomBoxText = "RMB Long Click + Alt ${bottomBoxCount++}" println("Long Click with secondary button and Alt pressed") }, onDoubleClick = { // Optional bottomBoxText = "RMB Double Click + Alt ${bottomBoxCount++}" println("Double Click with secondary button and Alt pressed") }, onClick = { bottomBoxText = "RMB Click + Alt ${bottomBoxCount++}" println("Click with secondary button and Alt pressed") } ) .onClick(interactionSource = interactionSource) { // Uses default parameters bottomBoxText = "LMB Click ${bottomBoxCount++}" println("Click with primary button (mouse left button)") } .indication(interactionSource, LocalIndication.current) ) { AnimatedContent( targetState = bottomBoxText, modifier = Modifier.align(Alignment.Center) ) { Text(text = it, textAlign = TextAlign.Center) } } } }
Экспериментальный модификатор onDrag
С помощью Modifier.onDrag можно указать указатель, который будет запускать перетаскивание, через matcher: PointerMatcher. Как и для onClick, можно объединить в цепочку несколько модификаторов onDrag.
Если клавиши могут менять поведение перетаскивания, можно также проверить состояние модификаторов клавиатуры с помощью LocalWindowInfo.current.keyboardModifier. Например, перемещать элемент обычным перетаскиванием, а копировать и вставлять его при перетаскивании с нажатой клавишей Ctrl.
В следующем примере показано, как обрабатывать события перетаскивания, вызванные левой и правой кнопками мыши, а также с использованием клавиатуры:
Обработка необработанных событий в общем коде с помощью pointerInput
В приведённых выше фрагментах используется функция Modifier.onPointerEvent — вспомогательная функция для подписки на определённый тип событий указателя. Это новый и более короткий вариант функции Modifier.pointerInput. Сейчас она является экспериментальной и доступна только для настольных приложений, поэтому использовать её в общем коде нельзя.
Если нужно подписаться на события в общем коде или использовать стабильный API, можно воспользоваться функцией Modifier.pointerInput:
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.Text import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.window.singleWindowApplication fun main() = singleWindowApplication(title = "Raw events via Modifier.pointerInput") { val list = remember { mutableStateListOf<String>() } Column( Modifier .fillMaxSize() .pointerInput(Unit) { awaitPointerEventScope { while (true) { val event = awaitPointerEvent() val position = event.changes.first().position // On each relayout, Compose will send a synthetic Move event, // so we skip it to avoid event spam if (event.type != PointerEventType.Move) { list.add(0, "${event.type} $position") } } } }, ) { for (item in list.take(20)) { Text(item) } } }