많은 컴포저블에는 탭 또는 클릭에 대한 기본 지원이 있으며 onClick
람다를 포함합니다. 예를 들어 노출 영역과의 상호작용에 적합한 모든 Material Design 동작을 포함하는 클릭 가능한 Surface
를 만들 수 있습니다.
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
또는 D-패드 중앙을 눌러 상호작용할 수 있도록 하여 키보드 또는 조이스틱 상호작용을 지원합니다. - 마우스나 스타일러스가 요소 위로 마우스 오버할 때 요소가 반응하도록 요소를 마우스 오버할 수 있도록 만듭니다.
길게 눌러 컨텍스트 컨텍스트 메뉴 표시
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
호출이 포함되어 있습니다.
스림을 탭하여 컴포저블 닫기
위의 예에서 clickable
및 combinedClickable
는 컴포저블에 유용한 기능을 추가합니다. 상호작용에 시각적 표시를 보여주고 마우스 오버에 반응하며 포커스, 키보드, 접근성 지원을 포함합니다. 하지만 이 추가 동작이 항상 바람직한 것은 아닙니다.
이미지 세부정보 화면을 살펴보겠습니다. 배경은 반투명해야 하며 사용자가 배경을 탭하여 세부정보 화면을 닫을 수 있어야 합니다.
이 경우 백그라운드에는 상호작용에 대한 시각적 표시가 없어야 하며 마우스 오버에 응답해서는 안 되고 포커스를 받을 수 없어야 하며 키보드 및 접근성 이벤트에 대한 응답이 일반적인 컴포저블의 응답과 다릅니다. clickable
동작을 조정하는 대신 더 낮은 추상화 수준으로 드롭다운하고 detectTapGestures
메서드와 함께 pointerInput
수정자를 직접 사용할 수 있습니다.
@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
람다를 전달합니다. 이렇게 하면 람다가 자동으로 다시 실행되어 사용자가 스림을 탭할 때 올바른 콜백이 호출됩니다.
두 번 탭하여 확대
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
- Jetpack Compose용 Kotlin