Selectores de fechas

Los selectores de fecha permiten a los usuarios seleccionar una fecha, un período o ambos. Usan un diálogo de calendario o una entrada de texto para permitir que los usuarios seleccionen fechas.

Tipos

Existen tres tipos de selectores de fecha:

  • Asegurado: Aparece intercalado en el diseño. Es adecuado para diseños compactos en los que un diálogo dedicado podría resultar intrusivo.
  • Modal: Aparece como un diálogo que se superpone al contenido de la app. Esto proporciona un enfoque claro en la selección de fechas.
  • Entrada modal: Combina un campo de texto con un selector de fecha modal.

Puedes implementar estos selectores de fecha en tu app con los siguientes elementos componibles:

  • DatePicker: Es un elemento componible general para un selector de fecha. El contenedor que usas determina si está anclado o es un modelo.
  • DatePickerDialog: Es el contenedor para los selectores de fecha modales y de entrada modales.
  • DateRangePicker: Para cualquier selector de fecha en el que el usuario pueda seleccionar un rango con una fecha de inicio y una fecha de finalización.

Estado

El parámetro clave que comparten los diferentes elementos componibles del selector de fecha es state, que toma un objeto DatePickerState o DateRangePickerState. Sus propiedades capturan información sobre la selección del usuario con el selector de fecha, como la fecha seleccionada actual.

Para obtener más información sobre cómo puedes usar la fecha seleccionada, consulta la sección Uso de la fecha seleccionada.

Selector de fecha anclado

En el siguiente ejemplo, hay un campo de texto que le solicita al usuario que ingrese su fecha de nacimiento. Cuando hacen clic en el ícono de calendario en el campo, se abre un selector de fecha anclado debajo del campo de entrada.

@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))
}

Puntos clave sobre el código

  • El selector de fecha aparece cuando el usuario hace clic en IconButton.
    • El botón de ícono sirve como argumento para el parámetro trailingIcon de OutlinedTextField.
    • La variable de estado showDatePicker controla la visibilidad del selector de fecha anclado.
  • El contenedor del selector de fecha es un elemento Popup componible que superpone el contenido sin afectar el diseño de otros elementos.
  • selectedDate captura el valor de la fecha seleccionada del objeto DatePickerState y le da formato con la función convertMillisToDate.
  • La fecha seleccionada aparece en el campo de texto.
  • El selector de fecha anclado se posiciona debajo del campo de texto con un modificador offset.
  • Se usa un Box como contenedor raíz para permitir la correcta disposición en capas del campo de texto y el selector de fecha.

Resultados

Después de hacer clic en el ícono de calendario, esta implementación se ve de la siguiente manera:

Ejemplo de selector de fecha anclado.
Figura 1: Es un selector de fecha anclado.

Un selector de fecha modal muestra un diálogo que flota sobre la pantalla. Para implementarlo, crea un DatePickerDialog y pásale un 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)
    }
}

  • La función de componibilidad DatePickerModal muestra un selector de fecha modal.
  • La expresión lambda onDateSelected se ejecuta cuando el usuario selecciona una fecha.
    • Expone la fecha seleccionada al elemento componible principal.
  • La expresión lambda onDismiss se ejecuta cuando el usuario descarta el diálogo.

Resultados

Esta implementación se ve de la siguiente manera:

Ejemplo de selector de fechas modal.
Figura 2: Selector de fecha modal.

Selector de fechas modal de entrada

Un selector de fecha modal con entrada muestra un diálogo que flota sobre la pantalla y permite que el usuario ingrese una fecha.

@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)
    }
}

Es muy similar al ejemplo del selector de fecha modal. La diferencia principal es la siguiente:

  • El parámetro initialDisplayMode establece el modo de visualización inicial en DisplayMode.Input.
Selector de fecha modal con entrada.
Figura 3: Selector de fecha modal con entrada.

Selector de fecha con rango

Puedes crear un selector de fecha que permita al usuario seleccionar un período entre una fecha de inicio y una fecha de finalización. Para ello, usa DateRangePicker.

El uso de DateRangePicker es básicamente el mismo que el de DatePicker. Puedes usarlo para un selector anclado como elemento secundario de PopUp, o bien como un selector modal y pasarlo a DatePickerDialog. La principal diferencia es que usas DateRangePickerState en lugar de DatePickerState.

En el siguiente fragmento, se muestra cómo crear un selector de fecha modal con un rango:

@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)
        )
    }
}

Puntos clave sobre el código

  • El parámetro onDateRangeSelected es una devolución de llamada que recibe un Pair<Long?, Long?> que representa las fechas de inicio y finalización seleccionadas. Esto le da al elemento componible principal acceso al rango seleccionado.
  • rememberDateRangePickerState() crea el estado del selector de rango de fechas.
  • DatePickerDialog crea un contenedor de diálogo modal.
  • En el controlador onClick del botón de confirmación, onDateRangeSelected pasa el rango seleccionado al elemento componible principal.
  • La función de componibilidad DateRangePicker funciona como el contenido del diálogo.

Resultados

Esta implementación se ve de la siguiente manera:

Ejemplo de selector de fecha de rango modal.
Figura 4: Un selector de fecha modal con un rango seleccionado.

Usar la fecha seleccionada

Para capturar la fecha seleccionada, haz un seguimiento en el elemento componible principal como un Long y pasa el valor al DatePicker en onDateSelected. En el siguiente fragmento, se muestra esto, aunque puedes ver la implementación completa en la app oficial de fragmentos.

// ...
    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 }
        )
    }
// ...

Básicamente, lo mismo se aplica a los selectores de rango de fechas, aunque debes usar un Pair<Long?, Long?> o una clase de datos para capturar los valores de inicio y finalización.

Consulta también