خرماچیان

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

انواع

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

  • متصل (Docked ): به صورت درون‌خطی در طرح‌بندی ظاهر می‌شود. این گزینه برای طرح‌بندی‌های فشرده که در آن‌ها یک کادر محاوره‌ای اختصاصی ممکن است مزاحم به نظر برسد، مناسب است.
  • Modal : به صورت یک کادر محاوره‌ای که محتوای برنامه را پوشش می‌دهد، ظاهر می‌شود. این کادر تمرکز واضحی بر انتخاب تاریخ دارد.
  • ورودی مودال : یک فیلد متنی را با یک انتخابگر تاریخ مودال ترکیب می‌کند.

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

  • DatePicker : یک کامپوننت عمومی برای انتخابگر تاریخ. کانتینری که استفاده می‌کنید تعیین می‌کند که آیا متصل است یا مدل.
  • DatePickerDialog : ظرفی برای انتخابگرهای تاریخ ورودی modal و modal.
  • 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 به عنوان ظرف ریشه (root container) استفاده می‌شود تا امکان لایه‌بندی مناسب فیلد متن و انتخابگر تاریخ (date picker) فراهم شود.

نتایج

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

مثال انتخابگر تاریخ متصل.
شکل ۱. یک انتخابگر تاریخ متصل.

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

این بسیار شبیه به مثال انتخابگر تاریخ modal است. تفاوت اصلی به شرح زیر است:

  • پارامتر initialDisplayMode حالت نمایش اولیه را روی DisplayMode.Input تنظیم می‌کند.
انتخابگر تاریخ مودال با ورودی.
شکل ۳. یک انتخابگر تاریخ مودال با ورودی.

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

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

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

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

@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() وضعیت (state) را برای انتخابگر محدوده تاریخ ایجاد می‌کند.
  • DatePickerDialog یک کادر محاوره‌ای modal ایجاد می‌کند.
  • در کنترل‌کننده‌ی 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", 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?> یا یک کلاس داده استفاده کنید.

همچنین ببینید