日期挑選器

日期挑選器可讓使用者選取日期、日期範圍或兩者。這些控制項會使用日曆對話方塊或文字輸入,讓使用者選取日期。

類型

日期挑選器分為三種類型:

  • 已插入座架:內嵌顯示在版面配置中。適合用於緊湊的版面配置,以免專屬對話方塊顯得突兀。
  • 模式:以對話方塊形式顯示,疊加在應用程式內容上。這樣可清楚聚焦於日期選取作業。
  • 模式輸入:結合文字欄位和模式日期挑選器。

您可以使用下列可組合函式,在應用程式中導入這些日期挑選器:

  • 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 可組合項,會覆蓋內容,但不會影響其他元素的版面配置。
  • selectedDate 會擷取 DatePickerState 物件中選取日期的值,並使用 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?> 或資料類別來擷取開始和結束值。

另請參閱