지난번의 글에서 Jetpack Compose에 관한 내용을 썼을 때 Jetpack Compose가 (개인적으로 보는) 기본적인 컴포넌트들을 빼먹고 있다고 했는데, 그 중 하나는 툴팁입니다.
그 때에는 내장된 툴팁을 표시할 수 있는 composable가 없었고, 인터넷上에서 여러가지 대안적인 솔루션이 있었습니다. 그러나 문제는 Jetpack Compose가 새 버전을 출시하면 그러한 솔루션이 깨지는 경우도 있었습니다. 따라서 이는理想的하지 않았고, 커뮤니티는 향후 언젠가 툴팁을 지원할 것이라 기대하고 있었습니다.
이제 Compose Material 3의 버전 1.1.0부터 내장된 툴팁 지원이 있으니 좋네요. 👏
이것 자체는 좋은데, 그 버전이 출시된 이후로 더는 일년이 지났습니다. 그리고 후속 버전들에서 툴팁 관련 API는 크게 변경되었습니다.
变迁로그를 살펴보면 공개된 API와 내부 API가 어떻게 변경되었는지 알 수 있습니다. 따라서 이 글을 읽을 때는 툴팁 관련 내용이 여전히 ExperimentalMaterial3Api::class로 표시되어 있음을 유의하세요.
❗️ 이 글에 사용된 Material 3의 버전은 2024년 3월 6일에 출시된 1.2.1입니다.
툴팁 유형
이제 다음 두 가지 유형의 툴팁을 지원합니다:
-
단순 툴팁
-
리치 미디어 툴팁
플레인 툴팁
툴팁의 첫 번째 유형은 아이콘 버튼에 대한 정보를 제공하는 데 사용할 수 있으며, 그렇지 않으면 명확하지 않을 수 있습니다. 예를 들어, 플레인 툴팁을 사용하여 사용자에게 아이콘 버튼이 무엇을 나타내는지 알려줄 수 있습니다.
어플리케이션에 툴팁을 추가하려면 TooltipBox 컴포자블을 사용합니다. 이 컴포자블은 several arguments를 받습니다:
fun TooltipBox(
positionProvider: PopupPositionProvider,
tooltip: @Composable TooltipScope.() -> Unit,
state: TooltipState,
modifier: Modifier = Modifier,
focusable: Boolean = true,
enableUserInput: Boolean = true,
content: @Composable () -> Unit,
)
이전에 Composables를 사용했으면 일부는 익숙할 것입니다. 여기에서 특정 사용 사례에 해당하는 것을 강조하겠습니다:
-
positionProvider – PopupPositionProvider 형식으로, 툴팁의 위치를 계산하는 데 사용됩니다.
-
tooltip – 툴팁이 어떻게 보일지 UI를 디자인할 수 있는 곳입니다.
-
state – 특정 Tooltip 인스턴스와 관련된 상태를 저장합니다. 툴팁을 표시하거나 제거하는 메서드를 노출하며, 인스턴스를 생성할 때 툴팁이 지속적으로 표시되는지 여부를 선언할 수 있습니다 (즉, 사용자가 툴팁 외부를 클릭하는 동작을 수행할 때까지 화면에 표시되는지 여부).
-
UI content – This is the UI that the tooltip will display above/below.
다음은 BasicTooltipBox을(를) 모든 pertinent arguments를 filled in하여 인스턴트iate하는 예제입니다:
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable
fun BasicTooltip() {
val tooltipPosition = TooltipDefaults.rememberPlainTooltipPositionProvider()
val tooltipState = rememberBasicTooltipState(isPersistent = false)
BasicTooltipBox(positionProvider = tooltipPosition,
tooltip = { Text("Hello World") } ,
state = tooltipState) {
IconButton(onClick = { }) {
Icon(imageVector = Icons.Filled.Favorite,
contentDescription = "Your icon's description")
}
}
}
Jetpack Compose는 TooltipDefaults라는 내장 class가 있습니다. 이 class를 사용하여 TooltipBox의 구성요소를 인스턴트iate하는 것을 도울 수 있습니다. 例如, TooltipDefaults.rememberPlainTooltipPositionProvider를 사용하여 anchor element와 관계의 correct position을 구할 수 있습니다.
Rich Tooltip
Rich media tooltip은 plain tooltip보다 더 많은 공간을 사용하며 아이콘 버튼의 기능에 대한 더 많은 내용을 제공할 수 있습니다. Tooltip이 보여지는 때, 추가적인 설명 또는 정의를 제공하기 위해 buttons and links를 추가할 수 있습니다.
Plain tooltip과 유사하게 TooltipBox 안에서 RichTooltip composable를 사용하여 인스턴트iate됩니다.
TooltipBox(positionProvider = tooltipPosition,
tooltip = {
RichTooltip(
title = { Text("RichTooltip") },
caretSize = caretSize,
action = {
TextButton(onClick = {
scope.launch {
tooltipState.dismiss()
tooltipState.onDispose()
}
}) {
Text("Dismiss")
}
}
) {
Text("This is where a description would go.")
}
},
state = tooltipState) {
IconButton(onClick = {
/* Icon button's click event */
}) {
Icon(imageVector = tooltipIcon,
contentDescription = "Your icon's description",
tint = iconColor)
}
}
Rich tooltip에 대해 주의 사항이 몇 가지 있습니다:
-
Rich tooltip은 caret 지원합니다.
-
툴팁에 액션(즉, 버튼)을 추가하여用户提供更多信息的选项。
-
툴팁을 닫기 위한 논리를 추가할 수 있습니다。
Edge Cases
툴팁 상태를 지속적으로 표시할 때, 사용자가 툴팁을 보여주는 UI와 상호작용하면, 스크린의 다른 곳을 누르기 전까지 툴팁이 보이게 됩니다。
위에서 본 Rich 툴팁 예제를 보신다면, 툴팁을 클릭하면 닫히는 버튼을 추가했음을 알 수 있습니다。
이 버튼을 사용자가 누를 때 문제가 발생합니다. 툴팁에서 닫기 동작을 수행하므로, 사용자가 이 툴팁을 호출하는 UI 항목에 대한 다른 롱 프레스를 수행하고자 할 때, 툴팁이 다시 표시되지 않습니다. 이는 툴팁이 닫혔을 때의 상태가 지속적으로 유지됨을 의미합니다. 그렇다면 어떻게 해결할까요?
툴팁의 상태를 “리셋”하기 위해, 툴팁 상태를 표시하는 onDispose 메서드를 호출해야 합니다. 이렇게 하면 툴팁 상태가 초기화되고, 사용자가 UI 항목에 롱 프레스를 수행하면 툴팁이 다시 표시됩니다。
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RichTooltip() {
val tooltipPosition = TooltipDefaults.rememberRichTooltipPositionProvider()
val tooltipState = rememberTooltipState(isPersistent = true)
val scope = rememberCoroutineScope()
TooltipBox(positionProvider = tooltipPosition,
tooltip = {
RichTooltip(
title = { Text("RichTooltip") },
caretSize = TooltipDefaults.caretSize,
action = {
TextButton(onClick = {
scope.launch {
tooltipState.dismiss()
tooltipState.onDispose() /// <---- HERE
}
}) {
Text("Dismiss")
}
}
) {
}
},
state = tooltipState) {
IconButton(onClick = { }) {
Icon(imageVector = Icons.Filled.Call, contentDescription = "Your icon's description")
}
}
}
유저의 동작에 따라 dismiss 메서드를 호출하는 대신 툴팁 바깥쪽을 클릭하여 툴팁이 사라지는 상황에서 툴팁 상태가 초기화되지 않는 다른 시나리오가 있습니다. 이렇게 툴팁이 사라지게 되면 배경에서 dismiss 메서드가 호출되고 툴팁 상태가.dismiss 상태로 설정됩니다. UI 요소를 길게 누르면 툴팁을 다시 볼 수 있지만, 아무런 결과도 없게 됩니다.
툴팁의 onDispose 메서드를 호출하는 로직이 트리거되지 않기 때문에, 툴팁 상태를 어떻게 초기화할 수 있을까요?
현재 이를 파악할 수 없었습니다. 이可能与 툴팁의 MutatorMutex와 관련이 있을 수 있습니다. 아마도即将到来的版本에서 이에 대한 API가 생길지도 모릅니다. 다른 툴팁이 화면에 있고 그것을 클릭하면 이전에 클릭한 툴팁의 상태가 초기화되는 것을 발견했습니다.
여기서 소개한 코드를 보고 싶으신가요? 이 GitHub 저장소
에 가시면 됩니다. 응용프로그램에서 툴팁을 보고 싶으신가요? 여기를 확인하세요.
참고자료
Source:
https://www.freecodecamp.org/news/how-to-use-tooltips-in-jetpack-compose/