许多可组合项都内置了对点按或点击的支持,并包含 onClick lambda。例如,您可以创建一个可点击的 Surface,其中包含适合与 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 修饰符还会添加其他行为:
interactionSource和indication,默认情况下,当用户点按可组合项时,它们会绘制涟漪效果。如需了解如何自定义这些设置,请参阅处理用户互动页面。- 通过设置语义信息,允许无障碍服务与元素进行交互。
- 支持键盘或操纵杆互动,允许聚焦并按
Enter或方向键中心进行互动。 - 使元素可悬停,以便它响应鼠标或触控笔悬停在它上方。
长按即可显示上下文菜单
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 关闭可组合项
在上面的示例中,clickable 和 combinedClickable 为可组合项添加了实用功能。它们会在互动时显示视觉指示,响应悬停操作,并包含焦点、键盘和无障碍支持。但这种额外行为并不总是可取的。
我们来看看图片详情界面。背景应为半透明,用户应能点按该背景来关闭详情界面:
在这种情况下,该背景不应在互动时有任何视觉指示,不应响应悬停,不应可聚焦,并且其对键盘和无障碍事件的响应与典型可组合项不同。您可以改用较低的抽象级别,直接将 pointerInput 修饰符与 detectTapGestures 方法结合使用,而不是尝试调整 clickable 行为:
@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)) ) }
您需要将 onClose lambda 作为 pointerInput 修饰符的键进行传递。这会自动重新执行 lambda,确保在用户点按纱罩时调用正确的回调。
点按两次即可缩放
有时,clickable 和 combinedClickable 包含的信息不足,无法以正确的方式响应互动。例如,可组合项可能需要访问发生互动的位置(在可组合项的边界内)。
我们再来看一下图片详情屏幕。最佳做法是让用户能够通过点按两次来放大图片:
如您在视频中所见,放大操作发生在点按事件的位置周围。放大图片的左侧部分和右侧部分,结果会有所不同。我们可以将 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 } )
为你推荐
- 注意:当 JavaScript 处于关闭状态时,系统会显示链接文字
- 了解手势
- Compose 中的 Material Design 2
- Kotlin 对 Jetpack Compose 的支持