아이콘 버튼

아이콘 버튼은 사용자가 취할 수 있는 작업을 표시합니다. 아이콘 버튼은 의미가 명확한 아이콘을 사용해야 하며 일반적으로 일반적이거나 자주 사용되는 작업을 나타냅니다.

아이콘 버튼에는 두 가지 유형이 있습니다.

  • 기본: 이 버튼은 메뉴나 검색과 같은 다른 요소를 열 수 있습니다.
  • 전환: 이 버튼은 '즐겨찾기' 또는 '북마크'와 같이 사용 설정하거나 사용 중지할 수 있는 바이너리 작업을 나타낼 수 있습니다.
다양한 아이콘 (설정, 더보기 등)이 있는 아이콘 버튼 5개 일부는 채워져 있어 선택되었음을 나타내고 일부는 윤곽선으로 표시되어 있습니다.
그림 1. 아이콘 버튼. 일부는 채워져 있고 (선택됨을 나타냄) 일부는 윤곽선만 있습니다.

API 노출 영역

IconButton 컴포저블을 사용하여 표준 아이콘 버튼을 구현합니다. 채워진 스타일, 채워진 톤 스타일, 윤곽선 스타일과 같은 다양한 시각적 스타일을 만들려면 각각 FilledIconButton, FilledTonalIconButton, OutlinedIconButton를 사용합니다.

IconButton의 주요 매개변수는 다음과 같습니다.

  • onClick: 사용자가 아이콘 버튼을 탭할 때 실행되는 람다 함수입니다.
  • enabled: 버튼의 사용 설정 상태를 제어하는 불리언입니다. false인 경우 버튼은 사용자 입력에 응답하지 않습니다.
  • content: 버튼 내의 컴포저블 콘텐츠로, 일반적으로 Icon입니다.

기본 예: 전환 버튼 아이콘

이 예시에서는 전환 아이콘 버튼을 구현하는 방법을 보여줍니다. 전환 아이콘 버튼은 선택 여부에 따라 모양이 변경됩니다.

@Preview
@Composable
fun ToggleIconButtonExample() {
    // isToggled initial value should be read from a view model or persistent storage.
    var isToggled by rememberSaveable { mutableStateOf(false) }

    IconButton(
        onClick = { isToggled = !isToggled }
    ) {
        Icon(
            painter = if (isToggled) painterResource(R.drawable.favorite_filled) else painterResource(R.drawable.favorite),
            contentDescription = if (isToggled) "Selected icon button" else "Unselected icon button."
        )
    }
}

코드에 관한 핵심 사항

  • ToggleIconButtonExample 컴포저블은 전환 가능한 IconButton을 정의합니다.
    • mutableStateOf(false)는 불리언 값(처음에는 false)을 보유하는 MutableState 객체를 만듭니다. 이렇게 하면 isToggled가 상태 홀더가 되므로 값이 변경될 때마다 Compose가 UI를 리컴포지션합니다.
    • rememberSaveable은 화면 회전과 같은 구성 변경 시에도 isToggled 상태가 유지되도록 합니다.
  • IconButton의 onClick 람다는 클릭 시 버튼의 동작을 정의하여 true와 false 간에 상태를 전환합니다.
  • Icon 컴포저블의 painter 매개변수는 isToggled 상태에 따라 다른 painterResource를 조건부로 로드합니다. 이렇게 하면 아이콘의 시각적 모양이 변경됩니다.
    • isToggled가 true이면 채워진 하트 드로어블을 로드합니다.
    • isToggled가 false이면 윤곽선이 있는 하트 드로어블을 로드합니다.
  • Icon의 contentDescription도 isToggled 상태에 따라 업데이트되어 적절한 접근성 정보를 제공합니다.

결과

다음 이미지는 이전 스니펫의 전환 아이콘 버튼이 선택되지 않은 상태를 보여줍니다.

선택되지 않은 상태 (채워지지 않음)의 즐겨찾기 전환 아이콘 버튼 (하트)
그림 2. 선택 해제된 상태의 '즐겨찾기' 전환 아이콘 버튼

고급 예시: 누를 때 반복되는 작업

이 섹션에서는 사용자가 클릭당 한 번만 트리거하는 것이 아니라 길게 누르는 동안 작업을 계속 트리거하는 아이콘 버튼을 만드는 방법을 보여줍니다.

@Composable
fun MomentaryIconButton(
    unselectedImage: Int,
    selectedImage: Int,
    contentDescription: String,
    modifier: Modifier = Modifier,
    stepDelay: Long = 100L, // Minimum value is 1L milliseconds.
    onClick: () -> Unit
) {
    val interactionSource = remember { MutableInteractionSource() }
    val isPressed by interactionSource.collectIsPressedAsState()
    val pressedListener by rememberUpdatedState(onClick)

    LaunchedEffect(isPressed) {
        while (isPressed) {
            delay(stepDelay.coerceIn(1L, Long.MAX_VALUE))
            pressedListener()
        }
    }

    IconButton(
        modifier = modifier,
        onClick = onClick,
        interactionSource = interactionSource
    ) {
        Icon(
            painter = if (isPressed) painterResource(id = selectedImage) else painterResource(id = unselectedImage),
            contentDescription = contentDescription,
        )
    }
}

코드에 관한 핵심 사항

  • MomentaryIconButton은 버튼을 누르지 않았을 때 아이콘의 드로어블 리소스 ID인 unselectedImage: Int와 버튼을 눌렀을 때 아이콘의 드로어블 리소스 ID인 selectedImage: Int를 사용합니다.
  • interactionSource를 사용하여 사용자의 '누르기' 상호작용을 구체적으로 추적합니다.
  • isPressed는 버튼을 누르고 있으면 true이고 그렇지 않으면 false입니다. isPressed이 true이면 LaunchedEffect가 루프에 들어갑니다.
    • 이 루프 내에서 delay (stepDelay 사용)를 사용하여 트리거 작업 사이에 일시중지를 만듭니다. coerceIn는 무한 루프를 방지하기 위해 지연 시간이 최소 1ms인지 확인합니다.
    • pressedListener는 루프 내에서 각 지연 후에 호출됩니다. 이렇게 하면 작업이 반복됩니다.
  • pressedListener는 rememberUpdatedState를 사용하여 onClick 람다 (실행할 작업)가 항상 최신 컴포지션의 최신 상태인지 확인합니다.
  • Icon는 버튼이 현재 눌려 있는지 여부에 따라 표시되는 이미지를 변경합니다.
    • isPressed이 true이면 selectedImage가 표시됩니다.
    • 그렇지 않으면 unselectedImage가 표시됩니다.

그런 다음 이 MomentaryIconButton를 예시에 사용합니다. 다음 스니펫은 카운터를 제어하는 두 개의 아이콘 버튼을 보여줍니다.

@Preview()
@Composable
fun MomentaryIconButtonExample() {
    var pressedCount by remember { mutableIntStateOf(0) }

    Row(
        modifier = Modifier.fillMaxWidth(),
        verticalAlignment = Alignment.CenterVertically
    ) {
        MomentaryIconButton(
            unselectedImage = R.drawable.fast_rewind,
            selectedImage = R.drawable.fast_rewind_filled,
            stepDelay = 100L,
            onClick = { pressedCount -= 1 },
            contentDescription = "Decrease count button"
        )
        Spacer(modifier = Modifier)
        Text("advanced by $pressedCount frames")
        Spacer(modifier = Modifier)
        MomentaryIconButton(
            unselectedImage = R.drawable.fast_forward,
            selectedImage = R.drawable.fast_forward_filled,
            contentDescription = "Increase count button",
            stepDelay = 100L,
            onClick = { pressedCount += 1 }
        )
    }
}

코드에 관한 핵심 사항

  • MomentaryIconButtonExample 컴포저블은 MomentaryIconButton 인스턴스 2개와 Text 컴포저블이 포함된 Row를 표시하여 카운터를 늘리고 줄이는 UI를 빌드합니다.
  • remember 및 mutableIntStateOf를 사용하여 pressedCount 변경 가능한 상태 변수를 유지하며, 이 변수는 0으로 초기화됩니다. pressedCount가 변경되면 이를 관찰하는 모든 컴포저블 (예: Text 컴포저블)이 새 값을 반영하도록 리컴포지션됩니다.
  • 첫 번째 MomentaryIconButton는 클릭하거나 길게 누르면 pressedCount를 줄입니다.
  • 두 번째 MomentaryIconButton는 클릭하거나 길게 누르면 pressedCount가 증가합니다.
  • 두 버튼 모두 stepDelay를 100밀리초로 사용합니다. 즉, 버튼을 길게 누르고 있는 동안 onClick 작업이 100밀리초마다 반복됩니다.

결과

다음 동영상은 아이콘 버튼과 카운터가 있는 UI를 보여줍니다.

그림 3. 카운터를 늘리거나 줄이는 두 개의 아이콘 버튼 (더하기 및 빼기)이 있는 카운터 UI

추가 리소스