> 文章列表 > NumberPicker分析(一)

NumberPicker分析(一)

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"/>

然后设置minValuemaxValue。(当然也可以设置DisplayedValues,这里以最简单的使用方式为例)

mNumberPicker.setMaxValue(4);
mNumberPicker.setMinValue(0);

其显示效果如下:
显示效果
分析下NumberPicker构造方法(源码可参考NumberPicker.java)

mHasSelectorWheel = (layoutResId != DEFAULT_LAYOUT_RESOURCE_ID);

mHasSelectorWheel表示 - 是否具有选择轮
如果在源码处添加一个debug的断点,会发现mHasSelectorWheel结果是true,即表示layoutResId不是DEFAULT_LAYOUT_RESOURCE_IDR.layout.number_picker

通过官方文档对NumberPicker的介绍,可发现其style与主题有关:

  • 如果当前主题是从 R.style.Theme 派生的,则小部件将当前值显示为可编辑的输入字段,上面有一个递增按钮,下面有一个递减按钮。 长按按钮可以快速更改当前值。 点击输入字段允许输入所需的值。
  • 如果当前主题是从 R.style.Theme_HoloR.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
例如在上面设置minValue0maxValue4,可理解要显示的字符串数组为[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 == 4selectorIndex = 5时,此时selectorIndex超过了最大值4,即表示要从数组[0, 1, 2, 3, 4]正序寻找,从minValue开始寻找

minValue
getWrappedSelectorIndex方法中的

mMinValue + (selectorIndex - mMaxValue) % (mMaxValue - mMinValue) - 1