You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何阻止Material3 TimeInput始终保持焦点状态?

问题

我正在实现一个自定义Dialog,将Material3的DatePicker与TimeInput整合在一起,让用户可一次性选择开始日期和时间。界面显示正常,但TimeInput始终会聚焦在小时或分钟输入框上,且无法取消该焦点:

  • 打开对话框时,小时输入框会被选中,键盘自动弹出
  • 点击TimePicker外部或关闭键盘都无法清除焦点

查了相关文档没找到关于这种焦点行为的说明,这看起来像是个Bug,想问下是否遗漏了阻止该行为的方法?

自定义DateTimeDialog代码

@Suppress("LongParameterList")
@Composable
fun DateTimeDialog(
    datePickerState: DatePickerState,
    timeInputState: TimePickerState,
    pickerType: CalendarPickerType,
    title: @Composable () -> Unit = { DefaultTitle() },
    headline: @Composable () -> Unit = { DefaultHeadline(state = datePickerState) },
    onDismissRequest: () -> Unit,
    onConfirmRequest: (DateTimeData) -> Unit,
    dismissButtonText: String = stringResource(id = R.string.cancel),
    confirmButtonText: String = stringResource(id = R.string.save)
) {
    Dialog(onDismissRequest = onDismissRequest) {
        Column(
            modifier = Modifier
                .background(
                    Theme.colors.backgroundPrimary,
                    RoundedCornerShape(Theme.spacing.spacing150)
                ),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            DatePicker(
                modifier = Modifier
                    .clip(RoundedCornerShape(Theme.spacing.spacing150)),
                title = title,
                state = datePickerState,
                headline = headline,
                dateValidator = { calendarValidator(pickerType, it) },
                showModeToggle = false,
                colors = datePickerColors()
            )

            TimeInput(
                state = timeInputState,
                colors = timePickerColors()
            )

            BottomButtons(
                confirmButtonEnabled = datePickerState.selectedDateMillis != null,
                onDismissRequest = onDismissRequest,
                onConfirmRequest = {
                    datePickerState.selectedDateMillis?.let {
                        onConfirmRequest(
                            DateTimeData(
                                dateEpochMilli = it,
                                timeHour = timeInputState.hour,
                                timeMinute = timeInputState.minute
                            )
                        )
                    }
                },
                dismissButtonText = dismissButtonText,
                confirmButtonText = confirmButtonText
            )
        }
    }
}
解决方案

1. 手动接管Dialog的焦点控制

通过FocusRequester让Dialog根布局抢占焦点,同时支持点击外部清除TimeInput的焦点,是最直接有效的方法:

修改后的代码如下:

@Suppress("LongParameterList")
@Composable
fun DateTimeDialog(
    datePickerState: DatePickerState,
    timeInputState: TimePickerState,
    pickerType: CalendarPickerType,
    title: @Composable () -> Unit = { DefaultTitle() },
    headline: @Composable () -> Unit = { DefaultHeadline(state = datePickerState) },
    onDismissRequest: () -> Unit,
    onConfirmRequest: (DateTimeData) -> Unit,
    dismissButtonText: String = stringResource(id = R.string.cancel),
    confirmButtonText: String = stringResource(id = R.string.save)
) {
    val rootFocusRequester = remember { FocusRequester() }
    
    Dialog(onDismissRequest = onDismissRequest) {
        Column(
            modifier = Modifier
                .background(
                    Theme.colors.backgroundPrimary,
                    RoundedCornerShape(Theme.spacing.spacing150)
                )
                .focusRequester(rootFocusRequester)
                .focusable()
                .pointerInput(Unit) {
                    detectTapGestures(onTap = {
                        // 点击Dialog空白区域时,请求根布局获取焦点
                        rootFocusRequester.requestFocus()
                    })
                },
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            DatePicker(
                modifier = Modifier
                    .clip(RoundedCornerShape(Theme.spacing.spacing150)),
                title = title,
                state = datePickerState,
                headline = headline,
                dateValidator = { calendarValidator(pickerType, it) },
                showModeToggle = false,
                colors = datePickerColors()
            )

            // 用可聚焦的Box包裹TimeInput,避免其自动抢占焦点
            Box(modifier = Modifier.focusable()) {
                TimeInput(
                    state = timeInputState,
                    colors = timePickerColors()
                )
            }

            BottomButtons(
                confirmButtonEnabled = datePickerState.selectedDateMillis != null,
                onDismissRequest = onDismissRequest,
                onConfirmRequest = {
                    datePickerState.selectedDateMillis?.let {
                        onConfirmRequest(
                            DateTimeData(
                                dateEpochMilli = it,
                                timeHour = timeInputState.hour,
                                timeMinute = timeInputState.minute
                            )
                        )
                    }
                },
                dismissButtonText = dismissButtonText,
                confirmButtonText = confirmButtonText
            )
        }
    }

    // Dialog初始化时,让根布局获取焦点,阻止TimeInput自动聚焦
    LaunchedEffect(Unit) {
        rootFocusRequester.requestFocus()
    }
}

2. 升级Material3库版本

部分旧版本的Material3(比如1.0.x系列)确实存在TimeInput自动聚焦且无法取消的Bug,官方在后续稳定版中已修复。可以尝试将Material3依赖升级到1.1.0及以上版本,验证问题是否消失。

3. 自定义TimeInput输入框焦点属性

如果上述方法无效,可以尝试自定义TimeInput的输入框修饰符,禁用自动聚焦:
不过这种方法需要重写TimeInput的内部组件,复杂度较高,仅作为备选方案:

// 示例:自定义TimeInput的小时输入框修饰符
TimeInput(
    state = timeInputState,
    colors = timePickerColors(),
    // 若TimeInput支持传入输入框修饰符(部分版本支持)
    hourTextFieldModifier = Modifier.focusProperties { 
        canFocus = false // 禁用自动聚焦,用户点击后仍可手动聚焦
    }
)

内容的提问来源于stack exchange,提问作者Scoobidy-Bop

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 16:44:52