חלונית לבחירת תאריך

חלוניות לבחירת תאריך מאפשרות למשתמשים לבחור תאריך, טווח תאריכים או את שניהם. הם משתמשים בתיבת דו-שיח של לוח שנה או בשדה להזנת טקסט כדי לאפשר למשתמשים לבחור תאריכים.

סוגים

יש שלושה סוגים של חלוניות לבחירת תאריך:

  • מעוגן: מופיע בתוך השורה בפריסה. הוא מתאים לפריסות קומפקטיות שבהן תיבת דו-שיח ייעודית עלולה להפריע.
  • מודאלי: מופיע כתיבת דו-שיח שמוצגת מעל התוכן של האפליקציה. כך המשתמשים יכולים להתמקד בבחירת התאריך.
  • קלט מודאלי: שילוב של שדה טקסט עם חלונית מודאלית לבחירת תאריך.

כדי להטמיע את החלוניות האלה לבחירת תאריך באפליקציה, אפשר להשתמש ברכיבים הבאים שניתנים לשילוב:

  • ‫DatePicker: פונקציה כללית של Composable לחלונית לבחירת תאריך. המאגר שבו אתם משתמשים קובע אם הוא מעוגן או מודל.
  • ‫DatePickerDialog: הקונטיינר של החלוניות לבחירת תאריך, גם במודל וגם בקלט של המודל.
  • ‫DateRangePicker: לכל חלונית לבחירת תאריך שבה המשתמש יכול לבחור טווח עם תאריך התחלה ותאריך סיום.

מדינה

הפרמטר העיקרי שמשותף לרכיבי ה-Composable השונים של חלונית לבחירת תאריך הוא 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 מופעל כשהמשתמש בוחר תאריך.
    • הוא חושף את התאריך שנבחר לרכיב ה-Composable ברמת ההורה.
  • ביטוי הלמבדה 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, או להשתמש בו בתור בורר מודאלי ולהעביר אותו אל DatePickerDialog. ההבדל העיקרי הוא שמשתמשים ב-DateRangePickerState במקום ב-DatePickerState.

בקטע הקוד הבא אפשר לראות איך יוצרים חלונית מודאלית לבחירת טווח תאריכים:

@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 הוא קריאה חוזרת (callback) שמקבלת Pair<Long?, Long?> שמייצג את תאריכי ההתחלה והסיום שנבחרו. כך, לרכיב ה-composable הראשי יש גישה לטווח שנבחר.
  • ‫rememberDateRangePickerState() יוצר את המצב של בורר טווח התאריכים.
  • התג DatePickerDialog יוצר מאגר של תיבת דו-שיח מודאלית.
  • ב-handler של onClick בכפתור האישור, onDateRangeSelected מעביר את הטווח שנבחר לרכיב הקומפוזבילי הראשי.
  • הקומפוזבילי DateRangePicker משמש כתוכן של תיבת הדו-שיח.

תוצאות

ההטמעה הזו נראית כך:

דוגמה לחלונית לבחירת טווח תאריכים.
איור 4. חלונית מודאלית לבחירת תאריך עם טווח שנבחר.

שימוש בתאריך שנבחר

כדי לתעד את התאריך שנבחר, עוקבים אחריו ברכיב ה-parent הניתן לקישור בתור Long ומעבירים את הערך אל DatePicker ב-onDateSelected. קטע הקוד הבא מדגים את זה, אבל אפשר לראות את ההטמעה המלאה באפליקציית קטעי הקוד הרשמית.

// ...
    var selectedDate by remember { mutableStateOf<Long?>(null) }
// ...
        if (selectedDate != null) {
            val date = Date(selectedDate!!)
            val formattedDate = SimpleDateFormat("MMM dd, yyyy", LocalLocale.current.platformLocale).format(date)
            Text("Selected date: $formattedDate")
        } else {
            Text("No date selected")
        }
// ...
        DatePickerModal(
            onDateSelected = {
                selectedDate = it
                showModal = false
            },
            onDismiss = { showModal = false }
        )
    }
// ...

אותו עיקרון חל על חלוניות לבחירת טווח תאריכים, אבל צריך להשתמש ב-Pair<Long?, Long?> או במחלקת נתונים כדי לתעד את ערכי ההתחלה והסיום.

עוד באותו הקשר