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

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

סוגים

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

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

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

  • ‫DatePicker: רכיב כללי שאפשר להשתמש בו כדי ליצור בוחר תאריכים. המאגר שבו אתם משתמשים קובע אם הוא מעוגן או מודל.
  • ‫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.
    • הכפתור בצורת סמל משמש כארגומנט לפרמטר OutlinedTextField's trailingIcon.
    • משתנה המצב showDatePicker קובע את הרשאות הגישה של הכלי לבחירת תאריך שמוצמד.
  • הקונטיינר של בוחר התאריכים הוא רכיב קומפוזבילי Popup, שמוצג כשכבת-על מעל התוכן בלי להשפיע על הפריסה של רכיבים אחרים.
  • ‫selectedDate מחלצת את הערך של התאריך שנבחר מהאובייקט DatePickerState ומעצבת אותו באמצעות הפונקציה convertMillisToDate.
  • התאריך שנבחר מופיע בשדה הטקסט.
  • חלונית בחירת התאריכים המעוגנת ממוקמת מתחת לשדה הטקסט באמצעות offset modifier.
  • משתמשים ב-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 מציגה חלונית מודאלית לבחירת תאריך.
  • ביטוי ה-lambda‏ onDateSelected מופעל כשהמשתמש בוחר תאריך.
    • הוא חושף את התאריך שנבחר לרכיב הניתן להרכבה ברמת ההורה.
  • ביטוי ה-lambda‏ 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 יוצר מאגר של תיבת דו-שיח מודאלית.
  • ב-handler של לחצן האישור, הפונקציה 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", 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?> או במחלקת נתונים כדי לתעד את ערכי ההתחלה והסיום.

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