鼠标事件涵盖的内容
在这里, 我们将看到如何在 Compose for Desktop 的组件上安装鼠标事件监听器.
鼠标事件监听器
点击监听器
点击监听器在安卓Compose 和Compose for Desktop中都是可用的, 所以像这样的代码在两个平台上都可以使用:
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight 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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.sp import androidx.compose.ui.window.singleWindowApplication fun main() = singleWindowApplication { var count by remember { mutableStateOf(0) } Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxWidth()) { var text by remember { mutableStateOf("Click magenta box!") } Column { @OptIn(ExperimentalFoundationApi::class) Box( modifier = Modifier .background(Color.Magenta) .fillMaxWidth(0.7f) .fillMaxHeight(0.2f) .combinedClickable( onClick = { text = "Click! ${count++}" }, onDoubleClick = { text = "Double click! ${count++}" }, onLongClick = { text = "Long click! ${count++}" } ) ) Text(text = text, fontSize = 40.sp) } } }
combinedClickable只支持主按钮(鼠标左键)和触摸事件. 如果需要以不同方式处理其他按钮, 请看下面的Modifier.onClick(注意: Modifier.onClick目前只适用于Desktop-JVM平台).
鼠标移动监听器
让我们创建一个窗口, 并在其上安装一个指针移动监听器, 根据鼠标指针的位置来改变背景颜色:
import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf 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.graphics.Color import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.onPointerEvent import androidx.compose.ui.window.singleWindowApplication @OptIn(ExperimentalComposeUiApi::class) fun main() = singleWindowApplication { var color by remember { mutableStateOf(Color(0, 0, 0)) } Box( modifier = Modifier .wrapContentSize(Alignment.Center) .fillMaxSize() .background(color = color) .onPointerEvent(PointerEventType.Move) { val position = it.changes.first().position color = Color(position.x.toInt() % 256, position.y.toInt() % 256, 0) } ) }
注意, onPointerEvent是实验性的, 将来可能会被改变. 对于更多稳定的API请看Modifier.pointerInput.
鼠标进入监听器
Compose for Desktop 也支持指针的进入和退出处理, 像这样:
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.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.singleWindowApplication @OptIn(ExperimentalComposeUiApi::class) fun main() = singleWindowApplication { 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" ) } } }
注意, onPointerEvent是实验性的, 将来可能会被改变. 对于更多稳定的API请看Modifier.pointerInput.
鼠标滚动监听器
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.mutableStateOf 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 { var number by remember { mutableStateOf(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) } }
注意, onPointerEvent是实验性的, 将来可能会被改变. 对于更多稳定的API请看Modifier.pointerInput.
与Swing的交互操作
Compose for Desktop内部使用Swing, 并允许访问原始AWT事件:
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.mutableStateOf 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.awt.awtEventOrNull import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.onPointerEvent import androidx.compose.ui.window.singleWindowApplication @OptIn(ExperimentalComposeUiApi::class) fun main() = singleWindowApplication { var text by remember { mutableStateOf("") } Box( Modifier .fillMaxSize() .onPointerEvent(PointerEventType.Press) { text = it.awtEventOrNull?.locationOnScreen?.toString().orEmpty() }, contentAlignment = Alignment.Center ) { Text(text) } }
注意, onPointerEvent是实验性的, 将来可能会被改变. 对于更多稳定的API请看Modifier.pointerInput.
在commonMain中通过Modifier.pointerInput监听原生事件
在上面的片段中, 我们使用了Modifier.onPointerEvent, 它是一个辅助函数, 用于订阅某种类型的指针事件. 它是Modifier.pointerInput的一个简短变体. 目前, 它是实验性的, 并且只在桌面上使用(你不能在普通主代码中使用它). 如果你需要在commonMain中订阅事件或者你需要稳定的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 { val list = remember { mutableStateListOf() } Column( Modifier .fillMaxSize() .pointerInput(Unit) { awaitPointerEventScope { while (true) { val event = awaitPointerEvent() val position = event.changes.first().position // on every relayout Compose will send 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) } } }
新的实验性onClick处理(仅适用于Desktop-JVM平台)
Modifier.onClick为点击, 双击, 长按提供独立的回调. 它只处理源于指针事件的点击, 并且可访问性点击事件不被处理.
每个onClick可以被配置为针对特定的指针事件(使用matcher: PointerMatcher和keyboardModifiers: PointerKeyboardModifiers.() -> Boolean). matcher可以被指定来选择什么鼠标按钮应该触发点击. keyboardModifiers允许过滤有指定keyboardModifiers按下的指针事件.
多个onClick修改器可以被连锁, 以处理不同条件的点击(匹配器和键盘修改器). 与clickable不同, onClick没有默认的Modifier.indication, Modifier.semantics, 当Enter按下时, 它不会触发一个点击事件. 如果有必要, 这些修改器需要单独添加. 最通用的(条件最少的)onClick处理程序应该在其他之前声明, 以确保事件传播的正确性.
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.ExperimentalAnimationApi 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.ExperimentalComposeUiApi 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, ExperimentalAnimationApi::class) fun main() = singleWindowApplication { Column { var topBoxText by remember { mutableStateOf("Click me\nusing LMB or LMB + Shift") } var topBoxCount by remember { mutableStateOf(0) } // No indication on interaction Box(modifier = Modifier.size(200.dp, 100.dp).background(Color.Blue) // the most generic click handler (without extra conditions) should be the first one .onClick { // it will receive all LMB clicks except when Shift is pressed println("Click with primary button") topBoxText = "LMB ${topBoxCount++}" }.onClick( keyboardModifiers = { isShiftPressed } // accept clicks only when Shift pressed ) { // it will receive all LMB 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(200.dp, 100.dp).background(Color.Yellow) .onClick( enabled = true, interactionSource = interactionSource, matcher = PointerMatcher.mouse(PointerButton.Secondary), // Right Mouse Button keyboardModifiers = { isAltPressed }, // accept clicks only when Alt 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) { // use 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修改器(仅适用于Desktop-JVM平台)
Modifier.onDrag allows for configuration of the pointer that should trigger the drag (see matcher: PointerMatcher).
Many onDrag modifiers can be chained together.The example below also shows how to access the state of keyboard modifiers (via LocalWindowInfo.current.keyboardModifier) for cases when keyboard modifiers can alter the behaviour of the drag (for example: move an item if we perform a simple drag; or copy/paste an item if dragged with Ctrl pressed)
Modifier.onDrag允许配置应触发拖动的指针(见matcher: PointerMatcher). 许多onDrag修改器可以被连锁起来.
下面的例子还显示了如何访问键盘修改器的状态(通过LocalWindowInfo.current.keyboardModifier), 以应对键盘修改器可以改变拖动行为的情况(例如: 如果我们执行简单的拖动, 则移动一个项目; 如果按住Ctrl拖动, 则复制/粘贴一个项目)
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.PointerMatcher import androidx.compose.foundation.background import androidx.compose.foundation.gestures.onDrag import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size 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.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.PointerButton import androidx.compose.ui.input.pointer.isCtrlPressed import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.window.singleWindowApplication @OptIn(ExperimentalFoundationApi::class) fun main() = singleWindowApplication { val windowInfo = LocalWindowInfo.current Column { var topBoxOffset by remember { mutableStateOf(Offset(0f, 0f)) } Box(modifier = Modifier.offset { IntOffset(topBoxOffset.x.toInt(), topBoxOffset.y.toInt()) }.size(100.dp) .background(Color.Green) .onDrag { // all default: enabled = true, matcher = PointerMatcher.Primary (left mouse button) topBoxOffset += it } ) { Text(text = "Drag with LMB", modifier = Modifier.align(Alignment.Center)) } var bottomBoxOffset by remember { mutableStateOf(Offset(0f, 0f)) } Box(modifier = Modifier.offset { IntOffset(bottomBoxOffset.x.toInt(), bottomBoxOffset.y.toInt()) }.size(100.dp) .background(Color.LightGray) .onDrag( enabled = true, matcher = PointerMatcher.mouse(PointerButton.Secondary), // right mouse button onDragStart = { println("Gray Box: drag start") }, onDragEnd = { println("Gray Box: drag end") } ) { val keyboardModifiers = windowInfo.keyboardModifiers bottomBoxOffset += if (keyboardModifiers.isCtrlPressed) it * 2f else it } ) { Text(text = "Drag with RMB,\ntry with CTRL", modifier = Modifier.align(Alignment.Center)) } } }
也有非修改器方式来处理拖拽, 就是用suspend fun PointerInputScope.detectDragGestures:
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.PointerMatcher import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size 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.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.window.singleWindowApplication @OptIn(ExperimentalFoundationApi::class) fun main() = singleWindowApplication { var topBoxOffset by remember { mutableStateOf(Offset(0f, 0f)) } Box(modifier = Modifier.offset { IntOffset(topBoxOffset.x.toInt(), topBoxOffset.y.toInt()) }.size(100.dp) .background(Color.Green) .pointerInput(Unit) { detectDragGestures( matcher = PointerMatcher.Primary ) { topBoxOffset += it } } ) { Text(text = "Drag with LMB", modifier = Modifier.align(Alignment.Center)) } }
以上就是Compose for Desktop 鼠标事件示例demo的详细内容,更多关于Compose Desktop 鼠标事件的资料请关注脚本之家其它相关文章!