انتخاب‌گرهای تاریخ

انتخاب‌گرهای تاریخ به کاربران امکان می‌دهند تاریخ، محدوده تاریخ، یا هر دو را انتخاب کنند. آن‌ها از کادر گفتگوی تقویم یا ورودی نوشتار استفاده می‌کنند تا به کاربران امکان دهند تاریخ‌ها را انتخاب کنند.

انواع

سه نوع انتخابگر تاریخ وجود دارد:

  • روی پایه اتصال: به‌صورت درون‌خطی در چیدمان ظاهر می‌شود. برای چیدمان‌های فشرده که در آن‌ها کادر گفتگوی اختصاصی ممکن است مزاحم به‌نظر برسد مناسب است.
  • مشروط: به‌عنوان کادر گفتگویی که روی محتوای برنامه قرار می‌گیرد ظاهر می‌شود. این کار تمرکز واضحی بر انتخاب تاریخ ایجاد می‌کند.
  • ورودی مشروط: فیلد نوشتاری را با انتخابگر تاریخ مشروط ترکیب می‌کند.

بااستفاده از عناصر ترکیبی زیر می‌توانید این انتخابگرهای تاریخ را در برنامه‌تان پیاده‌سازی کنید:

  • 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 کلیک می‌کند، انتخابگر تاریخ ظاهر می‌شود.
    • دکمه نماد به‌عنوان آرگومان برای پارامتر OutlinedTextFieldtrailingIcon عمل می‌کند.
    • متغیر وضعیت showDatePicker رؤیت‌پذیری انتخابگر تاریخ چسبیده را کنترل می‌کند.
  • ظرف انتخابگر تاریخ یک عنصر ترکیبی Popup است که محتوا را بدون تأثیر گذاشتن بر چیدمان عناصر دیگر رونهاد می‌کند.
  • ‫selectedDate مقدار تاریخ انتخاب‌شده را از شیء DatePickerState می‌گیرد و آن را بااستفاده از تابع convertMillisToDate قالب‌بندی می‌کند.
  • تاریخ انتخاب‌شده در فیلد نوشتاری نشان داده می‌شود.
  • انتخاب‌گر تاریخ لنگرشده بااستفاده از offset اصلاح‌گر در زیر فیلد نوشتاری قرار می‌گیرد.
  • از Box به‌عنوان محتوی ریشه استفاده می‌شود تا لایه‌بندی صحیح فیلد نوشتاری و انتخابگر تاریخ امکان‌پذیر شود.

نتایج

پس‌از کلیک کردن روی نماد تقویم، این پیاده‌سازی به‌صورت زیر نشان داده می‌شود:

مثال انتخاب‌گر تاریخ چسبیده.
شکل ۱. انتخاب‌گر تاریخ چسبیده.

گزینشگر تاریخ مُدال کادر گفتگویی را نمایش می‌دهد که روی صفحه شناور است. برای پیاده‌سازی آن، 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 اجرا می‌شود.

نتایج

این پیاده‌سازی به‌صورت زیر نشان داده می‌شود:

نمونه انتخاب‌گر تاریخ مشروط.
شکل ۲. انتخاب‌گر تاریخ مُدال.

انتخاب‌گر تاریخ ورودی مُدال

گزینشگر تاریخ مشروط با ورودی کادر گفتگویی را نمایش می‌دهد که روی صفحه شناور است و به کاربر امکان می‌دهد تاریخ وارد کند.

@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 تنظیم می‌کند.
انتخاب‌گر تاریخ مشروط با ورودی.
شکل ۳. انتخاب‌گر تاریخ مشروط با ورودی.

انتخاب‌گر تاریخ با محدوده

می‌توانید انتخاب‌گر تاریخی بسازید که به کاربر امکان دهد محدوده‌ای بین تاریخ شروع و پایان را انتخاب کند. برای انجام این کار، از DateRangePicker استفاده کنید.

استفاده از DateRangePicker اساساً همان DatePicker است. می‌توانید از آن برای انتخاب‌گر چسبیده به‌عنوان فرزند PopUp استفاده کنید، یا می‌توانید از آن به‌عنوان انتخاب‌گر مشروط استفاده کنید و آن را به DatePickerDialog منتقل کنید. تفاوت اصلی این است که به‌جای DatePickerState از DateRangePickerState استفاده می‌کنید.

تکه‌کد زیر نحوه ایجاد انتخابگر تاریخ مشروط با محدوده را نشان می‌دهد:

@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 به‌عنوان محتوای کادر گفتگو عمل می‌کند.

نتایج

این پیاده‌سازی به‌صورت زیر نشان داده می‌شود:

نمونه انتخابگر تاریخ محدوده مشروط.
شکل ۴. انتخاب‌گر تاریخ مشروط با محدوده انتخابی.

استفاده از تاریخ انتخاب‌شده

برای ضبط تاریخ انتخاب‌شده، آن را در عنصر ترکیبی والد به‌عنوان 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?> یا کلاس داده برای گرفتن مقادیر شروع و پایان استفاده کنید.

همچنین مراجعه کنید به