NumberPicker分析(一)
NumberPicker分析(一)
NumberPicker
可实现连续滚动的字符串选择,其实现方式很有借鉴的意义
以最基本的使用方式为例,在layout中布局:
<NumberPickerandroid:id="@+id/number_picker"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintTop_toTopOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"/>
然后设置minValue
和maxValue
。(当然也可以设置DisplayedValues
,这里以最简单的使用方式为例)
mNumberPicker.setMaxValue(4);
mNumberPicker.setMinValue(0);
其显示效果如下:
分析下NumberPicker
构造方法(源码可参考NumberPicker.java)
mHasSelectorWheel = (layoutResId != DEFAULT_LAYOUT_RESOURCE_ID);
mHasSelectorWheel
表示 - 是否具有选择轮
如果在源码处添加一个debug的断点,会发现mHasSelectorWheel
结果是true
,即表示layoutResId
不是DEFAULT_LAYOUT_RESOURCE_ID
(R.layout.number_picker
)
通过官方文档对NumberPicker的介绍,可发现其style与主题有关:
- 如果当前主题是从
R.style.Theme
派生的,则小部件将当前值显示为可编辑的输入字段,上面有一个递增按钮,下面有一个递减按钮。 长按按钮可以快速更改当前值。 点击输入字段允许输入所需的值。- 如果当前主题是从
R.style.Theme_Holo
或R.style.Theme_Holo_Light
派生的,则小部件将当前值显示为可编辑的输入字段,上面的值较小,下面的值较大。 点击较小或较大的值,通过向上或向下动画数字轴来选择它,使所选值成为当前值。 向上或向下滑动允许当前值的多个增量或减量。 长按较小和较大的值也可以快速更改当前值。 点击当前值可以输入所需的值。- 如果当前主题是从
R.style.Theme_Material
派生的,则小部件将当前值显示为滚动的垂直选择器,所选值位于中心,前后数字由分隔符分隔。 通过垂直滑动来更改值。 可以使用R.attr.selectionDividerHeight
属性更改分隔线的厚度,可以使用R.attr.colorControlNormal
属性更改分隔线的颜色。
在android源码中搜索下number_picker.xml
相关的布局(frameworks/base/core/res/res/layout/number_picker.xml
)
Holo
主题中NumberPicker
定义如下(frameworks/base/core/res/res/values/styles_holo.xml)
:
<style name="Widget.Holo.NumberPicker" parent="Widget.NumberPicker"><item name="internalLayout">@layout/number_picker_with_selector_wheel</item><item name="solidColor">@color/transparent</item><item name="selectionDivider">@drawable/numberpicker_selection_divider</item><item name="selectionDividerHeight">2dip</item><item name="selectionDividersDistance">48dip</item><item name="internalMinWidth">64dip</item><item name="internalMaxHeight">180dip</item><item name="virtualButtonPressedDrawable">?attr/selectableItemBackground</item></style>
其internalLayout
对应的布局为number_picker_with_selector_wheel
number_picker_with_selector_wheel.xml
布局文件内容如下:
<merge xmlns:android="http://schemas.android.com/apk/res/android"><view class="android.widget.NumberPicker$CustomEditText"android:textAppearance="?android:attr/textAppearanceMedium"android:id="@+id/numberpicker_input"android:layout_width="fill_parent"android:layout_height="wrap_content"android:gravity="center"android:singleLine="true"android:background="@null" /></merge>
可见其中的view,是一个EditText
,类型是NumberPicker
的内部类CustomEditText
如果点击下上面创建的NumberPicker
的中间部分,会弹出键盘编辑值
setMinValue 和 setMaxValue
setMinValue
方法 和 setMaxValue
方法中逻辑有共通之处
/*** Sets the min value of the picker.** @param minValue The min value inclusive.** <strong>Note:</strong> The length of the displayed values array* set via {@link #setDisplayedValues(String[])} must be equal to the* range of selectable numbers which is equal to* {@link #getMaxValue()} - {@link #getMinValue()} + 1.*/public void setMinValue(int minValue) {if (mMinValue == minValue) {return;}if (minValue < 0) {throw new IllegalArgumentException("minValue must be >= 0");}mMinValue = minValue;if (mMinValue > mValue) {//设置了当前值mValue = mMinValue;}updateWrapSelectorWheel();initializeSelectorWheelIndices();updateInputTextView();tryComputeMaxWidth();invalidate();}/*** Sets the max value of the picker.** @param maxValue The max value inclusive.** <strong>Note:</strong> The length of the displayed values array* set via {@link #setDisplayedValues(String[])} must be equal to the* range of selectable numbers which is equal to* {@link #getMaxValue()} - {@link #getMinValue()} + 1.*/public void setMaxValue(int maxValue) {if (mMaxValue == maxValue) {return;}if (maxValue < 0) {throw new IllegalArgumentException("maxValue must be >= 0");}mMaxValue = maxValue;if (mMaxValue < mValue) {mValue = mMaxValue;}updateWrapSelectorWheel();initializeSelectorWheelIndices();updateInputTextView();tryComputeMaxWidth();invalidate();}
如都调用量initializeSelectorWheelIndices
方法
/*** Resets the selector indices and clear the cached string representation of* these indices.*/@UnsupportedAppUsageprivate void initializeSelectorWheelIndices() {mSelectorIndexToStringCache.clear();int[] selectorIndices = mSelectorIndices;int current = getValue();for (int i = 0; i < mSelectorIndices.length; i++) {/*** 1.SELECTOR_MIDDLE_ITEM_INDEX表示中间行,总共3行,中间行index就为1* 2.current表示当前值,在上面的初始设置中,即为mMinValue*/int selectorIndex = current + (i - SELECTOR_MIDDLE_ITEM_INDEX);if (mWrapSelectorWheel) {//处理selectorIndex大于最大值和小于最小值的情况selectorIndex = getWrappedSelectorIndex(selectorIndex);}selectorIndices[i] = selectorIndex;ensureCachedScrollSelectorValue(selectorIndices[i]);}}/*** @return The wrapped index <code>selectorIndex</code> value.*/private int getWrappedSelectorIndex(int selectorIndex) {if (selectorIndex > mMaxValue) {return mMinValue + (selectorIndex - mMaxValue) % (mMaxValue - mMinValue) - 1;} else if (selectorIndex < mMinValue) {return mMaxValue - (mMinValue - selectorIndex) % (mMaxValue - mMinValue) + 1;}return selectorIndex;}
如何理解上面的代码?
mSelectorIndices
是一个int数组,我自己的理解,其保存的是页面上从上到下显示的字符串,在数组中对应的索引index
例如在上面设置minValue
为0
,maxValue
为4
,可理解要显示的字符串数组为[0, 1, 2, 3, 4]
所以第一次要展示的字符串为[4, 0, 1]
,其index也对应为[4, 0, 1]
由于是循环滚动,所以如果计算的selectorIndex
小于了最小值0
, 即表示要从数组[0, 1, 2, 3, 4]
逆序寻找,即从maxValue
往前去获取
如第一次遍历中,selectorIndex = -1
,即从往前maxValue
找一个,即为maxValue
本身
即getWrappedSelectorIndex
方法中的
mMaxValue - (mMinValue - selectorIndex) % (mMaxValue - mMinValue) + 1
如果current == 4
, selectorIndex = 5
时,此时selectorIndex
超过了最大值4
,即表示要从数组[0, 1, 2, 3, 4]
正序寻找,从minValue
开始寻找
即getWrappedSelectorIndex
方法中的
mMinValue + (selectorIndex - mMaxValue) % (mMaxValue - mMinValue) - 1