45 |
46 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
32 |
33 |
44 |
45 |
56 |
57 |
66 |
67 |
78 |
79 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Odometer
2 | This is an Android Library for making Odometer with little customization with reading, slots, colors, font and size.
3 |
4 | Example is available in app module.
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ## Download
20 |
21 | ### Gradle dependency:
22 | - Add the following to your project level build.gradle:
23 | ~~~
24 | repositories {
25 | maven {
26 | url "https://jitpack.io"
27 | }
28 | }
29 | ~~~
30 | - Add this to your app build.gradle:
31 | ~~~
32 | dependencies {
33 | compile 'com.github.Chils17:OdometerLibrary:687d5ac1e9'
34 | }
35 | ~~~
36 |
37 |
38 |
39 | ## Usage
40 |
41 | ### Xml
42 | - Create Odometer with their properties.
43 | ~~~
44 |
47 |
48 |
49 | ~~~
50 |
51 | - Add EdgeColor of Odometer
52 | ~~~
53 | chils:np_edgeColor="@android:color/white"
54 | ~~~
55 |
56 | - Add CenterColor of Odometer
57 | ~~~
58 | chils:np_centerColor="@android:color/black"
59 | ~~~
60 |
61 | - Add Reading
62 | Reading is the values that you want to show.
63 | ~~~
64 | chils:np_reading="0000"
65 | ~~~
66 |
67 | - Add Slots
68 | Slots means that how many slots you want to create.
69 | ~~~
70 | chils:np_slots="4"
71 | ~~~
72 |
73 | - Add TextColor
74 | ~~~
75 | chils:np_textColor="@color/white"
76 | ~~~
77 |
78 | - Add TextSize
79 | ~~~
80 | chils:np_textSize="18sp"
81 | ~~~
82 |
83 | - Add custom font from .ttf. Put your .ttf file at assets\fonts. Font will apply everywhere title, content, buttons
84 | ~~~
85 | chils:np_font="@string/lato_regular"
86 | ~~~
87 |
88 | - Those attributes necessary to add reading and slots together in Odometer
89 | ~~~
90 | chils:np_reading="0000"
91 | chils:np_slots="4"
92 | ~~~
93 |
94 | ### Odometer
95 |
96 | - You can even use the Odometer alone.
97 | ~~~
98 |
109 | ~~~
110 |
111 | ### Java
112 |
113 | - Create Builder for default Odometer.
114 | Its necessary to add odometer in the layout.
115 | ~~~
116 | Odometer odometer=new Odometer.Builder(this)
117 | .build();
118 | ((LinearLayout) findViewById(R.id.linear)).addView(odometer);
119 | ~~~
120 |
121 |
122 | - Add Customize Color `.background(int odo_edge_color, int odo_center_color)`
123 | ~~~
124 | Odometer odometer=new Odometer.Builder(this)
125 | .background(ContextCompat.getColor(this, R.color.white), ContextCompat.getColor(this, R.color.black))
126 | .build();
127 | ((LinearLayout) findViewById(R.id.linear)).addView(odometer);
128 | ~~~
129 |
130 |
131 | - Add custom font from .ttf. Put your .ttf file at assets\fonts.
132 | Font will apply in odometer number.
133 | `.font(String font)`
134 | ~~~
135 | Odometer odometer=new Odometer.Builder(this)
136 | .background(ContextCompat.getColor(this, R.color.white), ContextCompat.getColor(this, R.color.black))
137 | .font(getString(R.string.lato_regular))
138 | .build();
139 | ((LinearLayout) findViewById(R.id.linear)).addView(odometer);
140 | ~~~
141 |
142 | - Add reading to set the value of odometer.
143 | It is essential of both reading and slot have to be equal in length.
144 | `.reading(String reading)`
145 | ~~~
146 | Odometer odometer=new Odometer.Builder(this)
147 | .background(ContextCompat.getColor(this, R.color.white), ContextCompat.getColor(this, R.color.black))
148 | .font(getString(R.string.lato_regular))
149 | .reading("1234")
150 | .build();
151 | ((LinearLayout) findViewById(R.id.linear)).addView(odometer);
152 | ~~~
153 |
154 | - Add slot.
155 | It is essential of both reading and slot have to be equal in length.
156 | `.slot(int slot)`
157 | ~~~
158 | Odometer odometer = new Odometer.Builder(this)
159 | .background(ContextCompat.getColor(this, R.color.white), ContextCompat.getColor(this, R.color.black))
160 | .font(getString(R.string.lato_regular))
161 | .reading("1234")
162 | .slot(4)
163 | .build();
164 | ((LinearLayout) findViewById(R.id.linear)).addView(odometer);
165 | ~~~
166 |
167 | - Add text color.
168 | `.textColor(int odo_text_color)`
169 | ~~~
170 | Odometer odometer = new Odometer.Builder(this)
171 | .background(ContextCompat.getColor(this, R.color.white), ContextCompat.getColor(this, R.color.black))
172 | .font(getString(R.string.lato_regular))
173 | .reading("1234")
174 | .slot(4)
175 | .textColor(ContextCompat.getColor(this, R.color.white))
176 | .build();
177 | ((LinearLayout) findViewById(R.id.linear)).addView(odometer);
178 | ~~~
179 |
180 | - Add text size.
181 | `.textSize(float textSize)`
182 | ~~~
183 | Odometer odometer = new Odometer.Builder(this)
184 | .background(ContextCompat.getColor(this, R.color.white), ContextCompat.getColor(this, R.color.black))
185 | .font(getString(R.string.lato_regular))
186 | .reading("1234")
187 | .slot(4)
188 | .textColor(ContextCompat.getColor(this, R.color.white))
189 | .textSize(18)
190 | .build();
191 | ((LinearLayout) findViewById(R.id.linear)).addView(odometer);
192 | ~~~
193 |
194 | ### Layout Customization
195 |
196 | If you want to get the value of Odometer scrolling value you can create your own.
197 |
198 | Note: You can see an example layout in both sample and library modules.
199 |
200 | Example XML layout:
201 | ~~~
202 |
210 |
211 |
221 | ~~~
222 |
223 | Set a listener to be notified when value has changed:
224 | ~~~
225 | btn_submit.setOnClickListener(new View.OnClickListener() {
226 | @Override
227 | public void onClick(View v) {
228 | tvOutPut.setText(odometer.getFinalOdometerValue());
229 | }
230 | });
231 | ~~~
232 |
233 | ### License
234 | ~~~
235 | Apache Version 2.0
236 |
237 | Copyright 2017.
238 |
239 | Licensed under the Apache License, Version 2.0 (the "License");
240 | you may not use this file except in compliance with the License.
241 | You may obtain a copy of the License at
242 |
243 | http://www.apache.org/licenses/LICENSE-2.0
244 |
245 | Unless required by applicable law or agreed to in writing, software
246 | distributed under the License is distributed on an "AS IS" BASIS,
247 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
248 | See the License for the specific language governing permissions and
249 | limitations under the License.
250 | ~~~
251 |
--------------------------------------------------------------------------------
/odometer/src/main/java/com/androidchils/odometer/Odometer.java:
--------------------------------------------------------------------------------
1 | package com.androidchils.odometer;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Typeface;
9 | import android.graphics.drawable.ColorDrawable;
10 | import android.graphics.drawable.GradientDrawable;
11 | import android.support.annotation.Nullable;
12 | import android.support.v4.content.ContextCompat;
13 | import android.text.TextUtils;
14 | import android.util.AttributeSet;
15 | import android.util.Log;
16 | import android.util.TypedValue;
17 | import android.view.Gravity;
18 | import android.view.LayoutInflater;
19 | import android.view.View;
20 | import android.widget.EditText;
21 | import android.widget.LinearLayout;
22 | import android.widget.NumberPicker;
23 | import android.widget.TextView;
24 |
25 | import java.lang.reflect.Field;
26 |
27 | /**
28 | * Created by chiragpatel on 15-05-2017.
29 | */
30 |
31 | public class Odometer extends LinearLayout {
32 |
33 | private LinearLayout llParent;
34 | private int slot;
35 | private int odo_text_color;
36 | private float textSize;
37 | private String read, fontName;
38 | private int odo_edge_color;
39 | private int odo_center_color;
40 |
41 | public Odometer(Context context, Builder builder) {
42 | super(context);
43 | initViews(context, builder);
44 | }
45 |
46 | public Odometer(Context context, @Nullable AttributeSet attrs) {
47 | super(context, attrs);
48 | initViews(context, attrs);
49 | }
50 |
51 | public Odometer(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
52 | super(context, attrs, defStyleAttr);
53 | initViews(context, attrs);
54 | }
55 |
56 | private void initViews(Context context, Builder builder) {
57 | setOrientation(HORIZONTAL);
58 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
59 | inflater.inflate(R.layout.number_picker, this, true);
60 |
61 | llParent = (LinearLayout) findViewById(R.id.llParent);
62 |
63 | createNumberPicker(context, builder);
64 | }
65 |
66 | private void initViews(Context context, AttributeSet attrs) {
67 |
68 | setOrientation(HORIZONTAL);
69 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
70 | inflater.inflate(R.layout.number_picker, this, true);
71 |
72 | llParent = (LinearLayout) findViewById(R.id.llParent);
73 |
74 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Odometer);
75 |
76 | try {
77 | fontName = typedArray.getString(R.styleable.Odometer_np_font);
78 |
79 | textSize = typedArray.getDimension(R.styleable.Odometer_np_textSize, spToPx(18f));
80 |
81 | odo_text_color = typedArray.getColor(R.styleable.Odometer_np_textColor, ContextCompat.getColor(context, R.color.white));
82 |
83 | slot = typedArray.getInt(R.styleable.Odometer_np_slots, 6);
84 | read = typedArray.getString(R.styleable.Odometer_np_reading);
85 |
86 | odo_edge_color = typedArray.getResourceId(R.styleable.Odometer_np_edgeColor, R.color.white);
87 | odo_center_color = typedArray.getResourceId(R.styleable.Odometer_np_centerColor, R.color.black);
88 |
89 | } finally {
90 | typedArray.recycle();
91 | }
92 |
93 | if (TextUtils.isEmpty(fontName)) {
94 | fontName = "Lato-Regular.ttf";
95 | }
96 |
97 |
98 | if (TextUtils.isEmpty(read)) {
99 | read = "000000";
100 | }
101 |
102 |
103 | if (TextUtils.isEmpty(read) || read.length() != slot) {
104 | TextView textView = new TextView(context);
105 | LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
106 | lp.gravity = Gravity.CENTER;
107 | textView.setLayoutParams(lp);
108 | textView.setTextColor(ContextCompat.getColor(context, R.color.black));
109 | textView.setText("Invalid Values");
110 | llParent.addView(textView);
111 |
112 | } else {
113 | createDynamicNumberPicker(context);
114 | }
115 |
116 | }
117 |
118 | private GradientDrawable makeGradientDrawable(GradientDrawable.Orientation orientation,
119 | int startColor, int centerColor, int endColor) {
120 | int[] colors = new int[]{startColor, centerColor, endColor};
121 | GradientDrawable gd = new GradientDrawable(orientation, colors);
122 | gd.setCornerRadius(8);
123 | gd.setGradientRadius(90);
124 | return gd;
125 | }
126 |
127 | private float spToPx(float sp) {
128 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
129 | }
130 |
131 | private void createDynamicNumberPicker(Context context) {
132 |
133 | for (int i = 1; i <= slot; i++) {
134 | NumberPicker numberPicker = new NumberPicker(context);
135 | LayoutParams lp = new LayoutParams(90, LayoutParams.WRAP_CONTENT);
136 | lp.setMargins(2, 0, 2, 0);
137 | lp.gravity = Gravity.CENTER;
138 | numberPicker.setLayoutParams(lp);
139 |
140 | setDividerColor(numberPicker, Color.TRANSPARENT);
141 |
142 | setNumberPickerTextColor(numberPicker, odo_text_color, fontName, textSize);
143 |
144 | if (odo_edge_color!=0 && odo_center_color!=0) {
145 | numberPicker.setBackgroundDrawable(makeGradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,
146 | ContextCompat.getColor(context, odo_edge_color),
147 | ContextCompat.getColor(context, odo_center_color),
148 | ContextCompat.getColor(context, odo_edge_color)));
149 | }
150 |
151 | numberPicker.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);
152 | numberPicker.setId(i - 1);
153 | numberPicker.setMinValue(0);
154 | numberPicker.setMaxValue(9);
155 |
156 | numberPicker.setWrapSelectorWheel(true);
157 |
158 | int read_val = Character.getNumericValue(read.charAt(i - 1));
159 | numberPicker.setValue(read_val);
160 |
161 | llParent.addView(numberPicker);
162 | }
163 |
164 | }
165 |
166 |
167 | private void createNumberPicker(Context context, Builder builder) {
168 |
169 | for (int i = 1; i <= builder.slot; i++) {
170 | NumberPicker numberPicker = new NumberPicker(context);
171 | LayoutParams lp = new LayoutParams(90, LayoutParams.WRAP_CONTENT);
172 | lp.setMargins(2, 0, 2, 0);
173 | lp.gravity = Gravity.CENTER;
174 | numberPicker.setLayoutParams(lp);
175 |
176 | setDividerColor(numberPicker, Color.TRANSPARENT);
177 |
178 | setNumberPickerTextColor(numberPicker, builder.odo_text_color, builder.font, spToPx(builder.textSize));
179 |
180 | numberPicker.setBackgroundDrawable(makeGradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,
181 | builder.odo_edge_color, builder.odo_center_color, builder.odo_edge_color));
182 |
183 | numberPicker.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);
184 | numberPicker.setId(i - 1);
185 | numberPicker.setMinValue(0);
186 | numberPicker.setMaxValue(9);
187 |
188 | numberPicker.setWrapSelectorWheel(true);
189 |
190 | int read_val = Character.getNumericValue(builder.reading.charAt(i - 1));
191 | numberPicker.setValue(read_val);
192 |
193 | llParent.addView(numberPicker);
194 | Log.e("add", "add" + i);
195 | }
196 |
197 | }
198 |
199 | public void setNumberPickerTextColor(NumberPicker numberPicker, int color, String fontName, float textSize) {
200 | final int count = numberPicker.getChildCount();
201 | for (int i = 0; i < count; i++) {
202 | View child = numberPicker.getChildAt(i);
203 | if (child instanceof EditText) {
204 | try {
205 | Field selectorWheelPaintField = numberPicker.getClass().getDeclaredField("mSelectorWheelPaint");
206 | selectorWheelPaintField.setAccessible(true);
207 | ((Paint) selectorWheelPaintField.get(numberPicker)).setColor(color);
208 |
209 | if (!TextUtils.isEmpty(fontName))
210 | ((Paint) selectorWheelPaintField.get(numberPicker)).setTypeface(Typeface.createFromAsset(getResources().getAssets(), fontName));
211 |
212 | ((Paint) selectorWheelPaintField.get(numberPicker)).setTextSize(textSize);
213 |
214 | ((EditText) child).setTextColor(color);
215 |
216 | if (!TextUtils.isEmpty(fontName))
217 | ((EditText) child).setTypeface(Typeface.createFromAsset(getResources().getAssets(), fontName));
218 |
219 | ((EditText) child).setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
220 |
221 | numberPicker.invalidate();
222 | } catch (NoSuchFieldException e) {
223 | Log.w("NumberPickerTextColor", e);
224 | } catch (IllegalAccessException e) {
225 | Log.w("NumberPickerTextColor", e);
226 | } catch (IllegalArgumentException e) {
227 | Log.w("NumberPickerTextColor", e);
228 | }
229 | }
230 | }
231 | }
232 |
233 | public void setDividerColor(NumberPicker picker, int color) {
234 |
235 | Field[] pickerFields = NumberPicker.class.getDeclaredFields();
236 | for (Field pf : pickerFields) {
237 | if (pf.getName().equals("mSelectionDivider")) {
238 | pf.setAccessible(true);
239 | try {
240 | ColorDrawable colorDrawable = new ColorDrawable(color);
241 | pf.set(picker, colorDrawable);
242 | } catch (IllegalArgumentException e) {
243 | e.printStackTrace();
244 | } catch (Resources.NotFoundException e) {
245 | e.printStackTrace();
246 | } catch (IllegalAccessException e) {
247 | e.printStackTrace();
248 | }
249 | break;
250 | }
251 | }
252 | }
253 |
254 |
255 | public String getFinalOdometerValue() {
256 | StringBuilder stringBuilder = new StringBuilder();
257 | for (int i = 0; i < llParent.getChildCount(); i++) {
258 |
259 | NumberPicker localNumberPicker = (NumberPicker) llParent.getChildAt(i);
260 | localNumberPicker.getValue();
261 |
262 | stringBuilder.append(localNumberPicker.getValue());
263 | stringBuilder.append(" ");
264 | }
265 | return stringBuilder.toString();
266 | }
267 |
268 |
269 | public static class Builder {
270 | // default values
271 | private Context context;
272 | private String font = "";
273 | private float textSize = 0;
274 | private int odo_text_color = 0;
275 | private int odo_edge_color = 0;
276 | private int odo_center_color = 0;
277 | private int slot;
278 | private String reading;
279 |
280 |
281 | public Builder(Context context) {
282 | this.context = context;
283 | odo_text_color = ContextCompat.getColor(context, R.color.white);
284 | odo_edge_color = ContextCompat.getColor(context, R.color.startColor);
285 | odo_center_color = ContextCompat.getColor(context, R.color.centerColor);
286 | textSize = spToPx(14);
287 | slot = 4;
288 | reading = "0000";
289 | }
290 |
291 | public Builder background(int odo_edge_color, int odo_center_color) {
292 | this.odo_edge_color = odo_edge_color;
293 | this.odo_center_color = odo_center_color;
294 | return this;
295 | }
296 |
297 |
298 | public Builder textColor(int odo_text_color) {
299 | this.odo_text_color = odo_text_color;
300 | return this;
301 | }
302 |
303 |
304 | public Builder font(String font) {
305 | this.font = font;
306 | return this;
307 | }
308 |
309 | public Builder textSize(float textSize) {
310 | this.textSize = textSize;
311 | return this;
312 | }
313 |
314 | public Builder slot(int slot) {
315 | this.slot = slot;
316 | return this;
317 | }
318 |
319 | public Builder reading(String reading) {
320 | this.reading = reading;
321 | return this;
322 | }
323 |
324 |
325 | public Odometer build() {
326 | return new Odometer(context, this);
327 | }
328 |
329 |
330 | private float spToPx(float sp) {
331 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, context.getResources().getDisplayMetrics());
332 | }
333 |
334 | }
335 |
336 |
337 | }
338 |
--------------------------------------------------------------------------------
/odometer/src/main/java/com/androidchils/odometer/NumberPicker.java:
--------------------------------------------------------------------------------
1 | package com.androidchils.odometer;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Paint;
7 | import android.graphics.Typeface;
8 | import android.graphics.drawable.ColorDrawable;
9 | import android.graphics.drawable.Drawable;
10 | import android.os.Build;
11 | import android.support.annotation.ColorInt;
12 | import android.support.annotation.ColorRes;
13 | import android.support.annotation.DimenRes;
14 | import android.support.annotation.IntDef;
15 | import android.support.annotation.StringRes;
16 | import android.support.v4.content.ContextCompat;
17 | import android.text.InputType;
18 | import android.text.Spanned;
19 | import android.text.TextUtils;
20 | import android.text.method.NumberKeyListener;
21 | import android.util.AttributeSet;
22 | import android.util.SparseArray;
23 | import android.util.TypedValue;
24 | import android.view.KeyEvent;
25 | import android.view.LayoutInflater;
26 | import android.view.MotionEvent;
27 | import android.view.VelocityTracker;
28 | import android.view.View;
29 | import android.view.ViewConfiguration;
30 | import android.view.accessibility.AccessibilityEvent;
31 | import android.view.animation.DecelerateInterpolator;
32 | import android.view.inputmethod.EditorInfo;
33 | import android.widget.EditText;
34 | import android.widget.LinearLayout;
35 | import android.widget.Scroller;
36 |
37 | import java.lang.annotation.Retention;
38 | import java.text.DecimalFormatSymbols;
39 | import java.util.Locale;
40 |
41 | import static java.lang.annotation.RetentionPolicy.SOURCE;
42 |
43 | /**
44 | * Created by chiragpatel on 17-05-2017.
45 | */
46 |
47 | public class NumberPicker extends LinearLayout {
48 | @Retention(SOURCE)
49 | @IntDef({VERTICAL, HORIZONTAL})
50 | public @interface Orientation{}
51 |
52 | public static final int VERTICAL = LinearLayout.VERTICAL;
53 |
54 | public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
55 |
56 | /**
57 | * The default update interval during long press.
58 | */
59 | private static final long DEFAULT_LONG_PRESS_UPDATE_INTERVAL = 300;
60 |
61 | /**
62 | * The coefficient by which to adjust (divide) the max fling velocity.
63 | */
64 | private static final int SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT = 8;
65 |
66 | /**
67 | * The the duration for adjusting the selector wheel.
68 | */
69 | private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800;
70 |
71 | /**
72 | * The duration of scrolling while snapping to a given position.
73 | */
74 | private static final int SNAP_SCROLL_DURATION = 300;
75 |
76 | /**
77 | * The strength of fading in the top and bottom while drawing the selector.
78 | */
79 | private static final float FADING_EDGE_STRENGTH = 0.9f;
80 |
81 | /**
82 | * The default unscaled height of the selection divider.
83 | */
84 | private static final int UNSCALED_DEFAULT_SELECTION_DIVIDER_THICKNESS = 2;
85 |
86 | /**
87 | * The default unscaled distance between the selection dividers.
88 | */
89 | private static final int UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE = 48;
90 |
91 | /**
92 | * Constant for unspecified size.
93 | */
94 | private static final int SIZE_UNSPECIFIED = -1;
95 |
96 | /**
97 | * The default max value of this widget.
98 | */
99 | private static final int DEFAULT_MAX_VALUE = 100;
100 |
101 | /**
102 | * The default min value of this widget.
103 | */
104 | private static final int DEFAULT_MIN_VALUE = 1;
105 |
106 | /**
107 | * The default max height of this widget.
108 | */
109 | private static final int DEFAULT_MAX_HEIGHT = 180;
110 |
111 | /**
112 | * The default min width of this widget.
113 | */
114 | private static final int DEFAULT_MIN_WIDTH = 64;
115 |
116 | /**
117 | * The default color of text.
118 | */
119 | private static final int DEFAULT_TEXT_COLOR = 0xFF000000;
120 |
121 | /**
122 | * The default size of text.
123 | */
124 | private static final float DEFAULT_TEXT_SIZE = 25f;
125 |
126 | /**
127 | * Use a custom NumberPicker formatting callback to use two-digit minutes
128 | * strings like "01". Keeping a static formatter etc. is the most efficient
129 | * way to do this; it avoids creating temporary objects on every call to
130 | * format().
131 | */
132 | private static class TwoDigitFormatter implements Formatter {
133 | final StringBuilder mBuilder = new StringBuilder();
134 |
135 | char mZeroDigit;
136 | java.util.Formatter mFmt;
137 |
138 | final Object[] mArgs = new Object[1];
139 |
140 | TwoDigitFormatter() {
141 | final Locale locale = Locale.getDefault();
142 | init(locale);
143 | }
144 |
145 | private void init(Locale locale) {
146 | mFmt = createFormatter(locale);
147 | mZeroDigit = getZeroDigit(locale);
148 | }
149 |
150 | public String format(int value) {
151 | final Locale currentLocale = Locale.getDefault();
152 | if (mZeroDigit != getZeroDigit(currentLocale)) {
153 | init(currentLocale);
154 | }
155 | mArgs[0] = value;
156 | mBuilder.delete(0, mBuilder.length());
157 | mFmt.format("%02d", mArgs);
158 | return mFmt.toString();
159 | }
160 |
161 | private static char getZeroDigit(Locale locale) {
162 | // return LocaleData.get(locale).zeroDigit;
163 | return new DecimalFormatSymbols(locale).getZeroDigit();
164 | }
165 |
166 | private java.util.Formatter createFormatter(Locale locale) {
167 | return new java.util.Formatter(mBuilder, locale);
168 | }
169 | }
170 |
171 | private static final TwoDigitFormatter sTwoDigitFormatter = new TwoDigitFormatter();
172 |
173 | public static final Formatter getTwoDigitFormatter() {
174 | return sTwoDigitFormatter;
175 | }
176 |
177 | /**
178 | * The text for showing the current value.
179 | */
180 | private final EditText mSelectedText;
181 |
182 | /**
183 | * The min height of this widget.
184 | */
185 | private int mMinHeight;
186 |
187 | /**
188 | * The max height of this widget.
189 | */
190 | private int mMaxHeight;
191 |
192 | /**
193 | * The max width of this widget.
194 | */
195 | private int mMinWidth;
196 |
197 | /**
198 | * The max width of this widget.
199 | */
200 | private int mMaxWidth;
201 |
202 | /**
203 | * Flag whether to compute the max width.
204 | */
205 | private final boolean mComputeMaxWidth;
206 |
207 | /**
208 | * The color of the selected text.
209 | */
210 | private int mSelectedTextColor = DEFAULT_TEXT_COLOR;
211 |
212 | /**
213 | * The color of the text.
214 | */
215 | private int mTextColor = DEFAULT_TEXT_COLOR;
216 |
217 | /**
218 | * The size of the text.
219 | */
220 | private float mTextSize = DEFAULT_TEXT_SIZE;
221 |
222 | /**
223 | * The typeface of the text.
224 | */
225 | private Typeface mTypeface;
226 |
227 | /**
228 | * The width of the gap between text elements if the selector wheel.
229 | */
230 | private int mSelectorTextGapWidth;
231 |
232 | /**
233 | * The height of the gap between text elements if the selector wheel.
234 | */
235 | private int mSelectorTextGapHeight;
236 |
237 | /**
238 | * The values to be displayed instead the indices.
239 | */
240 | private String[] mDisplayedValues;
241 |
242 | /**
243 | * Lower value of the range of numbers allowed for the NumberPicker
244 | */
245 | private int mMinValue = DEFAULT_MIN_VALUE;
246 |
247 | /**
248 | * Upper value of the range of numbers allowed for the NumberPicker
249 | */
250 | private int mMaxValue = DEFAULT_MAX_VALUE;
251 |
252 | /**
253 | * Current value of this NumberPicker
254 | */
255 | private int mValue;
256 |
257 | /**
258 | * Listener to be notified upon current value change.
259 | */
260 | private OnValueChangeListener mOnValueChangeListener;
261 |
262 | /**
263 | * Listener to be notified upon scroll state change.
264 | */
265 | private OnScrollListener mOnScrollListener;
266 |
267 | /**
268 | * Formatter for for displaying the current value.
269 | */
270 | private Formatter mFormatter;
271 |
272 | /**
273 | * The speed for updating the value form long press.
274 | */
275 | private long mLongPressUpdateInterval = DEFAULT_LONG_PRESS_UPDATE_INTERVAL;
276 |
277 | /**
278 | * Cache for the string representation of selector indices.
279 | */
280 | private final SparseArray mSelectorIndexToStringCache = new SparseArray<>();
281 |
282 | /**
283 | * The number of items show in the selector wheel.
284 | */
285 | private int mWheelItemCount = 3;
286 |
287 | /**
288 | * The index of the middle selector item.
289 | */
290 | private int mWheelMiddleItemIndex = mWheelItemCount / 2;
291 |
292 | /**
293 | * The selector indices whose value are show by the selector.
294 | */
295 | private int[] mSelectorIndices = new int[mWheelItemCount];
296 |
297 | /**
298 | * The {@link Paint} for drawing the selector.
299 | */
300 | private final Paint mSelectorWheelPaint;
301 |
302 | /**
303 | * The size of a selector element (text + gap).
304 | */
305 | private int mSelectorElementSize;
306 |
307 | /**
308 | * The initial offset of the scroll selector.
309 | */
310 | private int mInitialScrollOffset = Integer.MIN_VALUE;
311 |
312 | /**
313 | * The current offset of the scroll selector.
314 | */
315 | private int mCurrentScrollOffset;
316 |
317 | /**
318 | * The {@link Scroller} responsible for flinging the selector.
319 | */
320 | private final Scroller mFlingScroller;
321 |
322 | /**
323 | * The {@link Scroller} responsible for adjusting the selector.
324 | */
325 | private final Scroller mAdjustScroller;
326 |
327 | /**
328 | * The previous X coordinate while scrolling the selector.
329 | */
330 | private int mPreviousScrollerX;
331 |
332 | /**
333 | * The previous Y coordinate while scrolling the selector.
334 | */
335 | private int mPreviousScrollerY;
336 |
337 | /**
338 | * Handle to the reusable command for setting the input text selection.
339 | */
340 | private SetSelectionCommand mSetSelectionCommand;
341 |
342 | /**
343 | * Handle to the reusable command for changing the current value from long press by one.
344 | */
345 | private ChangeCurrentByOneFromLongPressCommand mChangeCurrentByOneFromLongPressCommand;
346 |
347 | /**
348 | * The X position of the last down event.
349 | */
350 | private float mLastDownEventX;
351 |
352 | /**
353 | * The Y position of the last down event.
354 | */
355 | private float mLastDownEventY;
356 |
357 | /**
358 | * The X position of the last down or move event.
359 | */
360 | private float mLastDownOrMoveEventX;
361 |
362 | /**
363 | * The Y position of the last down or move event.
364 | */
365 | private float mLastDownOrMoveEventY;
366 |
367 | /**
368 | * Determines speed during touch scrolling.
369 | */
370 | private VelocityTracker mVelocityTracker;
371 |
372 | /**
373 | * @see ViewConfiguration#getScaledTouchSlop()
374 | */
375 | private int mTouchSlop;
376 |
377 | /**
378 | * @see ViewConfiguration#getScaledMinimumFlingVelocity()
379 | */
380 | private int mMinimumFlingVelocity;
381 |
382 | /**
383 | * @see ViewConfiguration#getScaledMaximumFlingVelocity()
384 | */
385 | private int mMaximumFlingVelocity;
386 |
387 | /**
388 | * Flag whether the selector should wrap around.
389 | */
390 | private boolean mWrapSelectorWheel;
391 |
392 | /**
393 | * Divider for showing item to be selected while scrolling
394 | */
395 | private Drawable mSelectionDivider;
396 |
397 | /**
398 | * The color of the selection divider.
399 | */
400 | private int mSelectionDividerColor;
401 |
402 | /**
403 | * The distance between the two selection dividers.
404 | */
405 | private int mSelectionDividersDistance;
406 |
407 | /**
408 | * The thickness of the selection divider.
409 | */
410 | private int mSelectionDividerThickness;
411 |
412 | /**
413 | * The current scroll state of the number picker.
414 | */
415 | private int mScrollState = NumberPicker.OnScrollListener.SCROLL_STATE_IDLE;
416 |
417 | /**
418 | * The top of the top selection divider.
419 | */
420 | private int mTopSelectionDividerTop;
421 |
422 | /**
423 | * The bottom of the bottom selection divider.
424 | */
425 | private int mBottomSelectionDividerBottom;
426 |
427 | /**
428 | * The left of the top selection divider.
429 | */
430 | private int mLeftOfSelectionDividerLeft;
431 |
432 | /**
433 | * The right of the bottom selection divider.
434 | */
435 | private int mRightOfSelectionDividerRight;
436 |
437 | /**
438 | * The keycode of the last handled DPAD down event.
439 | */
440 | private int mLastHandledDownDpadKeyCode = -1;
441 |
442 | /**
443 | * The width of this widget.
444 | */
445 | private float mWidth;
446 |
447 | /**
448 | * The height of this widget.
449 | */
450 | private float mHeight;
451 |
452 | /**
453 | * The orientation of this widget.
454 | */
455 | private int mOrientation;
456 |
457 | /**
458 | * The context of this widget.
459 | */
460 | private Context mContext;
461 |
462 | /**
463 | * Interface to listen for changes of the current value.
464 | */
465 | public interface OnValueChangeListener {
466 |
467 | /**
468 | * Called upon a change of the current value.
469 | *
470 | * @param picker The NumberPicker associated with this listener.
471 | * @param oldVal The previous value.
472 | * @param newVal The new value.
473 | */
474 | void onValueChange(NumberPicker picker, int oldVal, int newVal);
475 | }
476 |
477 | /**
478 | * Interface to listen for the picker scroll state.
479 | */
480 | public interface OnScrollListener {
481 |
482 | /**
483 | * The view is not scrolling.
484 | */
485 | public static int SCROLL_STATE_IDLE = 0;
486 |
487 | /**
488 | * The user is scrolling using touch, and his finger is still on the screen.
489 | */
490 | public static int SCROLL_STATE_TOUCH_SCROLL = 1;
491 |
492 | /**
493 | * The user had previously been scrolling using touch and performed a fling.
494 | */
495 | public static int SCROLL_STATE_FLING = 2;
496 |
497 | /**
498 | * Callback invoked while the number picker scroll state has changed.
499 | *
500 | * @param view The view whose scroll state is being reported.
501 | * @param scrollState The current scroll state. One of
502 | * {@link #SCROLL_STATE_IDLE},
503 | * {@link #SCROLL_STATE_TOUCH_SCROLL} or
504 | * {@link #SCROLL_STATE_IDLE}.
505 | */
506 | public void onScrollStateChange(NumberPicker view, int scrollState);
507 | }
508 |
509 | /**
510 | * Interface used to format current value into a string for presentation.
511 | */
512 | public interface Formatter {
513 |
514 | /**
515 | * Formats a string representation of the current value.
516 | *
517 | * @param value The currently selected value.
518 | * @return A formatted string representation.
519 | */
520 | public String format(int value);
521 | }
522 |
523 | /**
524 | * Create a new number picker.
525 | *
526 | * @param context The application environment.
527 | */
528 | public NumberPicker(Context context) {
529 | this(context, null);
530 | }
531 |
532 | /**
533 | * Create a new number picker.
534 | *
535 | * @param context The application environment.
536 | * @param attrs A collection of attributes.
537 | */
538 | public NumberPicker(Context context, AttributeSet attrs) {
539 | this(context, attrs, 0);
540 | }
541 |
542 | /**
543 | * Create a new number picker
544 | *
545 | * @param context the application environment.
546 | * @param attrs a collection of attributes.
547 | * @param defStyle The default style to apply to this view.
548 | */
549 | public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
550 | super(context, attrs);
551 | mContext = context;
552 |
553 | TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.NumberPicker, defStyle, 0);
554 |
555 | mSelectionDivider = ContextCompat.getDrawable(context, R.drawable.np_numberpicker_selection_divider);
556 |
557 | mSelectionDividerColor = attributesArray.getColor(R.styleable.NumberPicker_np_dividerColor, mSelectionDividerColor);
558 |
559 | final int defSelectionDividerDistance = (int) TypedValue.applyDimension(
560 | TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE,
561 | getResources().getDisplayMetrics());
562 | mSelectionDividersDistance = attributesArray.getDimensionPixelSize(
563 | R.styleable.NumberPicker_np_dividerDistance, defSelectionDividerDistance);
564 |
565 | final int defSelectionDividerThickness = (int) TypedValue.applyDimension(
566 | TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDER_THICKNESS,
567 | getResources().getDisplayMetrics());
568 | mSelectionDividerThickness = attributesArray.getDimensionPixelSize(
569 | R.styleable.NumberPicker_np_dividerThickness, defSelectionDividerThickness);
570 |
571 | mOrientation = attributesArray.getInt(R.styleable.NumberPicker_np_orientation, VERTICAL);
572 |
573 | mWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_np_width, SIZE_UNSPECIFIED);
574 | mHeight = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_np_height, SIZE_UNSPECIFIED);
575 |
576 | setWidthAndHeight();
577 |
578 | mComputeMaxWidth = true;
579 |
580 | mValue = attributesArray.getInt(R.styleable.NumberPicker_np_value, mValue);
581 | mMaxValue = attributesArray.getInt(R.styleable.NumberPicker_np_max, mMaxValue);
582 | mMinValue = attributesArray.getInt(R.styleable.NumberPicker_np_min, mMinValue);
583 |
584 | mSelectedTextColor = attributesArray.getColor(R.styleable.NumberPicker_np_selectedTextColor, mSelectedTextColor);
585 | mTextColor = attributesArray.getColor(R.styleable.NumberPicker_np_text_color, mTextColor);
586 | mTextSize = attributesArray.getDimension(R.styleable.NumberPicker_np_text_size, spToPx(mTextSize));
587 | mTypeface = Typeface.create(attributesArray.getString(R.styleable.NumberPicker_np_typeface), Typeface.NORMAL);
588 | mFormatter = stringToFormatter(attributesArray.getString(R.styleable.NumberPicker_np_formatter));
589 | mWheelItemCount = attributesArray.getInt(R.styleable.NumberPicker_np_wheelItemCount, mWheelItemCount);
590 |
591 | // By default Linearlayout that we extend is not drawn. This is
592 | // its draw() method is not called but dispatchDraw() is called
593 | // directly (see ViewGroup.drawChild()). However, this class uses
594 | // the fading edge effect implemented by View and we need our
595 | // draw() method to be called. Therefore, we declare we will draw.
596 | setWillNotDraw(false);
597 |
598 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
599 | inflater.inflate(R.layout.number_picker_selector_wheel, this, true);
600 |
601 | // input text
602 | mSelectedText = (EditText) findViewById(R.id.np__numberpicker_input);
603 | mSelectedText.setEnabled(false);
604 | mSelectedText.setFocusable(false);
605 | mSelectedText.setImeOptions(EditorInfo.IME_ACTION_NONE);
606 |
607 | // create the selector wheel paint
608 | Paint paint = new Paint();
609 | paint.setAntiAlias(true);
610 | paint.setTextAlign(Paint.Align.CENTER);
611 | mSelectorWheelPaint = paint;
612 |
613 | setSelectedTextColor(mSelectedTextColor);
614 | setTextColor(mTextColor);
615 | setTextSize(mTextSize);
616 | setTypeface(mTypeface);
617 | setFormatter(mFormatter);
618 | updateInputTextView();
619 |
620 | setValue(mValue);
621 | setMaxValue(mMaxValue);
622 | setMinValue(mMinValue);
623 |
624 | setDividerColor(mSelectionDividerColor);
625 |
626 | setWheelItemCount(mWheelItemCount);
627 |
628 | mWrapSelectorWheel = attributesArray.getBoolean(R.styleable.NumberPicker_np_wrapSelectorWheel, mWrapSelectorWheel);
629 | setWrapSelectorWheel(mWrapSelectorWheel);
630 |
631 | if (mWidth != SIZE_UNSPECIFIED && mHeight != SIZE_UNSPECIFIED) {
632 | setScaleX(mWidth / mMinWidth);
633 | setScaleY(mHeight / mMaxHeight);
634 | } else if (mWidth != SIZE_UNSPECIFIED) {
635 | setScaleX(mWidth / mMinWidth);
636 | setScaleY(mWidth / mMinWidth);
637 | } else if (mHeight != SIZE_UNSPECIFIED) {
638 | setScaleX(mHeight / mMaxHeight);
639 | setScaleY(mHeight / mMaxHeight);
640 | }
641 |
642 | // initialize constants
643 | ViewConfiguration configuration = ViewConfiguration.get(context);
644 | mTouchSlop = configuration.getScaledTouchSlop();
645 | mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
646 | mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity() / SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT;
647 |
648 | // create the fling and adjust scrollers
649 | mFlingScroller = new Scroller(context, null, true);
650 | mAdjustScroller = new Scroller(context, new DecelerateInterpolator(2.5f));
651 |
652 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
653 | // If not explicitly specified this view is important for accessibility.
654 | if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
655 | setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
656 | }
657 | }
658 |
659 | attributesArray.recycle();
660 | }
661 |
662 | @Override
663 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
664 | final int msrdWdth = getMeasuredWidth();
665 | final int msrdHght = getMeasuredHeight();
666 |
667 | // Input text centered horizontally.
668 | final int inptTxtMsrdWdth = mSelectedText.getMeasuredWidth();
669 | final int inptTxtMsrdHght = mSelectedText.getMeasuredHeight();
670 | final int inptTxtLeft = (msrdWdth - inptTxtMsrdWdth) / 2;
671 | final int inptTxtTop = (msrdHght - inptTxtMsrdHght) / 2;
672 | final int inptTxtRight = inptTxtLeft + inptTxtMsrdWdth;
673 | final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght;
674 | mSelectedText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom);
675 |
676 | if (changed) {
677 | // need to do all this when we know our size
678 | initializeSelectorWheel();
679 | initializeFadingEdges();
680 |
681 | if (isHorizontalMode()) {
682 | mLeftOfSelectionDividerLeft = (getWidth() - mSelectionDividersDistance) / 2 - mSelectionDividerThickness;
683 | mRightOfSelectionDividerRight = mLeftOfSelectionDividerLeft + 2 * mSelectionDividerThickness + mSelectionDividersDistance;
684 | } else {
685 | mTopSelectionDividerTop = (getHeight() - mSelectionDividersDistance) / 2 - mSelectionDividerThickness;
686 | mBottomSelectionDividerBottom = mTopSelectionDividerTop + 2 * mSelectionDividerThickness + mSelectionDividersDistance;
687 | }
688 | }
689 | }
690 |
691 | @Override
692 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
693 | // Try greedily to fit the max width and height.
694 | final int newWidthMeasureSpec = makeMeasureSpec(widthMeasureSpec, mMaxWidth);
695 | final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMaxHeight);
696 | super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec);
697 | // Flag if we are measured with width or height less than the respective min.
698 | final int widthSize = resolveSizeAndStateRespectingMinSize(mMinWidth, getMeasuredWidth(), widthMeasureSpec);
699 | final int heightSize = resolveSizeAndStateRespectingMinSize(mMinHeight, getMeasuredHeight(), heightMeasureSpec);
700 | setMeasuredDimension(widthSize, heightSize);
701 | }
702 |
703 | /**
704 | * Move to the final position of a scroller. Ensures to force finish the scroller
705 | * and if it is not at its final position a scroll of the selector wheel is
706 | * performed to fast forward to the final position.
707 | *
708 | * @param scroller The scroller to whose final position to get.
709 | * @return True of the a move was performed, i.e. the scroller was not in final position.
710 | */
711 | private boolean moveToFinalScrollerPosition(Scroller scroller) {
712 | scroller.forceFinished(true);
713 | if (isHorizontalMode()) {
714 | int amountToScroll = scroller.getFinalX() - scroller.getCurrX();
715 | int futureScrollOffset = (mCurrentScrollOffset + amountToScroll) % mSelectorElementSize;
716 | int overshootAdjustment = mInitialScrollOffset - futureScrollOffset;
717 | if (overshootAdjustment != 0) {
718 | if (Math.abs(overshootAdjustment) > mSelectorElementSize / 2) {
719 | if (overshootAdjustment > 0) {
720 | overshootAdjustment -= mSelectorElementSize;
721 | } else {
722 | overshootAdjustment += mSelectorElementSize;
723 | }
724 | }
725 | amountToScroll += overshootAdjustment;
726 | scrollBy(amountToScroll, 0);
727 | return true;
728 | }
729 | } else {
730 | int amountToScroll = scroller.getFinalY() - scroller.getCurrY();
731 | int futureScrollOffset = (mCurrentScrollOffset + amountToScroll) % mSelectorElementSize;
732 | int overshootAdjustment = mInitialScrollOffset - futureScrollOffset;
733 | if (overshootAdjustment != 0) {
734 | if (Math.abs(overshootAdjustment) > mSelectorElementSize / 2) {
735 | if (overshootAdjustment > 0) {
736 | overshootAdjustment -= mSelectorElementSize;
737 | } else {
738 | overshootAdjustment += mSelectorElementSize;
739 | }
740 | }
741 | amountToScroll += overshootAdjustment;
742 | scrollBy(0, amountToScroll);
743 | return true;
744 | }
745 | }
746 | return false;
747 | }
748 |
749 | @Override
750 | public boolean onInterceptTouchEvent(MotionEvent event) {
751 | if (!isEnabled()) {
752 | return false;
753 | }
754 |
755 | final int action = event.getAction() & MotionEvent.ACTION_MASK;
756 | switch (action) {
757 | case MotionEvent.ACTION_DOWN: {
758 | removeAllCallbacks();
759 | mSelectedText.setVisibility(View.INVISIBLE);
760 | if (isHorizontalMode()) {
761 | mLastDownOrMoveEventX = mLastDownEventX = event.getX();
762 | // Make sure we support flinging inside scrollables.
763 | getParent().requestDisallowInterceptTouchEvent(true);
764 | if (!mFlingScroller.isFinished()) {
765 | mFlingScroller.forceFinished(true);
766 | mAdjustScroller.forceFinished(true);
767 | onScrollStateChange(NumberPicker.OnScrollListener.SCROLL_STATE_IDLE);
768 | } else if (!mAdjustScroller.isFinished()) {
769 | mFlingScroller.forceFinished(true);
770 | mAdjustScroller.forceFinished(true);
771 | } else if (mLastDownEventX < mLeftOfSelectionDividerLeft) {
772 | postChangeCurrentByOneFromLongPress(false, ViewConfiguration.getLongPressTimeout());
773 | } else if (mLastDownEventX > mRightOfSelectionDividerRight) {
774 | postChangeCurrentByOneFromLongPress(true, ViewConfiguration.getLongPressTimeout());
775 | }
776 | return true;
777 | } else {
778 | mLastDownOrMoveEventY = mLastDownEventY = event.getY();
779 | // Make sure we support flinging inside scrollables.
780 | getParent().requestDisallowInterceptTouchEvent(true);
781 | if (!mFlingScroller.isFinished()) {
782 | mFlingScroller.forceFinished(true);
783 | mAdjustScroller.forceFinished(true);
784 | onScrollStateChange(NumberPicker.OnScrollListener.SCROLL_STATE_IDLE);
785 | } else if (!mAdjustScroller.isFinished()) {
786 | mFlingScroller.forceFinished(true);
787 | mAdjustScroller.forceFinished(true);
788 | } else if (mLastDownEventY < mTopSelectionDividerTop) {
789 | postChangeCurrentByOneFromLongPress(false, ViewConfiguration.getLongPressTimeout());
790 | } else if (mLastDownEventY > mBottomSelectionDividerBottom) {
791 | postChangeCurrentByOneFromLongPress(true, ViewConfiguration.getLongPressTimeout());
792 | }
793 | return true;
794 | }
795 | }
796 | }
797 | return false;
798 | }
799 |
800 | @Override
801 | public boolean onTouchEvent(MotionEvent event) {
802 | if (!isEnabled()) {
803 | return false;
804 | }
805 | if (mVelocityTracker == null) {
806 | mVelocityTracker = VelocityTracker.obtain();
807 | }
808 | mVelocityTracker.addMovement(event);
809 | int action = event.getAction() & MotionEvent.ACTION_MASK;
810 | switch (action) {
811 | case MotionEvent.ACTION_MOVE: {
812 | if (isHorizontalMode()) {
813 | float currentMoveX = event.getX();
814 | if (mScrollState != NumberPicker.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
815 | int deltaDownX = (int) Math.abs(currentMoveX - mLastDownEventX);
816 | if (deltaDownX > mTouchSlop) {
817 | removeAllCallbacks();
818 | onScrollStateChange(NumberPicker.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
819 | }
820 | } else {
821 | int deltaMoveX = (int) ((currentMoveX - mLastDownOrMoveEventX));
822 | scrollBy(deltaMoveX, 0);
823 | invalidate();
824 | }
825 | mLastDownOrMoveEventX = currentMoveX;
826 | } else {
827 | float currentMoveY = event.getY();
828 | if (mScrollState != NumberPicker.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
829 | int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY);
830 | if (deltaDownY > mTouchSlop) {
831 | removeAllCallbacks();
832 | onScrollStateChange(NumberPicker.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
833 | }
834 | } else {
835 | int deltaMoveY = (int) ((currentMoveY - mLastDownOrMoveEventY));
836 | scrollBy(0, deltaMoveY);
837 | invalidate();
838 | }
839 | mLastDownOrMoveEventY = currentMoveY;
840 | }
841 | }
842 | break;
843 | case MotionEvent.ACTION_UP: {
844 | removeChangeCurrentByOneFromLongPress();
845 | VelocityTracker velocityTracker = mVelocityTracker;
846 | velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
847 | if (isHorizontalMode()) {
848 | int initialVelocity = (int) velocityTracker.getXVelocity();
849 | if (Math.abs(initialVelocity) > mMinimumFlingVelocity) {
850 | fling(initialVelocity);
851 | onScrollStateChange(NumberPicker.OnScrollListener.SCROLL_STATE_FLING);
852 | } else {
853 | int eventX = (int) event.getX();
854 | int deltaMoveX = (int) Math.abs(eventX - mLastDownEventX);
855 | if (deltaMoveX <= mTouchSlop) { // && deltaTime < ViewConfiguration.getTapTimeout()) {
856 | int selectorIndexOffset = (eventX / mSelectorElementSize) - mWheelMiddleItemIndex;
857 | if (selectorIndexOffset > 0) {
858 | changeValueByOne(true);
859 | } else if (selectorIndexOffset < 0) {
860 | changeValueByOne(false);
861 | } else {
862 | ensureScrollWheelAdjusted();
863 | }
864 | } else {
865 | ensureScrollWheelAdjusted();
866 | }
867 | onScrollStateChange(NumberPicker.OnScrollListener.SCROLL_STATE_IDLE);
868 | }
869 | } else {
870 | int initialVelocity = (int) velocityTracker.getYVelocity();
871 | if (Math.abs(initialVelocity) > mMinimumFlingVelocity) {
872 | fling(initialVelocity);
873 | onScrollStateChange(NumberPicker.OnScrollListener.SCROLL_STATE_FLING);
874 | } else {
875 | int eventY = (int) event.getY();
876 | int deltaMoveY = (int) Math.abs(eventY - mLastDownEventY);
877 | if (deltaMoveY <= mTouchSlop) { // && deltaTime < ViewConfiguration.getTapTimeout()) {
878 | int selectorIndexOffset = (eventY / mSelectorElementSize) - mWheelMiddleItemIndex;
879 | if (selectorIndexOffset > 0) {
880 | changeValueByOne(true);
881 | } else if (selectorIndexOffset < 0) {
882 | changeValueByOne(false);
883 | } else {
884 | ensureScrollWheelAdjusted();
885 | }
886 | } else {
887 | ensureScrollWheelAdjusted();
888 | }
889 | onScrollStateChange(NumberPicker.OnScrollListener.SCROLL_STATE_IDLE);
890 | }
891 | }
892 | mVelocityTracker.recycle();
893 | mVelocityTracker = null;
894 | }
895 | break;
896 | }
897 | return true;
898 | }
899 |
900 | @Override
901 | public boolean dispatchTouchEvent(MotionEvent event) {
902 | final int action = event.getAction() & MotionEvent.ACTION_MASK;
903 | switch (action) {
904 | case MotionEvent.ACTION_CANCEL:
905 | case MotionEvent.ACTION_UP:
906 | removeAllCallbacks();
907 | break;
908 | }
909 | return super.dispatchTouchEvent(event);
910 | }
911 |
912 | @Override
913 | public boolean dispatchKeyEvent(KeyEvent event) {
914 | final int keyCode = event.getKeyCode();
915 | switch (keyCode) {
916 | case KeyEvent.KEYCODE_DPAD_CENTER:
917 | case KeyEvent.KEYCODE_ENTER:
918 | removeAllCallbacks();
919 | break;
920 | case KeyEvent.KEYCODE_DPAD_DOWN:
921 | case KeyEvent.KEYCODE_DPAD_UP:
922 | switch (event.getAction()) {
923 | case KeyEvent.ACTION_DOWN:
924 | if (mWrapSelectorWheel || (keyCode == KeyEvent.KEYCODE_DPAD_DOWN)
925 | ? getValue() < getMaxValue() : getValue() > getMinValue()) {
926 | requestFocus();
927 | mLastHandledDownDpadKeyCode = keyCode;
928 | removeAllCallbacks();
929 | if (mFlingScroller.isFinished()) {
930 | changeValueByOne(keyCode == KeyEvent.KEYCODE_DPAD_DOWN);
931 | }
932 | return true;
933 | }
934 | break;
935 | case KeyEvent.ACTION_UP:
936 | if (mLastHandledDownDpadKeyCode == keyCode) {
937 | mLastHandledDownDpadKeyCode = -1;
938 | return true;
939 | }
940 | break;
941 | }
942 | }
943 | return super.dispatchKeyEvent(event);
944 | }
945 |
946 | @Override
947 | public boolean dispatchTrackballEvent(MotionEvent event) {
948 | final int action = event.getAction() & MotionEvent.ACTION_MASK;
949 | switch (action) {
950 | case MotionEvent.ACTION_CANCEL:
951 | case MotionEvent.ACTION_UP:
952 | removeAllCallbacks();
953 | break;
954 | }
955 | return super.dispatchTrackballEvent(event);
956 | }
957 |
958 | @Override
959 | public void computeScroll() {
960 | Scroller scroller = mFlingScroller;
961 | if (scroller.isFinished()) {
962 | scroller = mAdjustScroller;
963 | if (scroller.isFinished()) {
964 | return;
965 | }
966 | }
967 | scroller.computeScrollOffset();
968 | if (isHorizontalMode()) {
969 | int currentScrollerX = scroller.getCurrX();
970 | if (mPreviousScrollerX == 0) {
971 | mPreviousScrollerX = scroller.getStartX();
972 | }
973 | scrollBy(currentScrollerX - mPreviousScrollerX, 0);
974 | mPreviousScrollerX = currentScrollerX;
975 | } else {
976 | int currentScrollerY = scroller.getCurrY();
977 | if (mPreviousScrollerY == 0) {
978 | mPreviousScrollerY = scroller.getStartY();
979 | }
980 | scrollBy(0, currentScrollerY - mPreviousScrollerY);
981 | mPreviousScrollerY = currentScrollerY;
982 | }
983 | if (scroller.isFinished()) {
984 | onScrollerFinished(scroller);
985 | } else {
986 | invalidate();
987 | }
988 | }
989 |
990 | @Override
991 | public void setEnabled(boolean enabled) {
992 | super.setEnabled(enabled);
993 | mSelectedText.setEnabled(enabled);
994 | }
995 |
996 | @Override
997 | public void scrollBy(int x, int y) {
998 | int[] selectorIndices = mSelectorIndices;
999 | int gap;
1000 | if (isHorizontalMode()) {
1001 | if (!mWrapSelectorWheel && x > 0
1002 | && selectorIndices[mWheelMiddleItemIndex] <= mMinValue) {
1003 | mCurrentScrollOffset = mInitialScrollOffset;
1004 | return;
1005 | }
1006 | if (!mWrapSelectorWheel && x < 0
1007 | && selectorIndices[mWheelMiddleItemIndex] >= mMaxValue) {
1008 | mCurrentScrollOffset = mInitialScrollOffset;
1009 | return;
1010 | }
1011 |
1012 | mCurrentScrollOffset += x;
1013 | gap = mSelectorTextGapWidth;
1014 | } else {
1015 | if (!mWrapSelectorWheel && y > 0
1016 | && selectorIndices[mWheelMiddleItemIndex] <= mMinValue) {
1017 | mCurrentScrollOffset = mInitialScrollOffset;
1018 | return;
1019 | }
1020 | if (!mWrapSelectorWheel && y < 0
1021 | && selectorIndices[mWheelMiddleItemIndex] >= mMaxValue) {
1022 | mCurrentScrollOffset = mInitialScrollOffset;
1023 | return;
1024 | }
1025 |
1026 | mCurrentScrollOffset += y;
1027 | gap = mSelectorTextGapHeight;
1028 | }
1029 |
1030 | while (mCurrentScrollOffset - mInitialScrollOffset > gap) {
1031 | mCurrentScrollOffset -= mSelectorElementSize;
1032 | decrementSelectorIndices(selectorIndices);
1033 | setValueInternal(selectorIndices[mWheelMiddleItemIndex], true);
1034 | if (!mWrapSelectorWheel && selectorIndices[mWheelMiddleItemIndex] < mMinValue) {
1035 | mCurrentScrollOffset = mInitialScrollOffset;
1036 | }
1037 | }
1038 | while (mCurrentScrollOffset - mInitialScrollOffset < -gap) {
1039 | mCurrentScrollOffset += mSelectorElementSize;
1040 | incrementSelectorIndices(selectorIndices);
1041 | setValueInternal(selectorIndices[mWheelMiddleItemIndex], true);
1042 | if (!mWrapSelectorWheel && selectorIndices[mWheelMiddleItemIndex] > mMaxValue) {
1043 | mCurrentScrollOffset = mInitialScrollOffset;
1044 | }
1045 | }
1046 | }
1047 |
1048 | /**
1049 | * Sets the listener to be notified on change of the current value.
1050 | *
1051 | * @param onValueChangedListener The listener.
1052 | */
1053 | public void setOnValueChangedListener(NumberPicker.OnValueChangeListener onValueChangedListener) {
1054 | mOnValueChangeListener = onValueChangedListener;
1055 | }
1056 |
1057 | /**
1058 | * Set listener to be notified for scroll state changes.
1059 | *
1060 | * @param onScrollListener The listener.
1061 | */
1062 | public void setOnScrollListener(NumberPicker.OnScrollListener onScrollListener) {
1063 | mOnScrollListener = onScrollListener;
1064 | }
1065 |
1066 | /**
1067 | * Set the formatter to be used for formatting the current value.
1068 | *
1069 | * Note: If you have provided alternative values for the values this
1070 | * formatter is never invoked.
1071 | *
1072 | *
1073 | * @param formatter The formatter object. If formatter is null,
1074 | * {@link String#valueOf(int)} will be used.
1075 | *@see #setDisplayedValues(String[])
1076 | */
1077 | public void setFormatter(NumberPicker.Formatter formatter) {
1078 | if (formatter == mFormatter) {
1079 | return;
1080 | }
1081 | mFormatter = formatter;
1082 | initializeSelectorWheelIndices();
1083 | updateInputTextView();
1084 | }
1085 |
1086 | /**
1087 | * Set the current value for the number picker.
1088 | *
1089 | * If the argument is less than the {@link NumberPicker#getMinValue()} and
1090 | * {@link NumberPicker#getWrapSelectorWheel()} is false the
1091 | * current value is set to the {@link NumberPicker#getMinValue()} value.
1092 | *
1093 | *
1094 | * If the argument is less than the {@link NumberPicker#getMinValue()} and
1095 | * {@link NumberPicker#getWrapSelectorWheel()} is true the
1096 | * current value is set to the {@link NumberPicker#getMaxValue()} value.
1097 | *
1098 | *
1099 | * If the argument is less than the {@link NumberPicker#getMaxValue()} and
1100 | * {@link NumberPicker#getWrapSelectorWheel()} is false the
1101 | * current value is set to the {@link NumberPicker#getMaxValue()} value.
1102 | *
1103 | *
1104 | * If the argument is less than the {@link NumberPicker#getMaxValue()} and
1105 | * {@link NumberPicker#getWrapSelectorWheel()} is true the
1106 | * current value is set to the {@link NumberPicker#getMinValue()} value.
1107 | *
1108 | *
1109 | * @param value The current value.
1110 | * @see #setWrapSelectorWheel(boolean)
1111 | * @see #setMinValue(int)
1112 | * @see #setMaxValue(int)
1113 | */
1114 | public void setValue(int value) {
1115 | setValueInternal(value, false);
1116 | }
1117 |
1118 | /**
1119 | * Computes the max width if no such specified as an attribute.
1120 | */
1121 | private void tryComputeMaxWidth() {
1122 | if (!mComputeMaxWidth) {
1123 | return;
1124 | }
1125 | int maxTextWidth = 0;
1126 | if (mDisplayedValues == null) {
1127 | float maxDigitWidth = 0;
1128 | for (int i = 0; i <= 9; i++) {
1129 | final float digitWidth = mSelectorWheelPaint.measureText(formatNumberWithLocale(i));
1130 | if (digitWidth > maxDigitWidth) {
1131 | maxDigitWidth = digitWidth;
1132 | }
1133 | }
1134 | int numberOfDigits = 0;
1135 | int current = mMaxValue;
1136 | while (current > 0) {
1137 | numberOfDigits++;
1138 | current = current / 10;
1139 | }
1140 | maxTextWidth = (int) (numberOfDigits * maxDigitWidth);
1141 | } else {
1142 | final int valueCount = mDisplayedValues.length;
1143 | for (int i = 0; i < valueCount; i++) {
1144 | final float textWidth = mSelectorWheelPaint.measureText(mDisplayedValues[i]);
1145 | if (textWidth > maxTextWidth) {
1146 | maxTextWidth = (int) textWidth;
1147 | }
1148 | }
1149 | }
1150 | maxTextWidth += mSelectedText.getPaddingLeft() + mSelectedText.getPaddingRight();
1151 | if (mMaxWidth != maxTextWidth) {
1152 | if (maxTextWidth > mMinWidth) {
1153 | mMaxWidth = maxTextWidth;
1154 | } else {
1155 | mMaxWidth = mMinWidth;
1156 | }
1157 | invalidate();
1158 | }
1159 | }
1160 |
1161 | /**
1162 | * Gets whether the selector wheel wraps when reaching the min/max value.
1163 | *
1164 | * @return True if the selector wheel wraps.
1165 | *
1166 | * @see #getMinValue()
1167 | * @see #getMaxValue()
1168 | */
1169 | public boolean getWrapSelectorWheel() {
1170 | return mWrapSelectorWheel;
1171 | }
1172 |
1173 | /**
1174 | * Sets whether the selector wheel shown during flinging/scrolling should
1175 | * wrap around the {@link NumberPicker#getMinValue()} and
1176 | * {@link NumberPicker#getMaxValue()} values.
1177 | *
1178 | * By default if the range (max - min) is more than the number of items shown
1179 | * on the selector wheel the selector wheel wrapping is enabled.
1180 | *
1181 | *
1182 | * Note: If the number of items, i.e. the range (
1183 | * {@link #getMaxValue()} - {@link #getMinValue()}) is less than
1184 | * the number of items shown on the selector wheel, the selector wheel will
1185 | * not wrap. Hence, in such a case calling this method is a NOP.
1186 | *
1187 | *
1188 | * @param wrapSelectorWheel Whether to wrap.
1189 | */
1190 | public void setWrapSelectorWheel(boolean wrapSelectorWheel) {
1191 | final boolean wrappingAllowed = (mMaxValue - mMinValue) >= mSelectorIndices.length;
1192 | if ((!wrapSelectorWheel || wrappingAllowed) && wrapSelectorWheel != mWrapSelectorWheel) {
1193 | mWrapSelectorWheel = wrapSelectorWheel;
1194 | }
1195 | }
1196 |
1197 | /**
1198 | * Sets the speed at which the numbers be incremented and decremented when
1199 | * the up and down buttons are long pressed respectively.
1200 | *
1201 | * The default value is 300 ms.
1202 | *
1203 | *
1204 | * @param intervalMillis The speed (in milliseconds) at which the numbers
1205 | * will be incremented and decremented.
1206 | */
1207 | public void setOnLongPressUpdateInterval(long intervalMillis) {
1208 | mLongPressUpdateInterval = intervalMillis;
1209 | }
1210 |
1211 | /**
1212 | * Returns the value of the picker.
1213 | *
1214 | * @return The value.
1215 | */
1216 | public int getValue() {
1217 | return mValue;
1218 | }
1219 |
1220 | /**
1221 | * Returns the min value of the picker.
1222 | *
1223 | * @return The min value
1224 | */
1225 | public int getMinValue() {
1226 | return mMinValue;
1227 | }
1228 |
1229 | /**
1230 | * Sets the min value of the picker.
1231 | *
1232 | * @param minValue The min value inclusive.
1233 | *
1234 | * Note: The length of the displayed values array
1235 | * set via {@link #setDisplayedValues(String[])} must be equal to the
1236 | * range of selectable numbers which is equal to
1237 | * {@link #getMaxValue()} - {@link #getMinValue()} + 1.
1238 | */
1239 | public void setMinValue(int minValue) {
1240 | // if (minValue < 0) {
1241 | // throw new IllegalArgumentException("minValue must be >= 0");
1242 | // }
1243 | mMinValue = minValue;
1244 | if (mMinValue > mValue) {
1245 | mValue = mMinValue;
1246 | }
1247 | boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
1248 | setWrapSelectorWheel(wrapSelectorWheel);
1249 | initializeSelectorWheelIndices();
1250 | updateInputTextView();
1251 | tryComputeMaxWidth();
1252 | invalidate();
1253 | }
1254 |
1255 | /**
1256 | * Returns the max value of the picker.
1257 | *
1258 | * @return The max value.
1259 | */
1260 | public int getMaxValue() {
1261 | return mMaxValue;
1262 | }
1263 |
1264 | /**
1265 | * Sets the max value of the picker.
1266 | *
1267 | * @param maxValue The max value inclusive.
1268 | *
1269 | * Note: The length of the displayed values array
1270 | * set via {@link #setDisplayedValues(String[])} must be equal to the
1271 | * range of selectable numbers which is equal to
1272 | * {@link #getMaxValue()} - {@link #getMinValue()} + 1.
1273 | */
1274 | public void setMaxValue(int maxValue) {
1275 | if (maxValue < 0) {
1276 | throw new IllegalArgumentException("maxValue must be >= 0");
1277 | }
1278 | mMaxValue = maxValue;
1279 | if (mMaxValue < mValue) {
1280 | mValue = mMaxValue;
1281 | }
1282 |
1283 | boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
1284 | setWrapSelectorWheel(wrapSelectorWheel);
1285 | initializeSelectorWheelIndices();
1286 | updateInputTextView();
1287 | tryComputeMaxWidth();
1288 | invalidate();
1289 | }
1290 |
1291 | /**
1292 | * Gets the values to be displayed instead of string values.
1293 | *
1294 | * @return The displayed values.
1295 | */
1296 | public String[] getDisplayedValues() {
1297 | return mDisplayedValues;
1298 | }
1299 |
1300 | /**
1301 | * Sets the values to be displayed.
1302 | *
1303 | * @param displayedValues The displayed values.
1304 | *
1305 | * Note: The length of the displayed values array
1306 | * must be equal to the range of selectable numbers which is equal to
1307 | * {@link #getMaxValue()} - {@link #getMinValue()} + 1.
1308 | */
1309 | public void setDisplayedValues(String[] displayedValues) {
1310 | if (mDisplayedValues == displayedValues) {
1311 | return;
1312 | }
1313 | mDisplayedValues = displayedValues;
1314 | if (mDisplayedValues != null) {
1315 | // Allow text entry rather than strictly numeric entry.
1316 | mSelectedText.setRawInputType(InputType.TYPE_CLASS_TEXT
1317 | | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
1318 | } else {
1319 | mSelectedText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
1320 | }
1321 | updateInputTextView();
1322 | initializeSelectorWheelIndices();
1323 | tryComputeMaxWidth();
1324 | }
1325 |
1326 | @Override
1327 | protected float getTopFadingEdgeStrength() {
1328 | return isHorizontalMode() ? 0: FADING_EDGE_STRENGTH;
1329 | }
1330 |
1331 | @Override
1332 | protected float getBottomFadingEdgeStrength() {
1333 | return isHorizontalMode() ? 0: FADING_EDGE_STRENGTH;
1334 | }
1335 |
1336 | @Override
1337 | protected float getLeftFadingEdgeStrength() {
1338 | return isHorizontalMode() ? FADING_EDGE_STRENGTH : 0;
1339 | }
1340 |
1341 | @Override
1342 | protected float getRightFadingEdgeStrength() {
1343 | return isHorizontalMode() ? FADING_EDGE_STRENGTH : 0;
1344 | }
1345 |
1346 | @Override
1347 | protected void onDetachedFromWindow() {
1348 | super.onDetachedFromWindow();
1349 | removeAllCallbacks();
1350 | }
1351 |
1352 | @Override
1353 | protected void onDraw(Canvas canvas) {
1354 | float x, y;
1355 | if (isHorizontalMode()) {
1356 | x = mCurrentScrollOffset;
1357 | y = mSelectedText.getBaseline() + mSelectedText.getTop();
1358 | } else {
1359 | x = (getRight() - getLeft()) / 2;
1360 | y = mCurrentScrollOffset;
1361 | }
1362 |
1363 | // draw the selector wheel
1364 | int[] selectorIndices = mSelectorIndices;
1365 | for (int i = 0; i < selectorIndices.length; i++) {
1366 | if (i == mWheelMiddleItemIndex) {
1367 | mSelectorWheelPaint.setColor(mSelectedTextColor);
1368 | } else {
1369 | mSelectorWheelPaint.setColor(mTextColor);
1370 | }
1371 |
1372 | int selectorIndex = selectorIndices[i];
1373 | String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex);
1374 | // Do not draw the middle item if input is visible since the input
1375 | // is shown only if the wheel is static and it covers the middle
1376 | // item. Otherwise, if the user starts editing the text via the
1377 | // IME he may see a dimmed version of the old value intermixed
1378 | // with the new one.
1379 | if (i != mWheelMiddleItemIndex || mSelectedText.getVisibility() != VISIBLE) {
1380 | canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint);
1381 | }
1382 |
1383 | if (isHorizontalMode()) {
1384 | x += mSelectorElementSize;
1385 | } else {
1386 | y += mSelectorElementSize;
1387 | }
1388 | }
1389 |
1390 | // draw the selection dividers
1391 | if (mSelectionDivider != null) {
1392 | if (isHorizontalMode()) {
1393 | // draw the left divider
1394 | int leftOfLeftDivider = mLeftOfSelectionDividerLeft;
1395 | int rightOfLeftDivider = leftOfLeftDivider + mSelectionDividerThickness;
1396 | mSelectionDivider.setBounds(leftOfLeftDivider, 0, rightOfLeftDivider, getBottom());
1397 | mSelectionDivider.draw(canvas);
1398 |
1399 | // draw the right divider
1400 | int rightOfRightDivider = mRightOfSelectionDividerRight;
1401 | int leftOfRightDivider = rightOfRightDivider - mSelectionDividerThickness;
1402 | mSelectionDivider.setBounds(leftOfRightDivider, 0, rightOfRightDivider, getBottom());
1403 | mSelectionDivider.draw(canvas);
1404 | } else {
1405 | // draw the top divider
1406 | int topOfTopDivider = mTopSelectionDividerTop;
1407 | int bottomOfTopDivider = topOfTopDivider + mSelectionDividerThickness;
1408 | mSelectionDivider.setBounds(0, topOfTopDivider, getRight(), bottomOfTopDivider);
1409 | mSelectionDivider.draw(canvas);
1410 |
1411 | // draw the bottom divider
1412 | int bottomOfBottomDivider = mBottomSelectionDividerBottom;
1413 | int topOfBottomDivider = bottomOfBottomDivider - mSelectionDividerThickness;
1414 | mSelectionDivider.setBounds(0, topOfBottomDivider, getRight(), bottomOfBottomDivider);
1415 | mSelectionDivider.draw(canvas);
1416 | }
1417 | }
1418 | }
1419 |
1420 | @Override
1421 | public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1422 | super.onInitializeAccessibilityEvent(event);
1423 | event.setClassName(NumberPicker.class.getName());
1424 | event.setScrollable(true);
1425 | final int scroll = (mMinValue + mValue) * mSelectorElementSize;
1426 | final int maxScroll = (mMaxValue - mMinValue) * mSelectorElementSize;
1427 | if (isHorizontalMode()) {
1428 | event.setScrollX(scroll);
1429 | event.setMaxScrollX(maxScroll);
1430 | } else {
1431 | event.setScrollY(scroll);
1432 | event.setMaxScrollY(maxScroll);
1433 | }
1434 | }
1435 |
1436 | /**
1437 | * Makes a measure spec that tries greedily to use the max value.
1438 | *
1439 | * @param measureSpec The measure spec.
1440 | * @param maxSize The max value for the size.
1441 | * @return A measure spec greedily imposing the max size.
1442 | */
1443 | private int makeMeasureSpec(int measureSpec, int maxSize) {
1444 | if (maxSize == SIZE_UNSPECIFIED) {
1445 | return measureSpec;
1446 | }
1447 | final int size = MeasureSpec.getSize(measureSpec);
1448 | final int mode = MeasureSpec.getMode(measureSpec);
1449 | switch (mode) {
1450 | case MeasureSpec.EXACTLY:
1451 | return measureSpec;
1452 | case MeasureSpec.AT_MOST:
1453 | return MeasureSpec.makeMeasureSpec(Math.min(size, maxSize), MeasureSpec.EXACTLY);
1454 | case MeasureSpec.UNSPECIFIED:
1455 | return MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.EXACTLY);
1456 | default:
1457 | throw new IllegalArgumentException("Unknown measure mode: " + mode);
1458 | }
1459 | }
1460 |
1461 | /**
1462 | * Utility to reconcile a desired size and state, with constraints imposed
1463 | * by a MeasureSpec. Tries to respect the min size, unless a different size
1464 | * is imposed by the constraints.
1465 | *
1466 | * @param minSize The minimal desired size.
1467 | * @param measuredSize The currently measured size.
1468 | * @param measureSpec The current measure spec.
1469 | * @return The resolved size and state.
1470 | */
1471 | private int resolveSizeAndStateRespectingMinSize(int minSize, int measuredSize, int measureSpec) {
1472 | if (minSize != SIZE_UNSPECIFIED) {
1473 | final int desiredWidth = Math.max(minSize, measuredSize);
1474 | return resolveSizeAndState(desiredWidth, measureSpec, 0);
1475 | } else {
1476 | return measuredSize;
1477 | }
1478 | }
1479 |
1480 | /**
1481 | * Utility to reconcile a desired size and state, with constraints imposed
1482 | * by a MeasureSpec. Will take the desired size, unless a different size
1483 | * is imposed by the constraints. The returned value is a compound integer,
1484 | * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
1485 | * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the resulting
1486 | * size is smaller than the size the view wants to be.
1487 | *
1488 | * @param size How big the view wants to be
1489 | * @param measureSpec Constraints imposed by the parent
1490 | * @return Size information bit mask as defined by
1491 | * {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
1492 | */
1493 | public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
1494 | int result = size;
1495 | int specMode = MeasureSpec.getMode(measureSpec);
1496 | int specSize = MeasureSpec.getSize(measureSpec);
1497 | switch (specMode) {
1498 | case MeasureSpec.UNSPECIFIED:
1499 | result = size;
1500 | break;
1501 | case MeasureSpec.AT_MOST:
1502 | if (specSize < size) {
1503 | result = specSize | MEASURED_STATE_TOO_SMALL;
1504 | } else {
1505 | result = size;
1506 | }
1507 | break;
1508 | case MeasureSpec.EXACTLY:
1509 | result = specSize;
1510 | break;
1511 | }
1512 | return result | (childMeasuredState&MEASURED_STATE_MASK);
1513 | }
1514 |
1515 | /**
1516 | * Resets the selector indices and clear the cached string representation of
1517 | * these indices.
1518 | */
1519 | private void initializeSelectorWheelIndices() {
1520 | mSelectorIndexToStringCache.clear();
1521 | int[] selectorIndices = mSelectorIndices;
1522 | int current = getValue();
1523 | for (int i = 0; i < mSelectorIndices.length; i++) {
1524 | int selectorIndex = current + (i - mWheelMiddleItemIndex);
1525 | if (mWrapSelectorWheel) {
1526 | selectorIndex = getWrappedSelectorIndex(selectorIndex);
1527 | }
1528 | selectorIndices[i] = selectorIndex;
1529 | ensureCachedScrollSelectorValue(selectorIndices[i]);
1530 | }
1531 | }
1532 |
1533 | /**
1534 | * Sets the current value of this NumberPicker.
1535 | *
1536 | * @param current The new value of the NumberPicker.
1537 | * @param notifyChange Whether to notify if the current value changed.
1538 | */
1539 | private void setValueInternal(int current, boolean notifyChange) {
1540 | if (mValue == current) {
1541 | return;
1542 | }
1543 | // Wrap around the values if we go past the start or end
1544 | if (mWrapSelectorWheel) {
1545 | current = getWrappedSelectorIndex(current);
1546 | } else {
1547 | current = Math.max(current, mMinValue);
1548 | current = Math.min(current, mMaxValue);
1549 | }
1550 | int previous = mValue;
1551 | mValue = current;
1552 | updateInputTextView();
1553 | if (notifyChange) {
1554 | notifyChange(previous, current);
1555 | }
1556 | initializeSelectorWheelIndices();
1557 | invalidate();
1558 | }
1559 |
1560 | /**
1561 | * Changes the current value by one which is increment or
1562 | * decrement based on the passes argument.
1563 | * decrement the current value.
1564 | *
1565 | * @param increment True to increment, false to decrement.
1566 | */
1567 | private void changeValueByOne(boolean increment) {
1568 | mSelectedText.setVisibility(View.INVISIBLE);
1569 | if (!moveToFinalScrollerPosition(mFlingScroller)) {
1570 | moveToFinalScrollerPosition(mAdjustScroller);
1571 | }
1572 | if (isHorizontalMode()) {
1573 | mPreviousScrollerX = 0;
1574 | if (increment) {
1575 | mFlingScroller.startScroll(0, 0, -mSelectorElementSize, 0, SNAP_SCROLL_DURATION);
1576 | } else {
1577 | mFlingScroller.startScroll(0, 0, mSelectorElementSize, 0, SNAP_SCROLL_DURATION);
1578 | }
1579 | } else {
1580 | mPreviousScrollerY = 0;
1581 | if (increment) {
1582 | mFlingScroller.startScroll(0, 0, 0, -mSelectorElementSize, SNAP_SCROLL_DURATION);
1583 | } else {
1584 | mFlingScroller.startScroll(0, 0, 0, mSelectorElementSize, SNAP_SCROLL_DURATION);
1585 | }
1586 | }
1587 | invalidate();
1588 | }
1589 |
1590 | private void initializeSelectorWheel() {
1591 | initializeSelectorWheelIndices();
1592 | int[] selectorIndices = mSelectorIndices;
1593 | int totalTextSize = selectorIndices.length * (int) mTextSize;
1594 | float textGapCount = selectorIndices.length;
1595 | int editTextTextPosition;
1596 | if (isHorizontalMode()) {
1597 | float totalTextGapWidth = (getRight() - getLeft()) - totalTextSize;
1598 | mSelectorTextGapWidth = (int) (totalTextGapWidth / textGapCount + 0.5f);
1599 | mSelectorElementSize = (int) mTextSize + mSelectorTextGapWidth;
1600 | // Ensure that the middle item is positioned the same as the text in mSelectedText
1601 | editTextTextPosition = mSelectedText.getRight() / 2;
1602 | } else {
1603 | float totalTextGapHeight = (getBottom() - getTop()) - totalTextSize;
1604 | mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f);
1605 | mSelectorElementSize = (int) mTextSize + mSelectorTextGapHeight;
1606 | // Ensure that the middle item is positioned the same as the text in mSelectedText
1607 | editTextTextPosition = mSelectedText.getBaseline() + mSelectedText.getTop();
1608 | }
1609 | mInitialScrollOffset = editTextTextPosition - (mSelectorElementSize * mWheelMiddleItemIndex);
1610 | mCurrentScrollOffset = mInitialScrollOffset;
1611 | updateInputTextView();
1612 | }
1613 |
1614 | private void initializeFadingEdges() {
1615 | if (isHorizontalMode()) {
1616 | setHorizontalFadingEdgeEnabled(true);
1617 | setFadingEdgeLength((getRight() - getLeft() - (int) mTextSize) / 2);
1618 | } else {
1619 | setVerticalFadingEdgeEnabled(true);
1620 | setFadingEdgeLength((getBottom() - getTop() - (int) mTextSize) / 2);
1621 | }
1622 | }
1623 |
1624 | /**
1625 | * Callback invoked upon completion of a given scroller.
1626 | */
1627 | private void onScrollerFinished(Scroller scroller) {
1628 | if (scroller == mFlingScroller) {
1629 | if (!ensureScrollWheelAdjusted()) {
1630 | updateInputTextView();
1631 | }
1632 | onScrollStateChange(NumberPicker.OnScrollListener.SCROLL_STATE_IDLE);
1633 | } else if (mScrollState != NumberPicker.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
1634 | updateInputTextView();
1635 | }
1636 | }
1637 |
1638 | /**
1639 | * Handles transition to a given scrollState
1640 | */
1641 | private void onScrollStateChange(int scrollState) {
1642 | if (mScrollState == scrollState) {
1643 | return;
1644 | }
1645 | mScrollState = scrollState;
1646 | if (mOnScrollListener != null) {
1647 | mOnScrollListener.onScrollStateChange(this, scrollState);
1648 | }
1649 | }
1650 |
1651 | /**
1652 | * Flings the selector with the given velocity.
1653 | */
1654 | private void fling(int velocity) {
1655 | if (isHorizontalMode()) {
1656 | mPreviousScrollerX = 0;
1657 | if (velocity > 0) {
1658 | mFlingScroller.fling(0, 0, velocity, 0, 0, Integer.MAX_VALUE, 0, 0);
1659 | } else {
1660 | mFlingScroller.fling(Integer.MAX_VALUE, 0, velocity, 0, 0, Integer.MAX_VALUE, 0, 0);
1661 | }
1662 | } else {
1663 | mPreviousScrollerY = 0;
1664 | if (velocity > 0) {
1665 | mFlingScroller.fling(0, 0, 0, velocity, 0, 0, 0, Integer.MAX_VALUE);
1666 | } else {
1667 | mFlingScroller.fling(0, Integer.MAX_VALUE, 0, velocity, 0, 0, 0, Integer.MAX_VALUE);
1668 | }
1669 | }
1670 |
1671 | invalidate();
1672 | }
1673 |
1674 | /**
1675 | * @return The wrapped index selectorIndex value.
1676 | */
1677 | private int getWrappedSelectorIndex(int selectorIndex) {
1678 | if (selectorIndex > mMaxValue) {
1679 | return mMinValue + (selectorIndex - mMaxValue) % (mMaxValue - mMinValue) - 1;
1680 | } else if (selectorIndex < mMinValue) {
1681 | return mMaxValue - (mMinValue - selectorIndex) % (mMaxValue - mMinValue) + 1;
1682 | }
1683 | return selectorIndex;
1684 | }
1685 |
1686 | /**
1687 | * Increments the selectorIndices whose string representations
1688 | * will be displayed in the selector.
1689 | */
1690 | private void incrementSelectorIndices(int[] selectorIndices) {
1691 | for (int i = 0; i < selectorIndices.length - 1; i++) {
1692 | selectorIndices[i] = selectorIndices[i + 1];
1693 | }
1694 | int nextScrollSelectorIndex = selectorIndices[selectorIndices.length - 2] + 1;
1695 | if (mWrapSelectorWheel && nextScrollSelectorIndex > mMaxValue) {
1696 | nextScrollSelectorIndex = mMinValue;
1697 | }
1698 | selectorIndices[selectorIndices.length - 1] = nextScrollSelectorIndex;
1699 | ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
1700 | }
1701 |
1702 | /**
1703 | * Decrements the selectorIndices whose string representations
1704 | * will be displayed in the selector.
1705 | */
1706 | private void decrementSelectorIndices(int[] selectorIndices) {
1707 | for (int i = selectorIndices.length - 1; i > 0; i--) {
1708 | selectorIndices[i] = selectorIndices[i - 1];
1709 | }
1710 | int nextScrollSelectorIndex = selectorIndices[1] - 1;
1711 | if (mWrapSelectorWheel && nextScrollSelectorIndex < mMinValue) {
1712 | nextScrollSelectorIndex = mMaxValue;
1713 | }
1714 | selectorIndices[0] = nextScrollSelectorIndex;
1715 | ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
1716 | }
1717 |
1718 | /**
1719 | * Ensures we have a cached string representation of the given
1720 | * selectorIndex to avoid multiple instantiations of the same string.
1721 | */
1722 | private void ensureCachedScrollSelectorValue(int selectorIndex) {
1723 | SparseArray cache = mSelectorIndexToStringCache;
1724 | String scrollSelectorValue = cache.get(selectorIndex);
1725 | if (scrollSelectorValue != null) {
1726 | return;
1727 | }
1728 | if (selectorIndex < mMinValue || selectorIndex > mMaxValue) {
1729 | scrollSelectorValue = "";
1730 | } else {
1731 | if (mDisplayedValues != null) {
1732 | int displayedValueIndex = selectorIndex - mMinValue;
1733 | scrollSelectorValue = mDisplayedValues[displayedValueIndex];
1734 | } else {
1735 | scrollSelectorValue = formatNumber(selectorIndex);
1736 | }
1737 | }
1738 | cache.put(selectorIndex, scrollSelectorValue);
1739 | }
1740 |
1741 | private String formatNumber(int value) {
1742 | return (mFormatter != null) ? mFormatter.format(value) : formatNumberWithLocale(value);
1743 | }
1744 |
1745 | /**
1746 | * Updates the view of this NumberPicker. If displayValues were specified in
1747 | * the string corresponding to the index specified by the current value will
1748 | * be returned. Otherwise, the formatter specified in {@link #setFormatter}
1749 | * will be used to format the number.
1750 | *
1751 | * @return Whether the text was updated.
1752 | */
1753 | private boolean updateInputTextView() {
1754 | /*
1755 | * If we don't have displayed values then use the current number else
1756 | * find the correct value in the displayed values for the current
1757 | * number.
1758 | */
1759 | String text = (mDisplayedValues == null) ? formatNumber(mValue)
1760 | : mDisplayedValues[mValue - mMinValue];
1761 | if (!TextUtils.isEmpty(text) && !text.equals(mSelectedText.getText().toString())) {
1762 | mSelectedText.setText(text);
1763 | return true;
1764 | }
1765 |
1766 | return false;
1767 | }
1768 |
1769 | /**
1770 | * Notifies the listener, if registered, of a change of the value of this
1771 | * NumberPicker.
1772 | */
1773 | private void notifyChange(int previous, int current) {
1774 | if (mOnValueChangeListener != null) {
1775 | mOnValueChangeListener.onValueChange(this, previous, mValue);
1776 | }
1777 | }
1778 |
1779 | /**
1780 | * Posts a command for changing the current value by one.
1781 | *
1782 | * @param increment Whether to increment or decrement the value.
1783 | */
1784 | private void postChangeCurrentByOneFromLongPress(boolean increment, long delayMillis) {
1785 | if (mChangeCurrentByOneFromLongPressCommand == null) {
1786 | mChangeCurrentByOneFromLongPressCommand = new ChangeCurrentByOneFromLongPressCommand();
1787 | } else {
1788 | removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
1789 | }
1790 | mChangeCurrentByOneFromLongPressCommand.setStep(increment);
1791 | postDelayed(mChangeCurrentByOneFromLongPressCommand, delayMillis);
1792 | }
1793 |
1794 | /**
1795 | * Removes the command for changing the current value by one.
1796 | */
1797 | private void removeChangeCurrentByOneFromLongPress() {
1798 | if (mChangeCurrentByOneFromLongPressCommand != null) {
1799 | removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
1800 | }
1801 | }
1802 |
1803 | /**
1804 | * Removes all pending callback from the message queue.
1805 | */
1806 | private void removeAllCallbacks() {
1807 | if (mChangeCurrentByOneFromLongPressCommand != null) {
1808 | removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
1809 | }
1810 | if (mSetSelectionCommand != null) {
1811 | removeCallbacks(mSetSelectionCommand);
1812 | }
1813 | }
1814 |
1815 | /**
1816 | * @return The selected index given its displayed value.
1817 | */
1818 | private int getSelectedPos(String value) {
1819 | if (mDisplayedValues == null) {
1820 | try {
1821 | return Integer.parseInt(value);
1822 | } catch (NumberFormatException e) {
1823 | // Ignore as if it's not a number we don't care
1824 | }
1825 | } else {
1826 | for (int i = 0; i < mDisplayedValues.length; i++) {
1827 | // Don't force the user to type in jan when ja will do
1828 | value = value.toLowerCase();
1829 | if (mDisplayedValues[i].toLowerCase().startsWith(value)) {
1830 | return mMinValue + i;
1831 | }
1832 | }
1833 |
1834 | /*
1835 | * The user might have typed in a number into the month field i.e.
1836 | * 10 instead of OCT so support that too.
1837 | */
1838 | try {
1839 | return Integer.parseInt(value);
1840 | } catch (NumberFormatException e) {
1841 | // Ignore as if it's not a number we don't care
1842 | }
1843 | }
1844 | return mMinValue;
1845 | }
1846 |
1847 | /**
1848 | * Posts an {@link NumberPicker.SetSelectionCommand} from the given selectionStart
1849 | * to selectionEnd.
1850 | */
1851 | private void postSetSelectionCommand(int selectionStart, int selectionEnd) {
1852 | if (mSetSelectionCommand == null) {
1853 | mSetSelectionCommand = new SetSelectionCommand();
1854 | } else {
1855 | removeCallbacks(mSetSelectionCommand);
1856 | }
1857 | mSetSelectionCommand.mSelectionStart = selectionStart;
1858 | mSetSelectionCommand.mSelectionEnd = selectionEnd;
1859 | post(mSetSelectionCommand);
1860 | }
1861 |
1862 | /**
1863 | * The numbers accepted by the input text's {@link LayoutInflater.Filter}
1864 | */
1865 | private static final char[] DIGIT_CHARACTERS = new char[] {
1866 | // Latin digits are the common case
1867 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1868 | // Arabic-Indic
1869 | '\u0660', '\u0661', '\u0662', '\u0663', '\u0664',
1870 | '\u0665', '\u0666', '\u0667', '\u0668', '\u0669',
1871 | // Extended Arabic-Indic
1872 | '\u06f0', '\u06f1', '\u06f2', '\u06f3', '\u06f4',
1873 | '\u06f5', '\u06f6', '\u06f7', '\u06f8', '\u06f9',
1874 | // Negative
1875 | '-'
1876 | };
1877 |
1878 | /**
1879 | * Filter for accepting only valid indices or prefixes of the string
1880 | * representation of valid indices.
1881 | */
1882 | class InputTextFilter extends NumberKeyListener {
1883 |
1884 | // XXX This doesn't allow for range limits when controlled by a soft input method!
1885 | public int getInputType() {
1886 | return InputType.TYPE_CLASS_TEXT;
1887 | }
1888 |
1889 | @Override
1890 | protected char[] getAcceptedChars() {
1891 | return DIGIT_CHARACTERS;
1892 | }
1893 |
1894 | @Override
1895 | public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
1896 | if (mDisplayedValues == null) {
1897 | CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
1898 | if (filtered == null) {
1899 | filtered = source.subSequence(start, end);
1900 | }
1901 |
1902 | String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
1903 | + dest.subSequence(dend, dest.length());
1904 |
1905 | if ("".equals(result)) {
1906 | return result;
1907 | }
1908 | int val = getSelectedPos(result);
1909 |
1910 | /*
1911 | * Ensure the user can't type in a value greater than the max
1912 | * allowed. We have to allow less than min as the user might
1913 | * want to delete some numbers and then type a new number.
1914 | */
1915 | if (val > mMaxValue) {
1916 | return "";
1917 | } else {
1918 | return filtered;
1919 | }
1920 | } else {
1921 | CharSequence filtered = String.valueOf(source.subSequence(start, end));
1922 | if (TextUtils.isEmpty(filtered)) {
1923 | return "";
1924 | }
1925 | String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
1926 | + dest.subSequence(dend, dest.length());
1927 | String str = String.valueOf(result).toLowerCase();
1928 | for (String val : mDisplayedValues) {
1929 | String valLowerCase = val.toLowerCase();
1930 | if (valLowerCase.startsWith(str)) {
1931 | postSetSelectionCommand(result.length(), val.length());
1932 | return val.subSequence(dstart, val.length());
1933 | }
1934 | }
1935 | return "";
1936 | }
1937 | }
1938 | }
1939 |
1940 | /**
1941 | * Ensures that the scroll wheel is adjusted i.e. there is no offset and the
1942 | * middle element is in the middle of the widget.
1943 | *
1944 | * @return Whether an adjustment has been made.
1945 | */
1946 | private boolean ensureScrollWheelAdjusted() {
1947 | // adjust to the closest value
1948 | int delta = mInitialScrollOffset - mCurrentScrollOffset;
1949 | if (delta != 0) {
1950 | if (Math.abs(delta) > mSelectorElementSize / 2) {
1951 | delta += (delta > 0) ? -mSelectorElementSize : mSelectorElementSize;
1952 | }
1953 | if (isHorizontalMode()) {
1954 | mPreviousScrollerX = 0;
1955 | mAdjustScroller.startScroll(0, 0, delta, 0, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
1956 | } else {
1957 | mPreviousScrollerY = 0;
1958 | mAdjustScroller.startScroll(0, 0, 0, delta, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
1959 | }
1960 | invalidate();
1961 | return true;
1962 | }
1963 | return false;
1964 | }
1965 |
1966 | /**
1967 | * Command for setting the input text selection.
1968 | */
1969 | class SetSelectionCommand implements Runnable {
1970 | private int mSelectionStart;
1971 |
1972 | private int mSelectionEnd;
1973 |
1974 | public void run() {
1975 | mSelectedText.setSelection(mSelectionStart, mSelectionEnd);
1976 | }
1977 | }
1978 |
1979 | /**
1980 | * Command for changing the current value from a long press by one.
1981 | */
1982 | class ChangeCurrentByOneFromLongPressCommand implements Runnable {
1983 | private boolean mIncrement;
1984 |
1985 | private void setStep(boolean increment) {
1986 | mIncrement = increment;
1987 | }
1988 |
1989 | @Override
1990 | public void run() {
1991 | changeValueByOne(mIncrement);
1992 | postDelayed(this, mLongPressUpdateInterval);
1993 | }
1994 | }
1995 |
1996 | private String formatNumberWithLocale(int value) {
1997 | return String.format(Locale.getDefault(), "%d", value);
1998 | }
1999 |
2000 | private void setWidthAndHeight() {
2001 | if (isHorizontalMode()) {
2002 | mMinHeight = SIZE_UNSPECIFIED;
2003 | mMaxHeight = (int) dpToPx(DEFAULT_MIN_WIDTH);
2004 | mMinWidth = (int) dpToPx(DEFAULT_MAX_HEIGHT);
2005 | mMaxWidth = SIZE_UNSPECIFIED;
2006 | } else {
2007 | mMinHeight = SIZE_UNSPECIFIED;
2008 | mMaxHeight = (int) dpToPx(DEFAULT_MAX_HEIGHT);
2009 | mMinWidth = (int) dpToPx(DEFAULT_MIN_WIDTH);
2010 | mMaxWidth = SIZE_UNSPECIFIED;
2011 | }
2012 | }
2013 |
2014 | public void setDividerColor(@ColorInt int color) {
2015 | mSelectionDividerColor = color;
2016 | mSelectionDivider = new ColorDrawable(color);
2017 | }
2018 |
2019 | public void setDividerColorResource(@ColorRes int colorId) {
2020 | setDividerColor(ContextCompat.getColor(mContext, colorId));
2021 | }
2022 |
2023 | public void setDividerDistance(int distance) {
2024 | mSelectionDividersDistance = (int) dpToPx(distance);
2025 | }
2026 |
2027 | public void setDividerThickness(int thickness) {
2028 | mSelectionDividerThickness = (int) dpToPx(thickness);
2029 | }
2030 |
2031 | public void setOrientation(@NumberPicker.Orientation int orientation) {
2032 | mOrientation = orientation;
2033 | setWidthAndHeight();
2034 | }
2035 |
2036 | public void setWheelItemCount(final int count) {
2037 | mWheelItemCount = count;
2038 | mWheelMiddleItemIndex = mWheelItemCount / 2;
2039 | mSelectorIndices = new int[mWheelItemCount];
2040 | }
2041 |
2042 | public void setFormatter(final String formatter) {
2043 | if (TextUtils.isEmpty(formatter)) {
2044 | return;
2045 | }
2046 |
2047 | setFormatter(stringToFormatter(formatter));
2048 | }
2049 |
2050 | public void setFormatter(@StringRes int stringId) {
2051 | setFormatter(getResources().getString(stringId));
2052 | }
2053 |
2054 | public void setSelectedTextColor(@ColorInt int color) {
2055 | mSelectedTextColor = color;
2056 | mSelectedText.setTextColor(mSelectedTextColor);
2057 | }
2058 |
2059 | public void setSelectedTextColorResource(@ColorRes int colorId) {
2060 | setSelectedTextColor(ContextCompat.getColor(mContext, colorId));
2061 | }
2062 |
2063 | public void setTextColor(@ColorInt int color) {
2064 | mTextColor = color;
2065 | mSelectorWheelPaint.setColor(mTextColor);
2066 | }
2067 |
2068 | public void setTextColorResource(@ColorRes int colorId) {
2069 | setTextColor(ContextCompat.getColor(mContext, colorId));
2070 | }
2071 |
2072 | public void setTextSize(float textSize) {
2073 | mTextSize = textSize;
2074 | mSelectedText.setTextSize(pxToSp(mTextSize));
2075 | mSelectorWheelPaint.setTextSize(mTextSize);
2076 | }
2077 |
2078 | public void setTextSize(@DimenRes int dimenId) {
2079 | setTextSize(getResources().getDimension(dimenId));
2080 | }
2081 |
2082 | public void setTypeface(Typeface typeface) {
2083 | mTypeface = typeface;
2084 | if (mTypeface != null) {
2085 | mSelectedText.setTypeface(mTypeface);
2086 | mSelectorWheelPaint.setTypeface(mTypeface);
2087 | } else {
2088 | mSelectedText.setTypeface(Typeface.MONOSPACE);
2089 | mSelectorWheelPaint.setTypeface(Typeface.MONOSPACE);
2090 | }
2091 | }
2092 |
2093 | public void setTypeface(String string, int style) {
2094 | if (TextUtils.isEmpty(string)) {
2095 | return;
2096 | }
2097 | setTypeface(Typeface.create(string, style));
2098 | }
2099 |
2100 | public void setTypeface(String string) {
2101 | setTypeface(string, Typeface.NORMAL);
2102 | }
2103 |
2104 | public void setTypeface(@StringRes int stringId, int style) {
2105 | setTypeface(getResources().getString(stringId), style);
2106 | }
2107 |
2108 | public void setTypeface(@StringRes int stringId) {
2109 | setTypeface(stringId, Typeface.NORMAL);
2110 | }
2111 |
2112 | private NumberPicker.Formatter stringToFormatter(final String formatter) {
2113 | if (TextUtils.isEmpty(formatter)) {
2114 | return null;
2115 | }
2116 |
2117 | return new NumberPicker.Formatter() {
2118 | @Override
2119 | public String format(int i) {
2120 | return String.format(Locale.getDefault(), formatter, i);
2121 | }
2122 | };
2123 | }
2124 |
2125 | private float dpToPx(float dp) {
2126 | return dp * getResources().getDisplayMetrics().density;
2127 | }
2128 |
2129 | private float pxToDp(float px) {
2130 | return px / getResources().getDisplayMetrics().density;
2131 | }
2132 |
2133 | private float spToPx(float sp) {
2134 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
2135 | }
2136 |
2137 | private float pxToSp(float px) {
2138 | return px / getResources().getDisplayMetrics().scaledDensity;
2139 | }
2140 |
2141 | public boolean isHorizontalMode() {
2142 | return mOrientation == HORIZONTAL;
2143 | }
2144 |
2145 | public int getDividerColor() {
2146 | return mSelectionDividerColor;
2147 | }
2148 |
2149 | public float getDividerDistance() {
2150 | return pxToDp(mSelectionDividersDistance);
2151 | }
2152 |
2153 | public float getDividerThickness() {
2154 | return pxToDp(mSelectionDividerThickness);
2155 | }
2156 |
2157 | public int getOrientation() {
2158 | return mOrientation;
2159 | }
2160 |
2161 | public int getWheelItemCount() {
2162 | return mWheelItemCount;
2163 | }
2164 |
2165 | public NumberPicker.Formatter getFormatter() {
2166 | return mFormatter;
2167 | }
2168 |
2169 | public int getSelectedTextColor() {
2170 | return mSelectedTextColor;
2171 | }
2172 |
2173 | public int getTextColor() {
2174 | return mTextColor;
2175 | }
2176 |
2177 | public float getTextSize() {
2178 | return spToPx(mTextSize);
2179 | }
2180 |
2181 | public Typeface getTypeface() {
2182 | return mTypeface;
2183 | }
2184 |
2185 | }
2186 |
2187 |
--------------------------------------------------------------------------------