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

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

انواع

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

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

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

  • 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 به‌عنوان محفظه ریشه استفاده می‌شود تا لایه‌بندی مناسب فیلد نوشتار و انتخابگر تاریخ امکان‌پذیر شود.

نتایج

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

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

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

نتایج

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

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

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

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

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