Selektory dat

Selektory dat umożliwiają użytkownikom wybieranie daty, zakresu dat lub obu tych opcji. Umożliwiają one użytkownikom wybieranie dat za pomocą okna kalendarza lub wpisywanie tekstu.

Typy

Istnieją 3 typy selektorów daty:

  • Zadokowano: wyświetla się w układzie. Jest to odpowiednie rozwiązanie w przypadku kompaktowych układów, w których dedykowane okno dialogowe może być uciążliwe.
  • Modalna: wyświetla się jako okno dialogowe nakładające się na zawartość aplikacji. Ułatwia to wybór daty.
  • Modal input (Pole modalne): łączy pole tekstowe z modalnym selektorem daty.

W aplikacji możesz zaimplementować te selektory daty za pomocą tych funkcji kompozycyjnych:

  • DatePicker: ogólny komponent do wyboru daty. Kontener, którego używasz, określa, czy jest on zadokowany, czy modelowany.
  • DatePickerDialog: kontener zarówno dla selektora daty w oknie modalnym, jak i dla selektora daty w oknie modalnym z polem wejściowym.
  • DateRangePicker: w przypadku dowolnego selektora daty, w którym użytkownik może wybrać zakres z datą rozpoczęcia i zakończenia.

Województwo

Parametrem kluczowym, który jest wspólny dla różnych komponentów do wyboru daty, jest state, który przyjmuje obiekt DatePickerState lub DateRangePickerState. Ich właściwości rejestrują informacje o wyborze użytkownika za pomocą selektora daty, np. aktualnie wybraną datę.

Więcej informacji o tym, jak korzystać z wybranej daty, znajdziesz w sekcji Korzystanie z wybranej daty.

Zadokowany selektor daty

W przykładzie poniżej znajduje się pole tekstowe, w którym użytkownik jest proszony o podanie daty urodzenia. Gdy klikną ikonę kalendarza w polu, poniżej pola wprowadzania otworzy się zadokowany selektor daty.

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

Najważniejsze informacje o kodzie

  • Selektor daty pojawi się, gdy użytkownik kliknie IconButton.
    • Przycisk ikony służy jako argument parametru OutlinedTextFieldtrailingIcon.
    • Zmienna stanu showDatePicker kontroluje widoczność zadokowanego selektora dat.
  • Kontener selektora daty to komponent Popup, który nakłada się na treść bez wpływu na układ innych elementów.
  • selectedDate rejestruje wartość wybranej daty z obiektu DatePickerState i formatuje ją za pomocą funkcji convertMillisToDate.
  • Wybrana data pojawi się w polu tekstowym.
  • Przykotwiczony selektor dat jest umieszczony pod polem tekstowym za pomocą modyfikatora offset.
  • Element Box jest używany jako kontener główny, aby umożliwić prawidłowe ułożenie warstw pola tekstowego i selektora daty.

Wyniki

Po kliknięciu ikony kalendarza implementacja wygląda tak:

Przykład zadokowanego selektora daty.
Rysunek 1. zadokowany selektor daty;

Modalny selektor daty wyświetla okno, które pojawia się na ekranie. Aby go wdrożyć, utwórz DatePickerDialog i przekaż do niego 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)
    }
}

  • Funkcja typu „composable” DatePickerModal wyświetla selektor daty w oknie modalnym.
  • Wyrażenie lambda onDateSelected jest wykonywane, gdy użytkownik wybierze datę.
    • Przekazuje wybraną datę do elementu kompozycyjnego wyższego rzędu.
  • Wyrażenie lambda onDismiss jest wykonywane, gdy użytkownik zamyka okno.

Wyniki

Ta implementacja wygląda tak:

Przykład modalnego selektora daty
Rysunek 2. Modalny selektor daty.

Selektor daty w modalnym polu wprowadzania

Modalny selektor dat z polem wprowadzania wyświetla okno, które pojawia się na ekranie i umożliwia użytkownikowi wpisanie daty.

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

Jest to bardzo podobne do przykładu selektora dat w oknie modalnym. Podstawowa różnica jest następująca:

  • Parametr initialDisplayMode ustawia początkowy tryb wyświetlania na DisplayMode.Input.
Modalny selektor daty z polem wprowadzania.
Rysunek 3. Modalny selektor dat z polem wprowadzania.

Selektor dat z zakresem

Możesz utworzyć selektor daty, który umożliwia użytkownikowi wybranie zakresu między datą początkową a końcową. Aby to zrobić, użyj DateRangePicker.

Użycie DateRangePicker jest w zasadzie takie samo jak DatePicker. Możesz użyć go jako zadokowanego selektora jako elementu podrzędnego PopUp lub jako modalnego selektora i przekazać go do DatePickerDialog. Podstawowa różnica polega na tym, że zamiast DatePickerState używasz operatora DateRangePickerState.

Poniższy fragment kodu pokazuje, jak utworzyć modalny selektor dat z zakresem:

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

Najważniejsze informacje o kodzie

  • Parametr onDateRangeSelected to wywołanie zwrotne, które otrzymuje Pair<Long?, Long?> reprezentujące wybrane daty rozpoczęcia i zakończenia. Dzięki temu funkcja kompozycyjna nadrzędna ma dostęp do wybranego zakresu.
  • rememberDateRangePickerState() tworzy stan selektora zakresu dat.
  • Tag DatePickerDialog tworzy kontener okna modalnego.
  • W obsłudze przycisku potwierdzenia onClick funkcja onDateRangeSelected przekazuje wybrany zakres do nadrzędnego komponentu.
  • Element DateRangePicker pełni funkcję treści okna.

Wyniki

Ta implementacja wygląda tak:

Przykład selektora zakresu dat w oknie modalnym.
Rysunek 4. Modalny selektor dat z wybranym zakresem.

Użyj wybranej daty

Aby zarejestrować wybraną datę, śledź ją w kompozycji nadrzędnej jako Long i przekaż wartość do elementu DatePickeronDateSelected. Pokazuje to poniższy fragment kodu, ale pełną implementację możesz zobaczyć w oficjalnej aplikacji z fragmentami kodu.

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

W przypadku selektorów zakresu dat obowiązuje ta sama zasada, ale do przechwytywania wartości początkowej i końcowej musisz użyć Pair<Long?, Long?> lub klasy danych.

Zobacz również