날짜 선택도구

날짜 선택 도구를 사용하면 사용자가 날짜, 기간 또는 둘 다를 선택할 수 있습니다. 캘린더 대화상자 또는 텍스트 입력을 사용하여 사용자가 날짜를 선택할 수 있도록 합니다.

유형

날짜 선택기에는 세 가지 유형이 있습니다.

  • 도킹됨: 레이아웃 내에 인라인으로 표시됩니다. 전용 대화상자가 방해가 될 수 있는 소형 레이아웃에 적합합니다.
  • 모달: 앱 콘텐츠에 오버레이되는 대화상자로 표시됩니다. 그러면 날짜 선택에 명확하게 집중할 수 있습니다.
  • 모달 입력: 텍스트 필드를 모달 날짜 선택 도구와 결합합니다.

이러한 날짜 선택 도구는 다음 컴포저블을 사용하여 앱에서 구현할 수 있습니다.

  • DatePicker: 날짜 선택 도구의 일반 컴포저블입니다. 사용하는 컨테이너가 도킹되어 있는지 모델인지를 결정합니다.
  • DatePickerDialog: 모달 및 모달 입력 날짜 선택 도구의 컨테이너입니다.
  • DateRangePicker: 사용자가 시작일과 종료일이 있는 범위를 선택할 수 있는 날짜 선택 도구에 사용됩니다.

상태

다양한 날짜 선택 도구 컴포저블에서 공통적으로 사용하는 키 매개변수는 state이며, 이 매개변수는 DatePickerState 또는 DateRangePickerState 객체를 사용합니다. 이러한 속성은 현재 선택한 날짜와 같이 날짜 선택 도구를 사용하여 사용자의 선택에 관한 정보를 캡처합니다.

선택한 날짜를 사용하는 방법에 관한 자세한 내용은 선택한 날짜 사용 섹션을 참고하세요.

도킹된 날짜 선택기

다음 예에는 사용자에게 생년월일을 입력하라는 텍스트 필드가 있습니다. 입력란에서 캘린더 아이콘을 클릭하면 입력란 아래에 고정된 날짜 선택기가 열립니다.

@Composable
fun DatePickerDocked() {
    var showDatePicker by remember { mutableStateOf(false) }
    val datePickerState = rememberDatePickerState()
    val selectedDate = datePickerState.selectedDateMillis?.let {
        convertMillisToDate(it)
    } ?: ""

    Box(
        modifier = Modifier.fillMaxWidth()
    ) {
        OutlinedTextField(
            value = selectedDate,
            onValueChange = { },
            label = { Text("DOB") },
            readOnly = true,
            trailingIcon = {
                IconButton(onClick = { showDatePicker = !showDatePicker }) {
                    Icon(
                        imageVector = Icons.Default.DateRange,
                        contentDescription = "Select date"
                    )
                }
            },
            modifier = Modifier
                .fillMaxWidth()
                .height(64.dp)
        )

        if (showDatePicker) {
            Popup(
                onDismissRequest = { showDatePicker = false },
                alignment = Alignment.TopStart
            ) {
                Box(
                    modifier = Modifier
                        .fillMaxWidth()
                        .offset(y = 64.dp)
                        .shadow(elevation = 4.dp)
                        .background(MaterialTheme.colorScheme.surface)
                        .padding(16.dp)
                ) {
                    DatePicker(
                        state = datePickerState,
                        showModeToggle = false
                    )
                }
            }
        }
    }
}

@Composable
fun DatePickerFieldToModal(modifier: Modifier = Modifier) {
    var selectedDate by remember { mutableStateOf<Long?>(null) }
    var showModal by remember { mutableStateOf(false) }

    OutlinedTextField(
        value = selectedDate?.let { convertMillisToDate(it) } ?: "",
        onValueChange = { },
        label = { Text("DOB") },
        placeholder = { Text("MM/DD/YYYY") },
        trailingIcon = {
            Icon(Icons.Default.DateRange, contentDescription = "Select date")
        },
        modifier = modifier
            .fillMaxWidth()
            .pointerInput(selectedDate) {
                awaitEachGesture {
                    // Modifier.clickable doesn't work for text fields, so we use Modifier.pointerInput
                    // in the Initial pass to observe events before the text field consumes them
                    // in the Main pass.
                    awaitFirstDown(pass = PointerEventPass.Initial)
                    val upEvent = waitForUpOrCancellation(pass = PointerEventPass.Initial)
                    if (upEvent != null) {
                        showModal = true
                    }
                }
            }
    )

    if (showModal) {
        DatePickerModal(
            onDateSelected = { selectedDate = it },
            onDismiss = { showModal = false }
        )
    }
}

fun convertMillisToDate(millis: Long): String {
    val formatter = SimpleDateFormat("MM/dd/yyyy", Locale.getDefault())
    return formatter.format(Date(millis))
}

코드 관련 핵심 사항

  • 사용자가 IconButton를 클릭하면 날짜 선택 도구가 표시됩니다.
    • 아이콘 버튼은 OutlinedTextFieldtrailingIcon 매개변수의 인수 역할을 합니다.
    • showDatePicker 상태 변수는 고정 날짜 선택 도구의 공개 상태를 제어합니다.
  • 날짜 선택 도구의 컨테이너는 Popup 컴포저블로, 다른 요소의 레이아웃에 영향을 주지 않고 콘텐츠를 오버레이합니다.
  • selectedDateDatePickerState 객체에서 선택한 날짜의 값을 캡처하고 convertMillisToDate 함수를 사용하여 형식을 지정합니다.
  • 선택한 날짜가 텍스트 필드에 표시됩니다.
  • 고정된 날짜 선택 도구는 offset 수정자를 사용하여 텍스트 필드 아래에 배치됩니다.
  • Box는 텍스트 필드와 날짜 선택 도구를 적절하게 레이어링할 수 있도록 루트 컨테이너로 사용됩니다.

결과

캘린더 아이콘을 클릭하면 이 구현은 다음과 같이 표시됩니다.

고정된 날짜 선택 도구 예시
그림 1. 고정 날짜 선택 도구

모달 날짜 선택 도구는 화면 위에 플로팅되는 대화상자를 표시합니다. 이를 구현하려면 DatePickerDialog를 만들고 DatePicker를 전달합니다.

@Composable
fun DatePickerModal(
    onDateSelected: (Long?) -> Unit,
    onDismiss: () -> Unit
) {
    val datePickerState = rememberDatePickerState()

    DatePickerDialog(
        onDismissRequest = onDismiss,
        confirmButton = {
            TextButton(onClick = {
                onDateSelected(datePickerState.selectedDateMillis)
                onDismiss()
            }) {
                Text("OK")
            }
        },
        dismissButton = {
            TextButton(onClick = onDismiss) {
                Text("Cancel")
            }
        }
    ) {
        DatePicker(state = datePickerState)
    }
}

  • 구성 가능한 DatePickerModal 함수는 모달 날짜 선택 도구를 표시합니다.
  • onDateSelected 람다 표현식은 사용자가 날짜를 선택할 때 실행됩니다.
    • 선택한 날짜를 상위 컴포저블에 노출합니다.
  • onDismiss 람다 표현식은 사용자가 대화상자를 닫을 때 실행됩니다.

결과

이 구현은 다음과 같이 표시됩니다.

모달 날짜 선택 도구 예시
그림 2. 모달 날짜 선택 도구

입력 모달 날짜 선택 도구

입력이 있는 모달 날짜 선택 도구는 화면 위에 플로팅되어 사용자가 날짜를 입력할 수 있는 대화상자를 표시합니다.

@Composable
fun DatePickerModalInput(
    onDateSelected: (Long?) -> Unit,
    onDismiss: () -> Unit
) {
    val datePickerState = rememberDatePickerState(initialDisplayMode = DisplayMode.Input)

    DatePickerDialog(
        onDismissRequest = onDismiss,
        confirmButton = {
            TextButton(onClick = {
                onDateSelected(datePickerState.selectedDateMillis)
                onDismiss()
            }) {
                Text("OK")
            }
        },
        dismissButton = {
            TextButton(onClick = onDismiss) {
                Text("Cancel")
            }
        }
    ) {
        DatePicker(state = datePickerState)
    }
}

이는 모달 날짜 선택 도구의 예와 매우 유사합니다. 주요 차이점은 다음과 같습니다.

  • initialDisplayMode 매개변수는 초기 디스플레이 모드를 DisplayMode.Input로 설정합니다.
입력이 있는 모달 날짜 선택 도구
그림 3. 입력이 있는 모달 날짜 선택 도구

기간이 있는 날짜 선택기

사용자가 시작일과 종료일 사이의 기간을 선택할 수 있는 날짜 선택 도구를 만들 수 있습니다. 이렇게 하려면 DateRangePicker를 사용합니다.

DateRangePicker의 사용은 기본적으로 DatePicker와 동일합니다. PopUp의 하위 요소로 고정된 선택 도구에 사용할 수 있으며 모달 선택 도구로 사용하고 DatePickerDialog에 전달할 수도 있습니다. 주요 차이점은 DatePickerState 대신 DateRangePickerState를 사용한다는 점입니다.

다음 스니펫은 범위가 있는 모달 날짜 선택 도구를 만드는 방법을 보여줍니다.

@Composable
fun DateRangePickerModal(
    onDateRangeSelected: (Pair<Long?, Long?>) -> Unit,
    onDismiss: () -> Unit
) {
    val dateRangePickerState = rememberDateRangePickerState()

    DatePickerDialog(
        onDismissRequest = onDismiss,
        confirmButton = {
            TextButton(
                onClick = {
                    onDateRangeSelected(
                        Pair(
                            dateRangePickerState.selectedStartDateMillis,
                            dateRangePickerState.selectedEndDateMillis
                        )
                    )
                    onDismiss()
                }
            ) {
                Text("OK")
            }
        },
        dismissButton = {
            TextButton(onClick = onDismiss) {
                Text("Cancel")
            }
        }
    ) {
        DateRangePicker(
            state = dateRangePickerState,
            title = {
                Text(
                    text = "Select date range"
                )
            },
            showModeToggle = false,
            modifier = Modifier
                .fillMaxWidth()
                .height(500.dp)
                .padding(16.dp)
        )
    }
}

코드 관련 핵심 사항

  • onDateRangeSelected 매개변수는 선택된 시작일과 종료일을 나타내는 Pair<Long?, Long?>를 수신하는 콜백입니다. 이렇게 하면 상위 컴포저블이 선택한 범위에 액세스할 수 있습니다.
  • rememberDateRangePickerState()는 기간 선택 도구의 상태를 만듭니다.
  • DatePickerDialog는 모달 대화상자 컨테이너를 만듭니다.
  • 확인 버튼의 onClick 핸들러에서 onDateRangeSelected은 선택한 범위를 상위 컴포저블로 전달합니다.
  • DateRangePicker 컴포저블은 대화상자 콘텐츠 역할을 합니다.

결과

이 구현은 다음과 같이 표시됩니다.

모달 기간 날짜 선택 도구의 예
그림 4. 선택한 기간이 있는 모달 날짜 선택 도구

선택한 날짜 사용

선택한 날짜를 캡처하려면 상위 컴포저블에서 Long로 추적하고 onDateSelectedDatePicker에 값을 전달합니다. 다음 스니펫은 이를 보여줍니다. 전체 구현은 공식 스니펫 앱에서 확인할 수 있습니다.

// ...
    var selectedDate by remember { mutableStateOf<Long?>(null) }
// ...
        if (selectedDate != null) {
            val date = Date(selectedDate!!)
            val formattedDate = SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(date)
            Text("Selected date: $formattedDate")
        } else {
            Text("No date selected")
        }
// ...
        DatePickerModal(
            onDateSelected = {
                selectedDate = it
                showModal = false
            },
            onDismiss = { showModal = false }
        )
    }
// ...

범위 날짜 선택 도구에도 기본적으로 동일하게 적용되지만 Pair<Long?, Long?> 또는 데이터 클래스를 사용하여 시작 값과 종료 값을 캡처해야 합니다.

참고 항목