NumberPicker分析(一)

NumberPicker分析(一)

NumberPicker可实现连续滚动的字符串选择,其实现方式很有借鉴的意义

以最基本的使用方式为例,在layout中布局:

    <NumberPicker
        android: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方法

initializeSelectorWheelIndices方法可以理解为reset选择器中要显示字符串的数组,数值中的元素为字符串对应的index,并缓存index对应的string

    /**
     * Resets the selector indices and clear the cached string representation of
     * these indices.
     */
    @UnsupportedAppUsage
    private 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
ensureCachedScrollSelectorValue方法

ensureCachedScrollSelectorValue方法用来缓存上面计算的selectorIndex对应的string,将其存储在SparseArray<String>

    /**
     * Ensures we have a cached string representation of the given <code>
     * selectorIndex</code> to avoid multiple instantiations of the same string.
     */
    private void ensureCachedScrollSelectorValue(int selectorIndex) {
        SparseArray<String> cache = mSelectorIndexToStringCache;
        String scrollSelectorValue = cache.get(selectorIndex);
        if (scrollSelectorValue != null) {
            return;
        }
        if (selectorIndex < mMinValue || selectorIndex > mMaxValue) {
            scrollSelectorValue = "";
        } else {
            if (mDisplayedValues != null) {
                int displayedValueIndex = selectorIndex - mMinValue;
                scrollSelectorValue = mDisplayedValues[displayedValueIndex];
            } else {
                scrollSelectorValue = formatNumber(selectorIndex);
            }
        }
        cache.put(selectorIndex, scrollSelectorValue);
    }

tryComputeMaxWidth方法

tryComputeMaxWidth方法用来计算字符串的最大宽度

    /**
     * Computes the max width if no such specified as an attribute.
     */
    private void tryComputeMaxWidth() {
        if (!mComputeMaxWidth) {
            return;
        }
        int maxTextWidth = 0;
        if (mDisplayedValues == null) {
            float maxDigitWidth = 0;
            //找出0-9字符中最大的宽度
            for (int i = 0; i <= 9; i++) {
                final float digitWidth = mSelectorWheelPaint.measureText(formatNumberWithLocale(i));
                if (digitWidth > maxDigitWidth) {
                    maxDigitWidth = digitWidth;
                }
            }
            int numberOfDigits = 0;
            int current = mMaxValue;
            //数字有多少位
            while (current > 0) {
                numberOfDigits++;
                current = current / 10;
            }
            maxTextWidth = (int) (numberOfDigits * maxDigitWidth);
        } else {
            final int valueCount = mDisplayedValues.length;
            for (int i = 0; i < valueCount; i++) {
            	//计算字符串宽度
                final float textWidth = mSelectorWheelPaint.measureText(mDisplayedValues[i]);
                if (textWidth > maxTextWidth) {
                    maxTextWidth = (int) textWidth;
                }
            }
        }
        maxTextWidth += mInputText.getPaddingLeft() + mInputText.getPaddingRight();
        if (mMaxWidth != maxTextWidth) {
            if (maxTextWidth > mMinWidth) {
                mMaxWidth = maxTextWidth;
            } else {
                mMaxWidth = mMinWidth;
            }
            invalidate();
        }
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值