Sélecteurs de date

Les sélecteurs de date permettent aux utilisateurs de sélectionner une date, une plage de dates ou les deux. Ils utilisent une boîte de dialogue de calendrier ou une saisie de texte pour permettre aux utilisateurs de sélectionner des dates.

Types

Il existe trois types de sélecteurs de date :

  • Ancré : s'affiche en ligne dans la mise en page. Il convient aux mises en page compactes où une boîte de dialogue dédiée peut sembler intrusive.
  • Modal : s'affiche sous la forme d'une boîte de dialogue qui se superpose au contenu de l'application. Cela permet de se concentrer clairement sur la sélection de la date.
  • Saisie modale : combine un champ de texte avec un sélecteur de date modal.

Vous pouvez implémenter ces sélecteurs de date dans votre application à l'aide des composables suivants :

  • DatePicker : composable général pour un sélecteur de date. Le conteneur que vous utilisez détermine s'il est ancré ou modal.
  • DatePickerDialog : conteneur pour les sélecteurs de date modaux et d'entrée modale.
  • DateRangePicker : pour tout sélecteur de date où l'utilisateur peut sélectionner une plage avec une date de début et une date de fin.

État

Le paramètre clé que les différents composables du sélecteur de date ont en commun est state, qui accepte un objet DatePickerState ou DateRangePickerState. Leurs propriétés capturent des informations sur la sélection de l'utilisateur à l'aide du sélecteur de date, comme la date actuellement sélectionnée.

Pour savoir comment utiliser la date sélectionnée, consultez la section Utiliser la date sélectionnée.

Sélecteur de date ancré

Dans l'exemple suivant, un champ de texte invite l'utilisateur à saisir sa date de naissance. Lorsqu'il clique sur l'icône de calendrier dans le champ, un sélecteur de date ancré s'ouvre sous le champ de saisie.

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

Points clés concernant le code

  • Le sélecteur de date s'affiche lorsque l'utilisateur clique sur IconButton.
    • Le bouton en forme d'icône sert d'argument pour le paramètre trailingIcon de OutlinedTextField.
    • La variable d'état showDatePicker contrôle la visibilité du sélecteur de date ancré.
  • Le conteneur du sélecteur de date est un composable Popup, qui superpose le contenu sans affecter la mise en page des autres éléments.
  • selectedDate capture la valeur de la date sélectionnée à partir de l'objet DatePickerState et la met en forme à l'aide de la fonction convertMillisToDate.
  • La date sélectionnée s'affiche dans le champ de texte.
  • Le sélecteur de date ancré est positionné sous le champ de texte à l'aide d'un modificateur offset.
  • Un Box est utilisé comme conteneur racine pour permettre une superposition correcte du champ de texte et du sélecteur de date.

Résultats

Après avoir cliqué sur l'icône d'agenda, cette implémentation se présente comme suit :

Exemple de sélecteur de date ancré.
Figure 1. Sélecteur de date ancré.

Un sélecteur de date modal affiche une boîte de dialogue qui flotte sur l'écran. Pour l'implémenter, créez un DatePickerDialog et transmettez-lui 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 fonction composable DatePickerModal affiche un sélecteur de date modal.
  • L'expression lambda onDateSelected s'exécute lorsque l'utilisateur sélectionne une date.
    • Il expose la date sélectionnée au composable parent.
  • L'expression lambda onDismiss s'exécute lorsque l'utilisateur ferme la boîte de dialogue.

Résultats

Cette implémentation est la suivante :

Exemple de sélecteur de date modal.
Figure 2. Sélecteur de date modal.

Sélecteur de date modal de saisie

Un sélecteur de date modal avec entrée affiche une boîte de dialogue qui flotte sur l'écran et permet à l'utilisateur de saisir une date.

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

Il est très semblable à l'exemple de sélecteur de date modal. La principale différence est la suivante :

  • Le paramètre initialDisplayMode définit le mode d'affichage initial sur DisplayMode.Input.
Sélecteur de date modal avec saisie.
Figure 3 : Sélecteur de date modal avec saisie.

Sélecteur de date avec plage

Vous pouvez créer un sélecteur de date qui permet à l'utilisateur de sélectionner une plage entre une date de début et une date de fin. Pour ce faire, utilisez DateRangePicker.

L'utilisation de DateRangePicker est essentiellement la même que celle de DatePicker. Vous pouvez l'utiliser pour un sélecteur ancré en tant qu'enfant de PopUp, ou vous pouvez l'utiliser en tant que sélecteur modal et le transmettre à DatePickerDialog. La principale différence est que vous utilisez DateRangePickerState au lieu de DatePickerState.

L'extrait de code suivant montre comment créer un sélecteur de date modal avec une plage :

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

Points clés concernant le code

  • Le paramètre onDateRangeSelected est un rappel qui reçoit un Pair<Long?, Long?> représentant les dates de début et de fin sélectionnées. Cela donne au composable parent l'accès à la plage sélectionnée.
  • rememberDateRangePickerState() crée l'état du sélecteur de plage de dates.
  • DatePickerDialog crée un conteneur de boîte de dialogue modale.
  • Dans le gestionnaire onClick du bouton de confirmation, onDateRangeSelected transmet la plage sélectionnée au composable parent.
  • Le composable DateRangePicker sert de contenu de boîte de dialogue.

Résultats

Cette implémentation est la suivante :

Exemple de sélecteur de plage de dates modal.
Figure 4. Sélecteur de date modal avec une plage sélectionnée.

Utiliser la date sélectionnée

Pour capturer la date sélectionnée, effectuez-en le suivi dans le composable parent en tant que Long et transmettez la valeur à DatePicker dans onDateSelected. L'extrait suivant illustre ce point, mais vous pouvez voir l'implémentation complète dans l'application d'extraits officielle.

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

Il en va de même pour les sélecteurs de plage de dates, bien que vous deviez utiliser un Pair<Long?, Long?> ou une classe de données pour capturer les valeurs de début et de fin.

Voir aussi