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

DatePickerDialog更新selectableDays时重置回默认月份问题求解

问题描述

使用com.wdullaer.materialdatetimepicker.date包下的DatePickerDialog组件时,通过API接口填充selectableDays可选日期数据。由于对接的API单次仅支持查询单月的可选日期,因此需要在用户点击月份切换箭头、跳转至下一月时,拉取对应月份的可选日期补充到可选日期列表中。

月份切换箭头

当前实现方案是维护一个全局Calendar集合,将接口返回的新增可选日期追加到该集合中:

val selectableCalendars = HashSet<Calendar>() // 存储可预约日期(含时间)的集合

该方案基础运行逻辑正常,但当DatePickerDialog处于打开状态时,执行如下代码更新可选日期列表:

datePickerDialog.selectableDays = selectableCalendars.toTypedArray()

DatePickerDialog会直接跳转回日历列表中的第一个月份。

这种“跳转回最早可选日期所在月份”的非预期行为仅在同时满足以下两个条件时触发:

  1. 刚完成datePickerDialog.selectableDays的新值赋值操作后立即触发;
  2. 用户滑动进入的目标月份没有可选日期(若返回的该月存在可选日期则不会触发该bug)。

可行临时解决方案

该问题是组件内部固有逻辑导致的:setSelectableDays方法执行时会自动校验当前显示月份是否存在可选日期,若不存在则直接定位到可选日期数组中时间最早的月份,不会保留用户当前停留的月份状态。以下是3种不需要修改库源码即可落地的方案,适配4.2.3稳定版:

  • 预占位方案(体验最优,无闪屏)
    拉取目标月份接口数据前,先记录用户当前停留的年、月值,往selectableCalendars集合中临时添加一个对应当月1号的Calendar对象(注意将时分秒、毫秒值清零,和集合内其他Calendar的时间精度对齐),避免组件判定当前月无可用日期触发跳转。等接口返回真实数据后,移除占位对象、追加真实返回的日期,再重新给selectableDays赋值即可。
    配套加点击拦截:给对话框设置setOnDateClickListener,如果用户点击的日期和占位日期年月日完全一致,直接返回false禁止选中即可,用户完全感知不到占位数据的存在。
    核心实现代码:
    // 月份切换触发时,先获取当前停留的年月
    val curYear = datePickerDialog.year
    val curMonth = datePickerDialog.month
    // 生成当月1号的占位日期
    val placeholder = Calendar.getInstance().apply {
        set(curYear, curMonth, 1, 0, 0, 0)
        set(Calendar.MILLISECOND, 0)
    }
    // 先加入占位,更新可选列表
    selectableCalendars.add(placeholder)
    datePickerDialog.selectableDays = selectableCalendars.toTypedArray()
    
    // 发起接口请求拉取当月真实可选日期
    repo.fetchAvailableDates(curYear, curMonth) { realDates ->
        // 移除占位,追加真实数据
        selectableCalendars.remove(placeholder)
        selectableCalendars.addAll(realDates)
        datePickerDialog.selectableDays = selectableCalendars.toTypedArray()
    }
    
    如果接口返回当月确实没有可选日期,可以选择保留占位,在点击拦截里给用户弹个“当月无可预约日期”的提示即可,不会出现跳月问题。
  • 赋值后主动重置月份(实现最简单)
    如果不想维护占位数据,可以在赋值新的可选日期列表前,先记录当前停留的年、月、日,赋值操作完成后,通过post方法把切回原月份的操作投递到主线程消息队列尾部,等组件内部跳首月的逻辑执行完后,再把显示状态切回用户之前停留的月份即可。
    核心实现代码:
    // 记录当前停留位置
    val targetYear = datePickerDialog.year
    val targetMonth = datePickerDialog.month
    val targetDay = datePickerDialog.dayOfMonth
    // 更新可选日期
    datePickerDialog.selectableDays = selectableCalendars.toTypedArray()
    // 等组件内部逻辑执行完再切回原位置
    datePickerDialog.post {
        datePickerDialog.updateDate(targetYear, targetMonth, targetDay.coerceAtLeast(1))
    }
    
    该方案的唯一缺陷是部分低性能机型上可能出现极短暂的首月闪屏,对体验要求不高的场景可以直接使用。
  • 反射拦截内部跳转逻辑(无侵入无副作用)
    如果前两种方案都无法满足需求,可以通过反射拿到DatePickerDialog内部的mController实例,拦截其自动跳转至首个可选日期的逻辑。该方案体验和原生一致,缺点是不同版本的库内部字段名可能有变动,升级库版本时需要做兼容校验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 15:45:42