安卓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
相关产品推荐
相关产品推荐

