منتقي التاريخ

تتيح أدوات اختيار التاريخ للمستخدمين اختيار تاريخ أو نطاق زمني أو كليهما. تستخدم مربّع حوار التقويم أو إدخال النص للسماح للمستخدمين بتحديد التواريخ.

الأنواع

هناك ثلاثة أنواع من أدوات اختيار التاريخ:

  • مثبَّت: يظهر مضمّنًا في التنسيق. وهو مناسب للتنسيقات المدمجة التي قد يبدو فيها مربّع الحوار المخصّص مزعجًا.
  • مشروط: يظهر كمربّع حوار يغطي محتوى التطبيق. ويوفّر ذلك تركيزًا واضحًا على اختيار التاريخ.
  • الإدخال في نافذة مشروطة: يجمع بين حقل نصي وأداة اختيار التاريخ في نافذة مشروطة.

يمكنك تنفيذ أدوات اختيار التاريخ هذه في تطبيقك باستخدام الدوال المركّبة التالية:

  • ‫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 أداة اختيار التاريخ في نافذة مشروطة.
  • يتم تنفيذ تعبير 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 حاوية مربّع حوار مشروط.
  • في معالج 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?> أو فئة بيانات لتسجيل قيمتَي البداية والنهاية.

انظر أيضًا