日期选择器

借助日期选择器,用户可以选择日期、日期范围或两者兼选。它们使用日历对话框或文本输入来让用户选择日期。

类型

日期选择器有三种类型:

  • 停靠:内嵌在布局中。它适用于紧凑型布局,在紧凑型布局中,专用对话框可能会显得突兀。
  • 模态:以对话框的形式显示,叠加在应用的内容上。这样可以清晰地突出显示日期选择。
  • 模态输入:将文本字段与模态日期选择器相结合。

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

  • 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  )  }  }  }  } } @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 可组合项,它会覆盖内容,而不会影响其他元素的布局。
  • selectedDateDatePickerState 对象中捕获所选日期的值,并使用 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 可组合函数会显示一个模态日期选择器。
  • 当用户选择日期时,系统会执行 onDateSelected lambda 表达式。
    • 它将所选日期公开给父可组合项。
  • 当用户关闭对话框时,系统会执行 onDismiss lambda 表达式。

结果

此实现如下所示:

模态日期选择器示例。
图 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 进行跟踪,并将该值传递给 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?> 或数据类来捕获开始值和结束值。

另请参阅