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

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

סוגים

יש שלושה סוגים של בוררי תאריכים:

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

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

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

מדינה

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

תוצאות

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

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

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

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

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

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

למידע נוסף