日期选择器

日期选择器 允许用户选择一个日期、一个日期范围或两者。它们使用日历对话框或文本输入来允许用户选择日期。

类型

有三种类型的日期选择器

  • 停靠式:显示在布局中的内联区域。它适用于紧凑型布局,在这种布局中,专用对话框可能会显得过于突兀。
  • 模态:以对话框的形式显示,覆盖应用程序内容。这将为日期选择提供清晰的焦点。
  • 模态输入:将文本字段与模态日期选择器结合在一起。

您可以使用以下可组合项在您的应用中实现这些日期选择器

  • DatePicker: 用于日期选择器的通用可组合项。您使用的容器决定它是停靠式还是模态式。
  • DatePickerDialog: 用于模态和模态输入日期选择器的容器。
  • DateRangePicker: 用于任何日期选择器,用户可以在其中选择一个具有开始日期和结束日期的范围。

状态

不同的日期选择器可组合项共有的关键参数是 state,它接受 DatePickerStateDateRangePickerState 对象。它们的属性使用日期选择器捕获有关用户选择的信息,例如当前选择的日期。

有关如何使用所选日期的更多信息,请参阅 使用所选日期部分

停靠式日期选择器

在以下示例中,有一个文本字段提示用户输入他们的出生日期。当他们点击字段中的日历图标时,将在输入字段下方打开一个停靠式日期选择器。

@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
                    )
                }
            }
        }
    }
}

fun convertMillisToDate(millis: Long): String {
    val formatter = SimpleDateFormat("MM/dd/yyyy", Locale.getDefault())
    return formatter.format(Date(millis))
}

关于代码的关键点

  • 当用户点击 IconButton 时,日期选择器将出现。
    • 图标按钮作为 OutlinedTextFieldtrailingIcon 参数的参数。
    • 状态变量 showDatePicker 控制停靠式日期选择器的可见性。
  • 日期选择器的容器是 Popup 可组合项,它覆盖内容,而不影响其他元素的布局。
  • selectedDateDatePickerState 对象中捕获所选日期的值,并使用 convertMillisToDate 函数对其进行格式化。
  • 所选日期显示在文本字段中。
  • 停靠式日期选择器使用 offset 修饰符定位在文本字段下方。
  • 一个 Box 用作根容器,以允许文本字段和日期选择器正确分层。

结果

点击日历图标后,此实现将显示如下

Docked date picker example.
图 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 将执行。

结果

此实现将显示如下

Modal date picker example.
图 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
Modal date picker with 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 用作对话框内容。

结果

此实现将显示如下

Modal range date picker example.
图 4. 带有选定范围的模态日期选择器。

使用所选日期

要捕获所选日期,请在父可组合项中将其跟踪为 Long 并将值传递给 onDateSelected 中的 DatePicker。以下代码片段演示了这一点,但是您可以在 官方代码片段应用 中看到完整实现。

// ...
    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?> 或数据类来捕获开始值和结束值。

另请参阅