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

有没有更简洁的方式获取DatePickerDialog的选中值?

用协程实现可复用的日期/时间选择器挂起函数

核心思路是利用suspendCancellableCoroutine将回调式的Dialog转为挂起函数,同时确保Dialog的创建、显示全流程在UI线程执行,解决回调繁琐的问题,同时避免线程异常。

1. 日期选择器挂起函数实现

在Fragment中编写如下可复用代码:

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import java.time.LocalDateTime

suspend fun showDatePickerDlg(yr: Int, mth: Int, day: Int): LocalDateTime {
    // 强制所有UI操作在主线程执行
    return withContext(Dispatchers.Main) {
        suspendCancellableCoroutine { cont ->
            val datePickerDialog = DatePickerDialog(requireActivity()) { _, year, monthOfYear, dayOfMonth ->
                // 用户选择日期后恢复协程,返回选中值(注意DatePicker月份是0-based,转成1-based后传入LocalDateTime)
                cont.resume(LocalDateTime.of(year, monthOfYear + 1, dayOfMonth, 0, 0), null)
            }
            // 设置初始日期,若传入的mth是1-based,这里直接传mth-1适配DatePicker的0-based规则
            datePickerDialog.datePicker.init(yr, mth, day, null)
            
            // 处理用户取消对话框的场景,避免协程长期挂起
            datePickerDialog.setOnCancelListener {
                cont.cancel()
            }
            
            datePickerDialog.show()
        }
    }
}

2. 时间选择器挂起函数实现

同理,时间选择器的可复用实现如下:

import java.time.LocalDateTime
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext

suspend fun showTimePickerDlg(hour: Int, minute: Int): LocalDateTime {
    return withContext(Dispatchers.Main) {
        suspendCancellableCoroutine { cont ->
            val timePickerDialog = TimePickerDialog(requireActivity()) { _, selectedHour, selectedMinute ->
                cont.resume(LocalDateTime.of(0, 1, 1, selectedHour, selectedMinute), null)
            }
            timePickerDialog.timePicker.setInitialHour(hour)
            timePickerDialog.timePicker.setInitialMinute(minute)
            
            timePickerDialog.setOnCancelListener {
                cont.cancel()
            }
            
            timePickerDialog.show()
        }
    }
}

3. 正确调用方式

禁止使用GlobalScope,它不与Fragment生命周期绑定,容易引发内存泄漏。应使用Fragment自带的lifecycleScope:

// 示例:在按钮点击事件中调用
btnPickDateTime.setOnClickListener {
    lifecycleScope.launch {
        try {
            // 注意:若初始月份是1-based,这里传入mth-1(比如5月传4)适配DatePicker规则
            val schedDate = showDatePickerDlg(2024, 4, 20)
            val schedTime = showTimePickerDlg(14, 30)
            
            // 合并日期与时间
            val fullDateTime = schedDate.withHour(schedTime.hour).withMinute(schedTime.minute)
            Log.i("TAG", "选中的完整时间: $fullDateTime")
        } catch (e: CancellationException) {
            // 用户取消选择时的逻辑处理
            Log.i("TAG", "用户取消了日期/时间选择")
        }
    }
}

你的代码为何无法显示对话框?

  1. 线程调度错误:GlobalScope.async默认在后台线程执行挂起函数,即使嵌套runOnUiThread显示Dialog,Dialog的创建过程仍在后台线程,违反Android UI操作规范,导致初始化异常。
  2. 协程作用域不安全:GlobalScope不受Fragment生命周期管理,可能在Fragment销毁后才执行,或调度优先级过低导致Dialog无法正常显示。
  3. 月份参数适配错误:DatePickerDialog的月份参数是0-based,若你传入1-based的月份值,会导致初始日期异常,甚至Dialog显示异常。

内容的提问来源于stack exchange,提问作者rminaj

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 13:02:34