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

Android TimePicker切换分钟间隔报ArrayIndexOutOfBoundsException

问题原因
  • 核心崩溃触发逻辑:当小时从23滑动切到0的瞬间,TimePicker内部的分钟NumberPicker还保留着切换前的选中索引值0(对应非0点模式下的00分选项)。此时调用setMinuteIntervalWithOutZero直接把分钟选择器的最小值改为1、显示数组替换为长度11的["05","10",…,"55"],旧的选中值0小于新设置的最小值1,NumberPicker在内部刷新/滚动收尾阶段会用这个旧索引直接访问新的显示数组,触发ArrayIndexOutOfBoundsException。这个异常是系统组件内部布局/滚动流程抛出的,不在写在onTimeChanged里的try-catch捕获范围内,所以无法拦截。
  • 原有代码存在的基础bug:非0点模式的setMinuteInterval方法中,setMaxValue参数写为60 / INTERVAL是错误的。5分钟间隔下0055共12个选项,对应索引范围011,设置maxValue为12(即60/5的结果)本身就存在数组越界隐患,只是之前场景没有触发。
修复方案

按以下步骤调整代码即可解决崩溃:

  1. 修正NumberPicker的maxValue计算逻辑,保证索引范围和显示数组长度完全匹配
  2. 切换分钟选项配置前,先修正当前选中值到新的合法区间,避免旧选中值落在新范围外触发组件内部越界
  3. 更新配置时临时解绑时间变更监听,避免配置修改过程中触发递归回调
  4. 配置更新过程中临时禁用分钟选择器,阻断组件内部的无效刷新流程

修复后完整代码

首先首先定义固定步长常量:

private static final int INTERVAL = 5;
private int hour, minute;
private NumberPicker minutePicker;

非0点模式分钟配置(含00分选项)

private void setMinuteInterval(TimePicker tp) {
    try {
        minutePicker = (NumberPicker) tp.findViewById(Resources.getSystem().getIdentifier("minute", "id", "android"));
        minutePicker.setEnabled(false);
        
        List<String> displayedValues = new ArrayList<>();
        for (int i = 0; i < 60; i += INTERVAL) {
            displayedValues.add(String.format("%02d", i));
        }
        // 修正maxValue:12个选项对应索引0~11
        minutePicker.setMinValue(0);
        minutePicker.setMaxValue((60 / INTERVAL) - 1);
        minutePicker.setDisplayedValues(displayedValues.toArray(new String[0]));
        
        minutePicker.setEnabled(true);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

0点模式分钟配置(不含00分选项)

private void setMinuteIntervalWithOutZero(TimePicker timePicker) {
    try {
        minutePicker = (NumberPicker) timePicker.findViewById(Resources.getSystem().getIdentifier("minute", "id", "android"));
        minutePicker.setEnabled(false);
        
        // 提前修正选中值:如果当前选中索引为0(对应00分),直接调整为1(对应05分),避免值小于新的minValue
        int currentVal = minutePicker.getValue();
        if (currentVal < 1) {
            minutePicker.setValue(1);
        }
        
        List<String> displayedValues = new ArrayList<>();
        for (int i = 5; i < 60; i += INTERVAL) {
            displayedValues.add(String.format("%02d", i));
        }
        minutePicker.setMinValue(1);
        minutePicker.setMaxValue((60 / INTERVAL) - 1);
        minutePicker.setDisplayedValues(displayedValues.toArray(new String[0]));
        
        minutePicker.setEnabled(true);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

时间变更监听逻辑

private TimePicker.OnTimeChangedListener mChangeTimeListener = new TimePicker.OnTimeChangedListener() {
    @Override
    public void onTimeChanged(TimePicker tp, int selectedHour, int selectedMinute) {
        hour = selectedHour;
        minute = selectedMinute;

        try{
            // 临时解绑监听,避免配置更新触发递归回调
            tp.setOnTimeChangedListener(null);
            if (hour != 0) {
                setMinuteInterval(tp);
            } else {
                setMinuteIntervalWithOutZero(tp);
            }
        }catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 无论是否异常都重新绑定监听
            tp.setOnTimeChangedListener(this);
        }
    }
};

初始化逻辑

TimePicker tp = findViewById(R.id.tp); // 替换为实际的TimePicker初始化逻辑
tp.setHour(0);
tp.setIs24HourView(true);
setMinuteIntervalWithOutZero(tp);
// 初始默认选中05分,对应索引1
tp.setMinute(1);
tp.setOnTimeChangedListener(mChangeTimeListener);

注意:通过反射获取系统内部NumberPicker的方案,在部分定制ROM、特殊系统版本上可能存在id不匹配的兼容问题,如果需要覆盖全机型全版本,建议直接使用自定义时间选择组件,或基于AndroidX Material库的MaterialTimePicker做二次定制,稳定性更高。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 23:09:18