iT邦幫忙

2022 iThome 鐵人賽

DAY 19
1

前言


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

互動 (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 檢查是否有值,就能知道目元件正在 Pressed 或 Dragged 。

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 做出重訓紀錄APP 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

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

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

我要留言

立即登入留言