iT邦幫忙

2022 iThome 鐵人賽

DAY 19
1

前言


今天來研究如何處理使用者互動行為,這個專案會用到 clickdrag 等動作,讓記事進入編輯狀態,還能透過拖曳來改變 list 的順序。所以首先要先知道如何取得clickdrag的狀態。

互動 (interactions)


使用者和元件的互動最常用的就是 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 檢查是否有值,就能知道目元件正在 PressedDragged

val isPressedOrDragged = interactions.isNotEmpty()

因為動作狀態存在 list 的關係,還能看出動作的順序, list 的最後一項就是最新的動作。

val lastInteraction = when (interactions.lastOrNull()) {
    is DragInteraction.Start -> "Dragged"
    is PressInteraction.Press -> "Pressed"
    else -> "No state"
}

未完待續...

參考


手勢
手勢互動

今日運動
腳還在酸 休息


上一篇
Day 18 實作 Grid Layout 2 調整卡片內容樣式
下一篇
Day 20 使用三方庫做出 LazyGrid 拖曳排序
系列文
今年一定減成功!Jetpack Compose 做出重訓紀錄APP30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
John Lu
iT邦新手 4 級 ‧ 2022-09-26 00:05:00

原來如此,一直都沒認真了解InteractionSource

我要留言

立即登入留言