Compose?for?Desktop?鼠標(biāo)事件示例demo
鼠標(biāo)事件涵蓋的內(nèi)容
在這里, 我們將看到如何在 Compose for Desktop 的組件上安裝鼠標(biāo)事件監(jiān)聽器.
鼠標(biāo)事件監(jiān)聽器
點(diǎn)擊監(jiān)聽器
點(diǎn)擊監(jiān)聽器在安卓Compose 和Compose for Desktop中都是可用的, 所以像這樣的代碼在兩個(gè)平臺(tái)上都可以使用:
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只支持主按鈕(鼠標(biāo)左鍵)和觸摸事件. 如果需要以不同方式處理其他按鈕, 請(qǐng)看下面的Modifier.onClick(注意: Modifier.onClick目前只適用于Desktop-JVM平臺(tái)).
鼠標(biāo)移動(dòng)監(jiān)聽器
讓我們創(chuàng)建一個(gè)窗口, 并在其上安裝一個(gè)指針移動(dòng)監(jiān)聽器, 根據(jù)鼠標(biāo)指針的位置來改變背景顏色:
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是實(shí)驗(yàn)性的, 將來可能會(huì)被改變. 對(duì)于更多穩(wěn)定的API請(qǐng)看Modifier.pointerInput.

鼠標(biāo)進(jìn)入監(jiān)聽器
Compose for Desktop 也支持指針的進(jìn)入和退出處理, 像這樣:
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是實(shí)驗(yàn)性的, 將來可能會(huì)被改變. 對(duì)于更多穩(wěn)定的API請(qǐng)看Modifier.pointerInput.

鼠標(biāo)滾動(dòng)監(jiān)聽器
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是實(shí)驗(yàn)性的, 將來可能會(huì)被改變. 對(duì)于更多穩(wěn)定的API請(qǐng)看Modifier.pointerInput.
與Swing的交互操作
Compose for Desktop內(nèi)部使用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是實(shí)驗(yàn)性的, 將來可能會(huì)被改變. 對(duì)于更多穩(wěn)定的API請(qǐng)看Modifier.pointerInput.
在commonMain中通過Modifier.pointerInput監(jiān)聽原生事件
在上面的片段中, 我們使用了Modifier.onPointerEvent, 它是一個(gè)輔助函數(shù), 用于訂閱某種類型的指針事件. 它是Modifier.pointerInput的一個(gè)簡(jiǎn)短變體. 目前, 它是實(shí)驗(yàn)性的, 并且只在桌面上使用(你不能在普通主代碼中使用它). 如果你需要在commonMain中訂閱事件或者你需要穩(wěn)定的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<String>() }
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)
}
}
}
新的實(shí)驗(yàn)性onClick處理(僅適用于Desktop-JVM平臺(tái))
Modifier.onClick為點(diǎn)擊, 雙擊, 長(zhǎng)按提供獨(dú)立的回調(diào). 它只處理源于指針事件的點(diǎn)擊, 并且可訪問性點(diǎn)擊事件不被處理.
每個(gè)onClick可以被配置為針對(duì)特定的指針事件(使用matcher: PointerMatcher和keyboardModifiers: PointerKeyboardModifiers.() -> Boolean). matcher可以被指定來選擇什么鼠標(biāo)按鈕應(yīng)該觸發(fā)點(diǎn)擊. keyboardModifiers允許過濾有指定keyboardModifiers按下的指針事件.
多個(gè)onClick修改器可以被連鎖, 以處理不同條件的點(diǎn)擊(匹配器和鍵盤修改器). 與clickable不同, onClick沒有默認(rèn)的Modifier.indication, Modifier.semantics, 當(dāng)Enter按下時(shí), 它不會(huì)觸發(fā)一個(gè)點(diǎn)擊事件. 如果有必要, 這些修改器需要單獨(dú)添加. 最通用的(條件最少的)onClick處理程序應(yīng)該在其他之前聲明, 以確保事件傳播的正確性.
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)
}
}
}
}
新的實(shí)驗(yàn)性onDrag修改器(僅適用于Desktop-JVM平臺(tái))
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允許配置應(yīng)觸發(fā)拖動(dòng)的指針(見matcher: PointerMatcher). 許多onDrag修改器可以被連鎖起來.
下面的例子還顯示了如何訪問鍵盤修改器的狀態(tài)(通過LocalWindowInfo.current.keyboardModifier), 以應(yīng)對(duì)鍵盤修改器可以改變拖動(dòng)行為的情況(例如: 如果我們執(zhí)行簡(jiǎn)單的拖動(dòng), 則移動(dòng)一個(gè)項(xiàng)目; 如果按住Ctrl拖動(dòng), 則復(fù)制/粘貼一個(gè)項(xiàng)目)
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 鼠標(biāo)事件示例demo的詳細(xì)內(nèi)容,更多關(guān)于Compose Desktop 鼠標(biāo)事件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android里實(shí)現(xiàn)退出主程序的提示代碼
當(dāng)用戶選擇"確定",就退出當(dāng)前的對(duì)話框。其中,有個(gè)很重要的函數(shù),Activity.finish(),通過調(diào)用這個(gè)函數(shù),退出當(dāng)前運(yùn)行的整個(gè)Android程序2013-06-06
Android 中View.onDraw(Canvas canvas)的使用方法
這篇文章主要介紹了Android 中View.onDraw(Canvas canvas)的使用方法的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下2017-09-09
Android實(shí)現(xiàn)Activity水平和垂直滾動(dòng)條的方法
這篇文章主要介紹了Android實(shí)現(xiàn)Activity水平和垂直滾動(dòng)條的方法,涉及Activity的ScrollView設(shè)置相關(guān)技巧,需要的朋友可以參考下2016-07-07
Android中ListView的幾種常見的優(yōu)化方法總結(jié)
Android中的ListView應(yīng)該算是布局中幾種最常用的組件之一,本篇文章主要做了三種優(yōu)化總結(jié),有興趣的可以了解一下。2017-02-02
android kotlin集成WorkManager實(shí)現(xiàn)定時(shí)獲取數(shù)據(jù)的步驟
在Android中使用Kotlin集成WorkManager來實(shí)現(xiàn)定時(shí)獲取數(shù)據(jù)是一個(gè)很常見的需求,下面給大家分享android kotlin集成WorkManager實(shí)現(xiàn)定時(shí)獲取數(shù)據(jù)的步驟,感興趣的朋友跟隨小編一起看看吧2024-08-08
Android 出現(xiàn)問題Installation error: INSTALL_FAILED_CONFLICTING_P
這篇文章主要介紹了Android 出現(xiàn)問題Installation error: INSTALL_FAILED_CONFLICTING_PROVIDER解決辦法的相關(guān)資料,需要的朋友可以參考下2016-12-12
Android基于Aidl的跨進(jìn)程間雙向通信管理中心
這篇文章主要為大家詳細(xì)介紹了Android基于Aidl的跨進(jìn)程間雙向通信管理中心,類似于聊天室,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11
Android如何使用ViewPager2實(shí)現(xiàn)頁面滑動(dòng)切換效果
這篇文章主要給大家介紹了關(guān)于Android如何使用ViewPager2實(shí)現(xiàn)頁面滑動(dòng)切換效果的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-02-02
android獲得當(dāng)前view在屏幕中坐標(biāo)的方法
這篇文章主要介紹了android獲得當(dāng)前view在屏幕中坐標(biāo)的方法,涉及Android針對(duì)view坐標(biāo)相關(guān)屬性的使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10

