今天來研究如何處理使用者互動行為,這個專案會用到 click
、drag
等動作,讓記事進入編輯狀態,還能透過拖曳來改變 list 的順序。所以首先要先知道如何取得click
、drag
的狀態。
使用者和元件的互動最常用的就是 click,在 compose 中可以從 Modifier.clickable
來判斷使用者是否點擊了按鈕,並且執行在 lambda 中的代碼。直接使用 clickable 的好處是不需要知道點擊事件是由鍵盤操作還是畫面點擊執行的、也不需要知道背後的實作原理就能直接使用。
然而本專案需要知道drag
這個動作,因此要用到Interaction
。當使用者和介面互動時,系統會產生多個Interaction
事件來表達使用者行為。如果要從 Composable 中取得 Interaction
就可以觀察參數中有沒有InteractionSource
。
這次用的 Card 就有 interactionSource
,我們可以把預設值 remember { MutableInteractionSource() }
拿來使用。
@ExperimentalMaterial3Api
@Composable
fun Card(
onClick: () -> Unit,
//...
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
//...
) {
//...
}
首先建立變數 interactionSource
, 其值就是 remember { MutableInteractionSource() }
。
再來用 interactionSource
的 function 來獲取手勢的狀態,總共有這幾種
collectIsPressedAsState()
:是否按著元件
collectIsDraggedAsState()
:是否拖曳著元件
collectIsFocusedAsState()
:是否焦點在元件上
collectIsHoveredAsState()
:是否按著元件不動(懸停)
回傳型態是 State<Boolean>
。
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen() {
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
val isDragged by interactionSource.collectIsDraggedAsState()
val isFocused by interactionSource.collectIsFocusedAsState()
val isHovered by interactionSource.collectIsHoveredAsState()
//...
這裡我會遇到一個問題,那就是我同時監聽長按和拖曳的狀態,Compose 會執行大量的重複動作,而且不能確保正確的互動順,這時就要改成InteractionSource
。
InteractionSource
用的是 Flow API,要取得最新的資料就需要用到 collect
。
在 collect 中使用 when 確保互動是依照一定順序判斷使用者的互動,並且把正在進行的互動加到 interactions
list 中 。
val interactionSource = remember { MutableInteractionSource() }
val interactions = remember { mutableStateListOf<Interaction>() }
LaunchedEffect(interactionSource) {
interactionSource.interactions.collect { interaction ->
when (interaction) {
is PressInteraction.Press -> {
interactions.add(interaction)
}
is DragInteraction.Start -> {
interactions.add(interaction)
}
}
}
}
除了加入之外,當使用者放開元件,也要從 list 中移除狀態。
val interactions = remember { mutableStateListOf<Interaction>() }
LaunchedEffect(interactionSource) {
interactionSource.interactions.collect { interaction ->
when (interaction) {
is PressInteraction.Press -> {
interactions.add(interaction)
}
is PressInteraction.Release -> {
interactions.remove(interaction.press)
}
is PressInteraction.Cancel -> {
interactions.remove(interaction.press)
}
is DragInteraction.Start -> {
interactions.add(interaction)
}
is DragInteraction.Stop -> {
interactions.remove(interaction.start)
}
is DragInteraction.Cancel -> {
interactions.remove(interaction.start)
}
}
}
}
整理好後我就可以 interactions
檢查是否有值,就能知道目元件正在 Pressed
或 Dragged
。
val isPressedOrDragged = interactions.isNotEmpty()
因為動作狀態存在 list 的關係,還能看出動作的順序, list 的最後一項就是最新的動作。
val lastInteraction = when (interactions.lastOrNull()) {
is DragInteraction.Start -> "Dragged"
is PressInteraction.Press -> "Pressed"
else -> "No state"
}
未完待續...
今日運動
腳還在酸 休息