Seletores de data

Os seletores de data permitem que os usuários selecionem uma data, um período ou ambos. Eles usam uma caixa de diálogo de calendário ou uma entrada de texto para permitir que os usuários selecionem datas.

Tipos

Há três tipos de seletor de data:

  • Fixado: aparece inline no layout. É adequado para layouts compactos em que uma caixa de diálogo dedicada pode parecer intrusiva.
  • Modal: aparece como uma caixa de diálogo sobrepondo o conteúdo do app. Isso fornece um foco claro na seleção de data.
  • Entrada modal: combina um campo de texto com um seletor de data modal.

É possível implementar esses seletores de data no app usando os seguintes combináveis:

  • DatePicker: elemento combinável geral para um seletor de datas. O contêiner que você usa determina se ele está ancorado ou em modelo.
  • DatePickerDialog: o contêiner para os seletores de data de entrada modal e modal.
  • DateRangePicker: para qualquer seletor de data em que o usuário possa selecionar um intervalo com uma data inicial e final.

Estado

O parâmetro principal que os diferentes elementos combináveis do seletor de data têm em comum é state, que usa um objeto DatePickerState ou DateRangePickerState. As propriedades capturam informações sobre a seleção do usuário usando o seletor de data, como a data atual selecionada.

Para mais informações sobre como usar a data selecionada, consulte a seção Usar a data selecionada.

Seletor de datas fixado

No exemplo abaixo, há um campo de texto que solicita que o usuário insira a data de nascimento. Quando o usuário clica no ícone de calendário no campo, um seletor de datas fixado é aberto abaixo do 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))
}

Pontos principais sobre o código

  • O seletor de data aparece quando o usuário clica em IconButton.
    • O botão de ícone serve como argumento para o parâmetro trailingIcon do OutlinedTextField.
    • A variável de estado showDatePicker controla a visibilidade do seletor de data acoplado.
  • O contêiner do seletor de data é um elemento combinável Popup, que sobrepõe o conteúdo sem afetar o layout de outros elementos.
  • selectedDate captura o valor da data selecionada do objeto DatePickerState e o formata usando a função convertMillisToDate.
  • A data selecionada aparece no campo de texto.
  • O seletor de datas fixado é posicionado abaixo do campo de texto usando um modificador offset.
  • Um Box é usado como o contêiner raiz para permitir a estratificação adequada do campo de texto e do seletor de data.

Resultados

Depois de clicar no ícone de calendário, a implementação vai aparecer da seguinte maneira:

Exemplo de seletor de datas fixado.
Figura 1. Um seletor de datas fixado.

Um seletor de data modal mostra uma caixa de diálogo flutuante na tela. Para implementar isso, crie uma DatePickerDialog e transmita uma 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)
    }
}

  • A função combinável DatePickerModal mostra um seletor de data modal.
  • A expressão lambda onDateSelected é executada quando o usuário seleciona uma data.
    • Ele expõe a data selecionada para o elemento combinável pai.
  • A expressão lambda onDismiss é executada quando o usuário dispensa a caixa de diálogo.

Resultados

Essa implementação aparece da seguinte maneira:

Exemplo de seletor de datas modal.
Figura 2. Um seletor de data modal.

Input modal date picker

Um seletor de data modal com entrada mostra uma caixa de diálogo que flutua sobre a tela e permite que o usuário insira uma data.

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

Isso é muito parecido com o exemplo de seletor de data modal. A principal diferença é a seguinte:

  • O parâmetro initialDisplayMode define o modo de exibição inicial como DisplayMode.Input.
Seletor de data modal com entrada.
Figura 3. Um seletor de data modal com entrada.

Seletor de datas com período

Você pode criar um seletor de data que permita ao usuário selecionar um período entre uma data de início e de término. Para fazer isso, use DateRangePicker.

O uso de DateRangePicker é basicamente o mesmo que DatePicker. Você pode usá-lo para um seletor acoplado como filho de PopUp ou como um seletor modal e transmiti-lo para DatePickerDialog. A principal diferença é que você usa DateRangePickerState em vez de DatePickerState.

O snippet a seguir demonstra como criar um seletor de data modal com um intervalo:

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

Pontos principais sobre o código

  • O parâmetro onDateRangeSelected é um callback que recebe um Pair<Long?, Long?> que representa as datas de início e término selecionadas. Isso dá ao elemento combinável pai acesso ao intervalo selecionado.
  • rememberDateRangePickerState() cria o estado para o seletor de período de datas.
  • O DatePickerDialog cria um contêiner de caixa de diálogo modal.
  • No gerenciador onClick do botão de confirmação, onDateRangeSelected transmite o intervalo selecionado para o elemento combinável pai.
  • O elemento combinável DateRangePicker serve como o conteúdo da caixa de diálogo.

Resultados

Essa implementação aparece da seguinte maneira:

Exemplo de seletor de datas de período modal.
Figura 4. Um seletor de data modal com um intervalo selecionado.

Usar a data selecionada

Para capturar a data selecionada, rastreie-a no elemento combinável pai como Long e transmita o valor para DatePicker em onDateSelected. O snippet abaixo demonstra isso, embora você possa conferir a implementação completa no app oficial de snippets.

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

O mesmo se aplica aos seletores de datas de intervalo, mas é necessário usar uma Pair<Long?, Long?> ou uma classe de dados para capturar os valores de início e término.

Veja também