切换NumberPicker选项触发ArrayIndexOutOfBoundsException问题求助
问题:联动NumberPicker切换时触发ArrayIndexOutOfBoundsException崩溃
切换第一个NumberPicker从“5 han”回到“4 han”时,应用崩溃,抛出异常:
java.lang.ArrayIndexOutOfBoundsException: length=1; index=1 at android.widget.NumberPicker.ensureCachedScrollSelectorValue(NumberPicker.java:2010) at android.widget.NumberPicker.initializeSelectorWheelIndices(NumberPicker.java:1822) at android.widget.NumberPicker.setMaxValue(NumberPicker.java:1520) at com.vincent.majcal.ui.picker.PickerFragment.onCreateView$lambda$1(PickerFragment.kt:114)
相关实现代码:
package com.vincent.majcal.ui.picker import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.NumberPicker import android.widget.Switch import android.widget.TextView import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import com.vincent.majcal.databinding.FragmentPickerTestBinding import com.vincent.majcal.databinding.FragmentSearchBinding class PickerFragment : Fragment() { private var _binding: FragmentPickerTestBinding? = null // This property is only valid between onCreateView and // onDestroyView. private val binding get() = _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { val pickerViewModel = ViewModelProvider(this).get(PickerViewModel::class.java) _binding = FragmentPickerTestBinding.inflate(inflater, container, false) val root: View = binding.root val textView: TextView = binding.textSearch val firstPicker: NumberPicker = binding.firstPicker val secondPicker: NumberPicker = binding.secondPicker var fan = "1 han" val selections = mapOf( "1 han" to arrayOf( "30", "40", "50", "60", "70", "80", "90", "100", "110" ), "2 han" to arrayOf( "20", "25", "30", "40", "50", "60", "70", "80", "90", "100", "110" ), "3 han" to arrayOf( "20", "25", "30", "40", "50", "60", "70+", ), "4 han" to arrayOf( "20", "25", "30", "40+", ), "5 han" to arrayOf( "Mangan", ), "6 ~ 7 han" to arrayOf( "Haneman", ), "8 ~ 10 han" to arrayOf( "Baiman", ), "11 ~ 12 han" to arrayOf( "Sanbaiman", ), "13 han+" to arrayOf( "Kazoe Yakuman", ) ) firstPicker.minValue = 0 firstPicker.maxValue = selections.keys.size - 1 firstPicker.displayedValues = selections.keys.toTypedArray() firstPicker.wrapSelectorWheel = false secondPicker.wrapSelectorWheel = false secondPicker.minValue = 0 // default the max value to the first Picker value set var secondDisplayArray = selections[fan] if (secondDisplayArray != null) { secondPicker.maxValue = secondDisplayArray.size - 1 } secondPicker.displayedValues = secondDisplayArray firstPicker.setOnValueChangedListener { picker, oldVal, newVal -> run { fan = selections.keys.elementAt(newVal) secondDisplayArray = selections[fan] if (secondDisplayArray != null) { var secondDisplayArraySize: Int = secondDisplayArray!!.size secondPicker.value = 0 secondPicker.maxValue = secondDisplayArraySize - 1 //the Logcat show this line cause the exception secondPicker.displayedValues = secondDisplayArray } } } pickerViewModel.text.observe(viewLifecycleOwner) { textView.text = it } return root } override fun onDestroyView() { super.onDestroyView() _binding = null } }
原因分析
崩溃核心是NumberPicker内部状态不同步:切换时先设置secondPicker.value = 0,此时第二个Picker的displayedValues还是“5 han”对应的长度为1的数组,Picker内部缓存了旧状态。紧接着设置maxValue时,Picker尝试更新选择器轮盘索引,错误使用旧数组长度计算,导致索引越界。
解决方法
调整第二个Picker属性的修改顺序,先更新displayedValues,再设置maxValue,最后设置value,确保内部状态始终同步:
修改firstPicker.setOnValueChangedListener中的代码:
firstPicker.setOnValueChangedListener { picker, oldVal, newVal -> fan = selections.keys.elementAt(newVal) secondDisplayArray = selections[fan] if (secondDisplayArray != null) { val secondDisplayArraySize = secondDisplayArray!!.size // 顺序调整:先更新选项数组,再设置最大值,最后重置选中值 secondPicker.displayedValues = secondDisplayArray secondPicker.maxValue = secondDisplayArraySize - 1 secondPicker.value = 0 } }
同时,初始化第二个Picker时也建议保持相同顺序,避免初始化阶段的潜在问题:
var secondDisplayArray = selections[fan] if (secondDisplayArray != null) { secondPicker.displayedValues = secondDisplayArray secondPicker.maxValue = secondDisplayArray.size - 1 secondPicker.value = 0 }
额外说明
NumberPicker的API对属性修改顺序有隐含要求:关联displayedValues时,必须先设置选项数组,再调整minValue/maxValue,最后设置选中值,这样Picker才能正确计算内部选择器状态,避免状态不同步引发的崩溃。
内容的提问来源于stack exchange,提问作者Vincent Zheng
相关产品推荐
相关产品推荐

