輕觸並按下

許多可組合函式內建輕觸或點擊機制支援,且會包含 onClick lambda。舉例來說,您可以建立可點選的 Surface, 包括所有適合與介面互動的 Material Design 行為:

Surface(onClick = { /* handle click */ }) {
    Text("Click me!", Modifier.padding(24.dp))
}

但使用者並非唯一可以與可組合項互動的方式。這個頁面 著重於涉及單一指標的手勢,其中 這個指標對於處理該事件而言並不重要。下列 表列出以下類型的手勢:

手勢

說明

輕觸 (或點選)

指標先向下再向上移動

輕觸兩下

指標向下、上、下、上

長按

指標向下移動,並停留較長時間

新聞中心

指標向下移動

回應輕觸或點選動作

clickable 是常用的修飾符,可讓可組合函式做出反應 輕觸或點擊。這個修飾符也新增其他功能,例如: 聚焦、滑鼠和觸控筆懸停,以及自訂視覺指示 已按下。修飾符會回應「點擊」第一個字詞 使用滑鼠或手指點擊事件 也能透過鍵盤輸入或 以及無障礙服務

假設圖片格狀顯示,當使用者以全螢幕顯示圖片時 此時按一下:

您可以將 clickable 修飾符新增至格線中的每個項目,以實作此項目 行為:

@Composable
private fun ImageGrid(photos: List<Photo>) {
    var activePhotoId by rememberSaveable { mutableStateOf<Int?>(null) }
    LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
        items(photos, { it.id }) { photo ->
            ImageItem(
                photo,
                Modifier.clickable { activePhotoId = photo.id }
            )
        }
    }
    if (activePhotoId != null) {
        FullScreenImage(
            photo = photos.first { it.id == activePhotoId },
            onDismiss = { activePhotoId = null }
        )
    }
}

clickable 修飾符也會新增其他行為:

  • interactionSourceindication,預設會在發生下列情況時繪製漣漪效果 使用者輕觸可組合項瞭解如何在處理使用者 互動網頁。
  • 將 語意資訊
  • 允許使用者聚焦及按下鍵盤或搖桿互動 Enter 或 D-Pad 的中心要互動。
  • 將元素設為可懸停,藉此回應滑鼠或觸控筆懸停的動作 上面寫著

長按即可顯示內容相關內容選單

combinedClickable 可讓你在以下位置新增輕觸兩下或長按行為 以及正常的點擊行為您可以使用 combinedClickable 來顯示 使用者輕觸並按住格狀圖片時的內容選單:

var contextMenuPhotoId by rememberSaveable { mutableStateOf<Int?>(null) }
val haptics = LocalHapticFeedback.current
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
    items(photos, { it.id }) { photo ->
        ImageItem(
            photo,
            Modifier
                .combinedClickable(
                    onClick = { activePhotoId = photo.id },
                    onLongClick = {
                        haptics.performHapticFeedback(HapticFeedbackType.LongPress)
                        contextMenuPhotoId = photo.id
                    },
                    onLongClickLabel = stringResource(R.string.open_context_menu)
                )
        )
    }
}
if (contextMenuPhotoId != null) {
    PhotoActionsSheet(
        photo = photos.first { it.id == contextMenuPhotoId },
        onDismissSheet = { contextMenuPhotoId = null }
    )
}

最佳做法是指定當使用者出現觸覺回饋時 會導致長按元素,因此程式碼片段包含 performHapticFeedback 叫用。

輕觸 scrim 以關閉可組合函式

在上例中,clickablecombinedClickable 新增實用的資訊 組合功能會顯示有關互動的視覺指標 並支援焦點、鍵盤和無障礙功能。但 這種額外行為不一定是最理想的情況

我們來看看圖片詳細資料畫面。背景須為半透明 而使用者應該要能輕觸該背景來關閉詳細資料畫面:

在此情況下,該背景不應 互動時不應回應、並非可聚焦 對鍵盤和無障礙功能事件的回應 可組合函式。您不需要嘗試調整 clickable 行為,而是可以 較低抽象層級,並直接使用 pointerInput 修飾符 與 detectTapGestures 方法結合:

@Composable
private fun Scrim(onClose: () -> Unit, modifier: Modifier = Modifier) {
    val strClose = stringResource(R.string.close)
    Box(
        modifier
            // handle pointer input
            .pointerInput(onClose) { detectTapGestures { onClose() } }
            // handle accessibility services
            .semantics(mergeDescendants = true) {
                contentDescription = strClose
                onClick {
                    onClose()
                    true
                }
            }
            // handle physical keyboard input
            .onKeyEvent {
                if (it.key == Key.Escape) {
                    onClose()
                    true
                } else {
                    false
                }
            }
            // draw scrim
            .background(Color.DarkGray.copy(alpha = 0.75f))
    )
}

作為 pointerInput 修飾符的鍵,您必須傳遞 onClose lambda。這個 會自動重新執行 lambda,確保呼叫正確的回呼 當使用者輕觸剪刀時

輕觸兩下即可縮放

有時候 clickablecombinedClickable 未提供足夠的資訊 能以正確方式回應互動舉例來說,可組合函式 必須存取可組合項邊界內的位置 。

我們再看一次圖片詳細資料畫面。最佳做法是 輕觸兩下就能放大圖片:

如影片所示,放大功能會在輕觸位置的四周放大 活動。如果放大圖片左側部分,結果也會不同 和右側部分我們可以搭配使用 pointerInput 修飾符 透過 detectTapGestures 將輕觸位置納入我們的 計算方式:

var zoomed by remember { mutableStateOf(false) }
var zoomOffset by remember { mutableStateOf(Offset.Zero) }
Image(
    painter = rememberAsyncImagePainter(model = photo.highResUrl),
    contentDescription = null,
    modifier = modifier
        .pointerInput(Unit) {
            detectTapGestures(
                onDoubleTap = { tapOffset ->
                    zoomOffset = if (zoomed) Offset.Zero else
                        calculateOffset(tapOffset, size)
                    zoomed = !zoomed
                }
            )
        }
        .graphicsLayer {
            scaleX = if (zoomed) 2f else 1f
            scaleY = if (zoomed) 2f else 1f
            translationX = zoomOffset.x
            translationY = zoomOffset.y
        }
)