본문 바로가기

안드로이드/Compose Dialog

[안드로이드 Jetpack Compose Dialog 시리즈] TimePicker Dialog 구현하기 #5


안녕하세요. 이번 포스팅에서는 안드로이드 Jetpack Compose에서 TimePickerDialog를 구현하겠습니다. TimePickerDialog는 시간을 설정할 때 유용하게 사용할 수 있는 Dialog입니다. Compose Material3에서 제공하는 TimePicker를 사용해서 아래 사진과 같은 Dialog를 만들겠습니다.

안드로이드 compose material3을 이용해서 만든 time picker dialog
안드로이드 compose material3을 이용해서 만든 time picker dialog

1. TimePicker Dialog 만들기

Compose Material3에서 제공하는 Dialog와 TimePicker를 사용해서 TimePickerDialog를 만들었습니다. Compose는 상태를 관리하기 때문에 rememberTimePickerState를 통해 초기값이나 24시간제와 같은 옵션을 설정할 수 있습니다. 해당 상태변수를 TimePicker의 state 매개변수로 전달하면 됩니다.

TimePickerState로 전달하는 initialHour의 경우에는 24시간제에 해당하기 때문에 0~23 사이의 값을 전달해야 합니다. 만약에 12시간 제로 변수를 관리하고 있다면 오후의 경우에 +12를 해주어야 합니다.

// CustomTimePickerDialog.kt
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomTimePickerDialog(
    selectedHour: Int?,
    selectedMinute: Int?,
    onClickCancel: () -> Unit,
    onClickConfirm: (hour: Int, minute: Int) -> Unit
) {
    val timePickerState = rememberTimePickerState(
        initialHour = selectedHour ?: 0,
        initialMinute = selectedMinute ?: 0,
        is24Hour = false
    )
    Dialog(
        onDismissRequest = { onClickCancel() },
    ) {
        Card(
            shape = RoundedCornerShape(8.dp), // Card의 모든 꼭지점에 8.dp의 둥근 모서리 적용
        ) {
            Column(
                modifier = Modifier
                    .width(300.dp)
                    .wrapContentHeight()
                    .background(
                        color = Color.White,
                    )
                    .padding(16.dp),
                horizontalAlignment = Alignment.CenterHorizontally,
            ) {

                Text(text = "시간을 선택해주세요.")

                Spacer(modifier = Modifier.width(15.dp))

                TimePicker(
                    state = timePickerState,
                    modifier = Modifier.padding(top = 10.dp)
                )

                Row(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalArrangement = Arrangement.Center,
                ) {
                    Button(onClick = {
                        onClickCancel()
                    }) {
                        Text(text = "취소")
                    }

                    Spacer(modifier = Modifier.width(5.dp))

                    Button(onClick = {
                        val hour = timePickerState.hour
                        val minute = timePickerState.minute
                        onClickConfirm(hour, minute)
                    }) {
                        Text(text = "확인")
                    }
                }
            }
        }
    }
}

2. TimePicker Dialog 사용하기

위에서 만든 TimePickerDialog를 사용하기 위해서 관련된 상태를 만들어주고, 상태에 따른 Dialog의 노출 여부를 설정하겠습니다.

CustomTimePickerDialogState에서는 선택한 Hour와 Minute 정보를 가지고 있으며, selectedHHmm라는 getter를 통해 HHmm 형태로 받을 수 있게 구현했습니다.

// CustomTimePickerDialogState.kt
data class CustomTimePickerDialogState(
    var selectedHour: Int? = null,
    var selectedMinute: Int? = null,
    var isShowDialog: Boolean = false,
    val onClickConfirm: (hour: Int, minute: Int) -> Unit,
    val onClickCancel: () -> Unit,
) {
    val selectedHHmm: String?
        get() {
            return if (selectedHour != null && selectedMinute != null) {
                val time = String.format("%02d%02d", selectedHour, selectedMinute)
                time
            } else {
                null
            }
        }
}


아래는 ViewModel의 내부 모습입니다. 위에서 만든 상태 관련 클래스를 MutableState를 통해 관리하고 있으며, 클릭 이벤트에 대한 처리를 하고 있습니다. Confirm과 Cancel 이벤트에서는 각각 isShowDialog에 false를 전달하고 selectedHour와 selectedMinute에 대한 처리를 하고 있습니다. 또한, showTimePickerDialog 함수에서는 isShowDialog에 true를 전달함으로써 Dialog가 나타나게 됩니다.

// MainViewModel.kt
@HiltViewModel
class MainViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle?,
) : ViewModel() {

    val customTimePickerDialogState: MutableState<CustomTimePickerDialogState?> =
        mutableStateOf(null)

    init {
        customTimePickerDialogState.value = CustomTimePickerDialogState(
            onClickConfirm = { hour, minute ->
                customTimePickerDialogState.value = customTimePickerDialogState.value?.copy(
                    isShowDialog = false,
                    selectedHour = hour,
                    selectedMinute = minute
                )
            },
            onClickCancel = {
                customTimePickerDialogState.value = customTimePickerDialogState.value?.copy(
                    isShowDialog = false
                )
            }
        )
    }

    fun showTimePickerDialog() {
        customTimePickerDialogState.value =
            customTimePickerDialogState.value?.copy(isShowDialog = true)
    }
}


아래는 화면을 담당하는 MainScreen입니다. 사용자가 버튼을 누르면 ViewModel에서 정의한 showTimePickerDialog()를 호출합니다. 그러면 isShowDialog에 true가 전달되면서 CustomTimePickerDialog가 화면에 보이게 됩니다.

// MainScreen.kt
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(
    mainNavController: NavHostController,
    viewModel: MainViewModel
) {

    val customTimePickerDialogState = viewModel.customTimePickerDialogState.value

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(text = "메인화면") },
                navigationIcon = {
                    IconButton(onClick = { mainNavController.popBackStack() }) {
                        Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null)
                    }
                }
            )
        }
    ) { paddingValues ->
        Column(
            modifier = Modifier.padding(paddingValues)
        ) {
           
            Button(
                onClick = { viewModel.showTimePickerDialog() },
                shape = RectangleShape,
            ) {
                Text(
                    text = "5. Show TimePickerDialog",
                    textAlign = TextAlign.Center,
                    style = TextStyle(
                        fontSize = 14.sp,
                        fontWeight = FontWeight.Bold
                    )
                )
            }
            Text(
                text = "Selected Time: ${viewModel.customTimePickerDialogState.value?.selectedHHmm ?: "시간을 선택해주세요."}",
                style = TextStyle(
                    fontSize = 14.sp,
                    fontWeight = FontWeight.Bold
                )
            )

            if (customTimePickerDialogState?.isShowDialog == true) {
                CustomTimePickerDialog(
                    selectedHour = customTimePickerDialogState.selectedHour,
                    selectedMinute = customTimePickerDialogState.selectedMinute,
                    onClickCancel = customTimePickerDialogState.onClickCancel,
                    onClickConfirm = customTimePickerDialogState.onClickConfirm
                )
            }
        }
    }
}

3. TimePicker Dialog 정리

이번 포스팅에서는 안드로이드 Jetpack Compose Material3 디자인에서 Dialog와 TimePicker를 사용해서 TimePickerDialog를 구현했습니다. TimePicker에서 주의할 점은 Hour(시간) 처리에 대한 부분입니다. 시간은 24시간제와 12시간 제로 나뉘기 때문에 12시간 제로 데이터를 관리하고 있다면 12를 더한 값을 TimePicker의 초기 상태값으로 전달해야만 합니다.

위에서 만든 CustomTimePickerDialogState에서는 24시간 제로 데이터를 관리하고 있는데, 필요에 따라서 12시간 제로 시간을 반환해 주는 getter를 만들어서 사용해도 좋을 것 같습니다.