NumberPickerView

Introduction: NumberPicker
More: Author   ReportBugs   
Tags:
NumberPicker-

Another NumberPicker with more flexible attributes on Android platform

English

前言

在 Android 项目的开发中会用到NumberPicker组件,但是默认风格的NumberPicker具有一些不灵活的属性,定制起来也比较麻烦,并且缺少一些过渡动效,因此在应用开发时,一般采用自定义的控件来完成选择功能。

控件截图

Example Image
效果图 1

Example Image
效果图 2

Example Image
静态截图以及渐变效果

Example Image
NumberPickerView 的实际应用,一款可以选择公历/农历日期的 View,且公农历自由切换。
截屏有些问题,使得看上去有点卡顿且 divider 颜色不一致,实际效果很流畅。具体项目地址可见:
https://github.com/Carbs0126/GregorianLunarCalendar

说明

NumberPickerView是一款与 android 原生NumberPicker具有类似界面以及类似功能的View。 主要功能同样是从多个候选项中通过上下滚动的方式选择需要的选项,但是与NumberPicker相比较,有几个主要不同点,下面是两者的不同之处。

原始控件特性-NumberPicker

  1. 显示窗口只能显示 3 个备选选项;
  2. 在 fling 时阻力较大,无法快速滑动;
  3. 在选中与非选中状态切换比较生硬;
  4. 批量改变选项中的内容时,没有动画效果;
  5. 动态设置 wrap 模式时(setWrapSelectorWheel()方法),会有“暂时显示不出部分选项”的问题;
  6. 选中位置没有文字说明;
  7. 代码中不能控制选项滑动滚动到某一 item;

自定义控件特性-NumberPickerView

  1. 显示窗口可以显示多个备选选项;
  2. fling 时滑动速度较快,且可以设置摩擦力,如下代码使得摩擦力为默认状态的 2 倍
    mNumberPickerView.setFriction(2 * ViewConfiguration.get(mContext).getScrollFriction());
  3. 在选中与非选中的状态滑动时,具有渐变的动画效果,包括文字放大缩小以及颜色的渐变;
  4. 在批量改变选项中的内容时,可以选择是否采用友好的滑动效果;
  5. 可以动态的设置是否 wrap,即,是否循环滚动;
  6. 选中位置可以添加文字说明,可控制文字字体大小颜色等;
  7. 具有在代码中动态的滑动到某一位置的功能;
  8. 支持wrap_content,支持 item 的 padding
  9. 提供多种属性,优化 UI 效果
  10. 在滑动过程中不响应onValueChanged()
  11. 点击上下单元格,可以自动滑动到对应的点击对象。
  12. 可通过属性设置onValueChanged等回调接口的执行线程。
  13. 兼容 NumberPicker 的重要方法和接口:

    兼容的方法有:
    setOnValueChangedListener()
    setOnScrollListener()
    setDisplayedValues()/getDisplayedValues()
    setWrapSelectorWheel()/getWrapSelectorWheel()
    setMinValue()/getMinValue()
    setMaxValue()/getMaxValue()
    setValue()/getValue()
    
    兼容的内部接口有:
    OnValueChangeListener
    OnScrollListener
    
    添加的接口有:
    OnValueChangeListenerInScrolling//滑动过程中响应 value change
    
  14. 更改字体的方法
    mNumberPickerView.setContentTextTypeface(tf);
    mNumberPickerView.setContentTextTypeface(tf);
    mNumberPickerView.postInvalidate();
    

使用方法

1.导入至工程

    implementation 'cn.carbswang.android:NumberPickerView:1.2.0'

或者

    <dependency>
      <groupId>cn.carbswang.android</groupId>
      <artifactId>NumberPickerView</artifactId>
      <version>1.2.0</version>
      <type>pom</type>
    </dependency>

2.通过布局声明 NumberPickerView

    <cn.carbswang.android.numberpickerview.library.NumberPickerView
        android:id="@+id/picker"
        android:layout_width="wrap_content"
        android:layout_height="240dp"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="20dp"
        android:background="#11333333"
        android:contentDescription="test_number_picker_view"
        app:npv_ItemPaddingHorizontal="5dp"
        app:npv_ItemPaddingVertical="5dp"
        app:npv_ShownCount="5"
        app:npv_RespondChangeOnDetached="false"
        app:npv_TextSizeNormal="16sp"
        app:npv_TextSizeSelected="20sp"
        app:npv_WrapSelectorWheel="true"/>

3.Java 代码中使用: 1)若设置的数据(String[] mDisplayedValues)不会再次改变,可以使用如下方式进行设置:(与 NumberPicker 的设置方式一致)

        picker.setMinValue(minValue);
        picker.setMaxValue(maxValue);
        picker.setValue(value);

2)若设置的数据(String[] mDisplayedValues)会改变,可以使用如下组合方式进行设置:(与 NumberPicker 的更改数据方式一致)

        int minValue = getMinValue();
        int oldMaxValue = getMaxValue();
        int oldSpan = oldMaxValue - minValue + 1;
        int newMaxValue = display.length - 1;
        int newSpan = newMaxValue - minValue + 1;
        if (newSpan > oldSpan) {
            setDisplayedValues(display);
            setMaxValue(newMaxValue);
        } else {
            setMaxValue(newMaxValue);
            setDisplayedValues(display);
        }

或者直接使用 NumberPickerView 提供的方法:
refreshByNewDisplayedValues(String[] display)
使用此方法时需要注意保证数据改变前后的 minValue 值不变,以及设置的 display 不能够为 null,且长度不能够为 0。 3)添加了滑动过程中响应 value change 的函数

    picker.setOnValueChangeListenerInScrolling(...);

4.另外,NumberPickerView 提供了平滑滚动的方法:
public void smoothScrollToValue(int fromValue, int toValue, boolean needRespond)

此方法与setValue(int)方法相同之处是可以动态设置当前显示的 item,不同之处在于此方法可以使NumberPickerView平滑的从滚动,即从fromValue值挑选最近路径滚动到toValue,第三个参数needRespond用来标识在滑动过程中是否响应onValueChanged回调函数。因为多个NumberPickerView在联动时,很可能不同的NumberPickerView的停止时间不同,如果在此时响应了onValueChanged回调,就可能再次联动,造成数据不准确,将needRespond置为false,可避免在滑动中响应回调函数。

另外,在使用此方法或者间接调用此方法时,需要注意最好不要在onCreate(Bundle savedInstanceState)方法中调用,因为 scroll 动画需要一定时间,如需确要在onCreate(Bundle savedInstanceState)中调用,请使用如下方式:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //代码省略
        mNumberPickerView.post(new Runnable() {
            @Override
            public void run() {
                //调用 smoothScrollToValue()等方法的代码
            }
        });
    }

5.各项自定义属性的说明

    <declare-styleable name="NumberPickerView">
        <attr name="npv_ShownCount" format="reference|integer" />//显示的条目个数,默认 3 个
        <attr name="npv_ShowDivider" format="reference|boolean" />//是否显示两条 divider,默认显示
        <attr name="npv_DividerColor" format="reference|color" />//两条 divider 的颜色
        <attr name="npv_DividerMarginLeft" format="reference|dimension" />//divider 距左侧的距离
        <attr name="npv_DividerMarginRight" format="reference|dimension" />//divider 距右侧的距离
        <attr name="npv_DividerHeight" format="reference|dimension" />//divider 的高度
        <attr name="npv_TextColorNormal" format="reference|color" />//未选中文字的颜色
        <attr name="npv_TextColorSelected" format="reference|color" />//选中文字的颜色
        <attr name="npv_TextColorHint" format="reference|color" />//中间偏右侧说明文字的颜色
        <attr name="npv_TextSizeNormal" format="reference|dimension" />//未选中文字的大小
        <attr name="npv_TextSizeSelected" format="reference|dimension" />//选中文字的大小
        <attr name="npv_TextSizeHint" format="reference|dimension" />//说明文字的大小
        <attr name="npv_TextArray" format="reference" />//文字内容,stringarray 类型
        <attr name="npv_MinValue" format="reference|integer" />//最小值,同 setMinValue()
        <attr name="npv_MaxValue" format="reference|integer" />//最大值,同 setMaxValue()
        <attr name="npv_WrapSelectorWheel" format="reference|boolean" />//设置是否 wrap,同 setWrapSelectorWheel
        <attr name="npv_HintText" format="reference|string" />//设置说明文字
        <attr name="npv_EmptyItemHint" format="reference|string" />//空行的显示文字,默认不显示任何文字。只在 WrapSelectorWheel==false 是起作用
        <attr name="npv_MarginStartOfHint" format="reference|dimension" />//说明文字距离左侧的距离,"左侧"是指文字 array 最宽 item 的右侧
        <attr name="npv_MarginEndOfHint" format="reference|dimension" />//说明文字距离右侧的距离
        <attr name="npv_ItemPaddingHorizontal" format="reference|dimension" />//item 的水平 padding,用于 wrap_content 模式
        <attr name="npv_ItemPaddingVertical" format="reference|dimension" />//item 的竖直 padding,用于 wrap_content 模式
        <attr name="npv_RespondChangeOnDetached" format="reference|boolean" />//在 detach 时如果 NumberPickerView 正好滑动,设置
        //是否响应 onValueChange 回调,用在一个 Dialog/PopupWindow 被显示多次,
        //且多次显示时记录上次滑动状态的情况。建议 Dialog/PopupWindow 在显示时每次都指定初始值,且将此属性置为 false
        <attr name="npv_RespondChangeInMainThread" format="reference|boolean" />//指定`onValueChanged`响应事件在什么线程中执行。
        //默认为`true`,即在主线程中执行。如果设置为`false`则在子线程中执行。

        //以下属性用于在 wrap_content 模式下,改变内容 array 并且又不想让控件"跳动",那么就可以设置所有改变的内容的最大宽度
        <!--just used to measure maxWidth for wrap_content without hint,
            the string array will never be displayed.
            you can set this attr if you want to keep the wraped numberpickerview
            width unchanged when alter the content list-->
        <attr name="npv_AlternativeTextArrayWithMeasureHint" format="reference" />//可能达到的最大宽度,包括说明文字在内,最大宽度只可能比此 String 的宽度更大
        <attr name="npv_AlternativeTextArrayWithoutMeasureHint" format="reference" />//可能达到的最大宽度,不包括说明文字在内,最大宽度只可能比此 String 的宽度+说明文字+说明文字 marginstart +说明文字 marginend 更大
        <!--the max length of hint content-->
        <attr name="npv_AlternativeHint" format="reference|string" />//说明文字的最大宽度
    </declare-styleable>

版本更新

1.2.0

1.合并两个 pull request
2.删除 library 中不必要的 dependences

1.1.1

1.添加更改文字 typeface 的方法
2.添加滑动过程中响应 valuechange 的方法

    picker.setOnValueChangeListenerInScrolling(...);


1.1.0

1.优化位置校正时的滚动时间。
2.微调刷新时间。
3.优化示例界面显示布局。

1.0.9

1.添加属性app:npv_RespondChangeInMainThread="true",指定onValueChanged响应事件在什么线程中执行。默认为true,即在主线程中执行。如果设置为false则在子线程中执行。
2.更新TimePickerActivity示例,以说明属性app:npv_RespondChangeInMainThread="true"的用法。
3.修复 bug: 在更新内容时,如果滑动没有停止,那么新的内容显示出来后,滚动的位置不正确的 bug。

1.0.8

1.更改stopScrolling方法,在abortAnimation()之前添加滚动到当前坐标的代码
2.更改npv_RespondChangeOnDetached的默认值为 false

1.0.7

1.完善在onDetachToWindow()函数中添加的响应判断,主要针对多次调用Dialog/PopupWindow,如果此时Dialog/PopupWindow在隐藏时,NumberPickerView仍然在滑动,那么需要停止滑动+可选响应OnValueChange回调+更改上次选中索引。添加属性npv_RespondChangeOnDetached作为判断是否响应 onValueChange 回调,主要用在多个 NumberPickerView 联动的场景。同时建议每次在显示Dialog/PopupWindow时,重新为每个 NumberPickerView 设定确定的值,且将npv_RespondChangeOnDetached属性置为 false,具体可见GregorianLunarCalendar项目中的 dialog 相关用法。此次更改方式较为笨拙,如果有好的方法,还请告知,非常感谢。

1.0.6

1.在onDetachToWindow()函数中添加响应判断,主要针对多次调用的 Dialog/PopupWindow

1.0.5

1.在onAttachToWindow()函数中添加判断mHandlerThread有没有已经被quit掉的函数,避免在第二次进入 dialog/popupWindow 时无法刷新位置的问题

1.0.4

1.更改部分属性名称,更改部分注释

1.0.3

1.修复不能够在ScrollView中滑动的 bug,感谢 anjiao 以及 Elektroktay 的 issue

主要原理

1.滚动效果的产生:

Scroller + VelocityTracker + onDraw(Canvas canvas)

2.自动校准位置。

Handler 刷新当前位置

3.渐变的 UI 效果

渐变 UI 效果同样是通过计算当前滑动的坐标以及某个 item 与中间显示位置的差值比例,来确定此 item 中的字体大小以及颜色。

将 NumberPicker 改为 NumberPickerView

要替代项目中使用的 NumberPicker,只需要将涉及 NumberPicker 的代码(如回调中传入了 NumberPicker、使用了 NumberPicker 的内部接口)改为 NumberPickerView 即可。

UI 设计借鉴了 meizu 的多个应用的设计。感谢 google 的 android 平台以及 meizu 的设计。

Contact

wechat: AutoReleasePool

License

Copyright 2016 Carbs.Wang (NumberPickerView)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apps
About Me
GitHub: Trinea
Facebook: Dev Tools