Выбор даты

Инструменты выбора даты позволяют пользователям выбирать дату, диапазон дат или и то, и другое. Для выбора дат используются диалоговое окно календаря или текстовое поле ввода.

Типы

Существует три типа программ для выбора дат:

  • Закрепленный : отображается непосредственно в макете. Подходит для компактных макетов, где отдельное диалоговое окно может показаться навязчивым.
  • Модальное окно : Отображается в виде диалогового окна, наложенного поверх содержимого приложения. Это позволяет четко сфокусироваться на выборе даты.
  • Модальное поле ввода : объединяет текстовое поле с модальным окном выбора даты.

Вы можете реализовать эти средства выбора даты в своем приложении, используя следующие составные элементы:

  • 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 .
    • Кнопка с иконкой служит аргументом для параметра trailingIcon объекта OutlinedTextField .
    • Переменная состояния showDatePicker управляет видимостью закрепленного элемента выбора даты.
  • Контейнер для выбора даты представляет собой составной Popup , который накладывается поверх содержимого, не влияя на расположение других элементов.
  • selectedDate извлекает значение выбранной даты из объекта DatePickerState и форматирует его с помощью функции 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 или в качестве модального элемента выбора даты (Modal Picker) и передать его в 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 и передавайте значение в DatePicker в onDateSelected . Следующий фрагмент кода демонстрирует это, хотя полную реализацию можно увидеть в официальном приложении с фрагментами кода .

// ...
    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?> или класс данных.

См. также