You need to enable JavaScript to run this app. 安卓Kotlin开发中如何将DatePickerDialog默认日期设为用户上次选择的日期
优惠活动
大模型
产品
解决方案
定价
更多

安卓Kotlin开发中如何将DatePickerDialog默认日期设为用户上次选择的日期

DatePickerDialog 重复打开时默认日期重置问题

问题描述

在Fragment中创建DatePickerDialog,首次打开时默认设置为当前日期,用户选择日期后关闭弹窗,选中值会赋值给EditText。但再次打开弹窗时仍然显示当前默认日期,需要实现再次打开时直接展示上次选中的日期。

现有相关代码

DatePickerFragment

class DatePickerFragment : DialogFragment(),
    DatePickerDialog.OnDateSetListener {

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val c = Calendar.getInstance()
        val year = c.get(Calendar.YEAR)
        val month = c.get(Calendar.MONTH)
        val day = c.get(Calendar.DAY_OF_MONTH)
        val datePickerDialog = DatePickerDialog(requireContext(), this, year, month, day)
        datePickerDialog.datePicker.maxDate = c.timeInMillis
        return datePickerDialog
    }

    override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) {
        val date = DateUtils.formatToBirthdayString(year, month, dayOfMonth)
        parentFragmentManager.setFragmentResult(
            KEY_BIRTH_DATE,
            Bundle().apply { putString(KEY_BIRTH_DATE, date) }
        )
    }
}

BirthdayDialogCommand

class BirthdayDialogCommand(private val fragmentManager: FragmentManager) : Command {

    override fun execute() {
        val picker = DatePickerFragment()

        if (fragmentManager.findFragmentByTag(BirthdayDialogCommand::class.java.simpleName) == null) {
            picker.show(fragmentManager, BirthdayDialogCommand::class.java.simpleName)
        }
    }
}

DetailsFragment 相关实现

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View {
    
    setDataBinding()
    setDataListeners()

    return binding.root
}

private fun setDataBinding() {
    binding.apply {
        birthdayCommand = BirthdayDialogCommand(
            parentFragmentManager
        )
    }
}

private fun setDataListeners() {
    parentFragmentManager.setFragmentResultListener(
        KEY_BIRTH_DATE,
        viewLifecycleOwner,
        patientDataListener
    )
}

private val patientDataListener = FragmentResultListener { _, bundle ->
    bundle.getString(KEY_BIRTH_DATE)?.let {
        binding.birthday.text = it
        patientViewModel.setPatientBirthday(it)
    }
}

问题根因

  • DatePickerFragment每次创建弹窗时都固定读取Calendar.getInstance()的当前时间作为初始日期,没有接收外部传入的历史选中日期参数。
  • BirthdayDialogCommand每次执行都新建无参的DatePickerFragment实例,没有携带上次的日期数据。
  • 选中的日期格式化后只存了字符串,没有保留原始年月日数值用于下次弹窗初始化。

解决方案

1. 改造DatePickerFragment,支持接收初始日期参数

通过静态newInstance方法封装参数传递,避免直接构造Fragment实例,同时兼容Fragment状态重建:

class DatePickerFragment : DialogFragment(),
    DatePickerDialog.OnDateSetListener {

    // 定义参数存储Key
    private val ARG_INIT_YEAR = "arg_init_year"
    private val ARG_INIT_MONTH = "arg_init_month"
    private val ARG_INIT_DAY = "arg_init_day"

    companion object {
        /**
         * @param initYear 上次选中的年,传null则默认使用当前年
         * @param initMonth 上次选中的月,传null则默认使用当前月
         * @param initDay 上次选中的日,传null则默认使用当前日
         */
        fun newInstance(initYear: Int?, initMonth: Int?, initDay: Int?): DatePickerFragment {
            return DatePickerFragment().apply {
                arguments = Bundle().apply {
                    val defaultCalendar = Calendar.getInstance()
                    putInt(ARG_INIT_YEAR, initYear ?: defaultCalendar.get(Calendar.YEAR))
                    putInt(ARG_INIT_MONTH, initMonth ?: defaultCalendar.get(Calendar.MONTH))
                    putInt(ARG_INIT_DAY, initDay ?: defaultCalendar.get(Calendar.DAY_OF_MONTH))
                }
            }
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        // 从arguments中读取初始日期,避免硬编码使用当前时间
        val year = arguments?.getInt(ARG_INIT_YEAR) ?: Calendar.getInstance().get(Calendar.YEAR)
        val month = arguments?.getInt(ARG_INIT_MONTH) ?: Calendar.getInstance().get(Calendar.MONTH)
        val day = arguments?.getInt(ARG_INIT_DAY) ?: Calendar.getInstance().get(Calendar.DAY_OF_MONTH)
        
        val maxDateCalendar = Calendar.getInstance()
        val datePickerDialog = DatePickerDialog(requireContext(), this, year, month, day)
        datePickerDialog.datePicker.maxDate = maxDateCalendar.timeInMillis
        return datePickerDialog
    }

    override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) {
        val date = DateUtils.formatToBirthdayString(year, month, dayOfMonth)
        parentFragmentManager.setFragmentResult(
            KEY_BIRTH_DATE,
            Bundle().apply {
                putString(KEY_BIRTH_DATE, date)
                // 额外返回原始年月日数值,用于下次弹窗初始化
                putInt(KEY_BIRTH_YEAR, year)
                putInt(KEY_BIRTH_MONTH, month)
                putInt(KEY_BIRTH_DAY, dayOfMonth)
            }
        )
    }
}

注:新增的KEY_BIRTH_YEAR、KEY_BIRTH_MONTH、KEY_BIRTH_DAY需与原有KEY_BIRTH_DATE定义在同一常量类中,保证全局统一。

2. 改造BirthdayDialogCommand,支持传入初始日期

class BirthdayDialogCommand(
    private val fragmentManager: FragmentManager,
    private val initYear: Int? = null,
    private val initMonth: Int? = null,
    private val initDay: Int? = null
) : Command {

    override fun execute() {
        // 仅当弹窗不存在时才新建,避免重复创建实例
        if (fragmentManager.findFragmentByTag(BirthdayDialogCommand::class.java.simpleName) == null) {
            val picker = DatePickerFragment.newInstance(initYear, initMonth, initDay)
            picker.show(fragmentManager, BirthdayDialogCommand::class.java.simpleName)
        }
    }
}

3. DetailsFragment 缓存历史日期,打开弹窗时传入

在DetailsFragment中新增变量缓存上次选中的年月日,接收弹窗返回结果时更新缓存,打开弹窗时把缓存值传入Command:

// 在DetailsFragment类中定义缓存变量
private var savedBirthYear: Int? = null
private var savedBirthMonth: Int? = null
private var savedBirthDay: Int? = null

private fun setDataBinding() {
    binding.apply {
        // 初始化Command时传入缓存的日期,无缓存则默认用当前时间
        birthdayCommand = BirthdayDialogCommand(
            parentFragmentManager,
            savedBirthYear,
            savedBirthMonth,
            savedBirthDay
        )
    }
}

private val patientDataListener = FragmentResultListener { _, bundle ->
    bundle.getString(KEY_BIRTH_DATE)?.let {
        binding.birthday.text = it
        patientViewModel.setPatientBirthday(it)
        // 更新缓存的原始年月日
        savedBirthYear = bundle.getInt(KEY_BIRTH_YEAR)
        savedBirthMonth = bundle.getInt(KEY_BIRTH_MONTH)
        savedBirthDay = bundle.getInt(KEY_BIRTH_DAY)
        // 重新绑定Command,确保下次打开弹窗时携带最新缓存的日期
        binding.birthdayCommand = BirthdayDialogCommand(
            parentFragmentManager,
            savedBirthYear,
            savedBirthMonth,
            savedBirthDay
        )
    }
}

优化建议

  • 若需要配置变更(如旋转屏幕、进程被系统杀死重建)后仍能保留选中日期,可将缓存的年月日存入ViewModel或本地SP,DetailsFragment初始化时先从存储中读取初始值,避免内存缓存丢失。
  • 不要直接构造DatePickerFragment的无参实例,所有参数必须通过arguments传递,防止Fragment重建时参数丢失。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.27 00:15:05