blob: 685e8de3c01ab86bb1629b48e9190bf293560b94 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Paul Westbrook68f2f542010-01-13 12:13:57 -080017package android.widget;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080018
Alan Viveretted2814282015-06-02 15:47:37 -070019import android.annotation.CallSuper;
Daniel Santiago Rivera11a57f22019-02-07 10:01:24 -080020import android.annotation.ColorInt;
21import android.annotation.FloatRange;
Tor Norbyed9273d62013-05-30 15:59:53 -070022import android.annotation.IntDef;
Clara Bayarri370a1b52018-11-22 17:54:10 +000023import android.annotation.IntRange;
24import android.annotation.Px;
Kirill Grouchnikov1d1e7db2016-04-14 16:20:14 -040025import android.annotation.TestApi;
Mathew Inwood978c6e22018-08-21 15:58:55 +010026import android.annotation.UnsupportedAppUsage;
Paul Westbrook68f2f542010-01-13 12:13:57 -080027import android.annotation.Widget;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.content.Context;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080029import android.content.res.ColorStateList;
30import android.content.res.TypedArray;
31import android.graphics.Canvas;
32import android.graphics.Color;
33import android.graphics.Paint;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080034import android.graphics.Paint.Align;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -070035import android.graphics.Rect;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -080036import android.graphics.drawable.Drawable;
Clara Bayarri370a1b52018-11-22 17:54:10 +000037import android.os.Build;
Svetoslav Ganovaa780c12012-04-19 23:01:39 -070038import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.text.InputFilter;
40import android.text.InputType;
41import android.text.Spanned;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080042import android.text.TextUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043import android.text.method.NumberKeyListener;
44import android.util.AttributeSet;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080045import android.util.SparseArray;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -080046import android.util.TypedValue;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080047import android.view.KeyEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048import android.view.LayoutInflater;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -070049import android.view.LayoutInflater.Filter;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080050import android.view.MotionEvent;
51import android.view.VelocityTracker;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052import android.view.View;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080053import android.view.ViewConfiguration;
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -070054import android.view.accessibility.AccessibilityEvent;
55import android.view.accessibility.AccessibilityManager;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080056import android.view.accessibility.AccessibilityNodeInfo;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -070057import android.view.accessibility.AccessibilityNodeProvider;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -080058import android.view.animation.DecelerateInterpolator;
Svetoslav Ganova2b41b42012-02-27 15:53:32 -080059import android.view.inputmethod.EditorInfo;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080060import android.view.inputmethod.InputMethodManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061
Aurimas Liutikas99441c52016-10-11 16:48:32 -070062import com.android.internal.R;
63
64import libcore.icu.LocaleData;
65
Tor Norbyed9273d62013-05-30 15:59:53 -070066import java.lang.annotation.Retention;
67import java.lang.annotation.RetentionPolicy;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -070068import java.util.ArrayList;
69import java.util.Collections;
70import java.util.List;
Fabrice Di Megliod88e3052012-09-21 12:15:23 -070071import java.util.Locale;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -070072
Paul Westbrook68f2f542010-01-13 12:13:57 -080073/**
Brian Duff0db4c3e2013-09-08 14:11:28 -070074 * A widget that enables the user to select a number from a predefined range.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -070075 * There are two flavors of this widget and which one is presented to the user
76 * depends on the current theme.
77 * <ul>
78 * <li>
79 * If the current theme is derived from {@link android.R.style#Theme} the widget
80 * presents the current value as an editable input field with an increment button
81 * above and a decrement button below. Long pressing the buttons allows for a quick
82 * change of the current value. Tapping on the input field allows to type in
83 * a desired value.
84 * </li>
85 * <li>
86 * If the current theme is derived from {@link android.R.style#Theme_Holo} or
87 * {@link android.R.style#Theme_Holo_Light} the widget presents the current
88 * value as an editable input field with a lesser value above and a greater
89 * value below. Tapping on the lesser or greater value selects it by animating
90 * the number axis up or down to make the chosen value current. Flinging up
91 * or down allows for multiple increments or decrements of the current value.
92 * Long pressing on the lesser and greater values also allows for a quick change
93 * of the current value. Tapping on the current value allows to type in a
94 * desired value.
95 * </li>
Clara Bayarri370a1b52018-11-22 17:54:10 +000096 * <li>
97 * If the current theme is derived from {@link android.R.style#Theme_Material}
98 * the widget presents the current value as a scrolling vertical selector with
99 * the selected value in the center and the previous and following numbers above
100 * and below, separated by a divider. The value is changed by flinging vertically.
101 * The thickness of the divider can be changed by using the
102 * {@link android.R.attr#selectionDividerHeight} attribute and the color of the
103 * divider can be changed by using the
104 * {@link android.R.attr#colorControlNormal} attribute.
105 * </li>
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700106 * </ul>
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800107 * <p>
108 * For an example of using this widget, see {@link android.widget.TimePicker}.
109 * </p>
Paul Westbrook68f2f542010-01-13 12:13:57 -0800110 */
111@Widget
112public class NumberPicker extends LinearLayout {
Tom Taylorfdf6db62009-09-09 11:37:58 -0700113
Paul Westbrook68f2f542010-01-13 12:13:57 -0800114 /**
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -0800115 * The number of items show in the selector wheel.
116 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100117 @UnsupportedAppUsage
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700118 private static final int SELECTOR_WHEEL_ITEM_COUNT = 3;
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -0800119
120 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800121 * The default update interval during long press.
122 */
123 private static final long DEFAULT_LONG_PRESS_UPDATE_INTERVAL = 300;
124
125 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800126 * The index of the middle selector item.
Paul Westbrook68f2f542010-01-13 12:13:57 -0800127 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100128 @UnsupportedAppUsage
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700129 private static final int SELECTOR_MIDDLE_ITEM_INDEX = SELECTOR_WHEEL_ITEM_COUNT / 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800130
Paul Westbrook68f2f542010-01-13 12:13:57 -0800131 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800132 * The coefficient by which to adjust (divide) the max fling velocity.
Paul Westbrook68f2f542010-01-13 12:13:57 -0800133 */
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800134 private static final int SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT = 8;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800135
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800136 /**
137 * The the duration for adjusting the selector wheel.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800138 */
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800139 private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800;
Tom Taylorfdf6db62009-09-09 11:37:58 -0700140
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800141 /**
Svetoslav Ganov42138042012-03-20 11:51:39 -0700142 * The duration of scrolling while snapping to a given position.
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700143 */
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -0700144 private static final int SNAP_SCROLL_DURATION = 300;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700145
146 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800147 * The strength of fading in the top and bottom while drawing the selector.
148 */
149 private static final float TOP_AND_BOTTOM_FADING_EDGE_STRENGTH = 0.9f;
150
151 /**
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800152 * The default unscaled height of the selection divider.
153 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700154 private static final int UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT = 2;
155
156 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700157 * The default unscaled distance between the selection dividers.
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700158 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700159 private static final int UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE = 48;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700160
161 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700162 * The resource id for the default layout.
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700163 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700164 private static final int DEFAULT_LAYOUT_RESOURCE_ID = R.layout.number_picker;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800165
166 /**
Svetoslav Ganov9f086d82011-11-29 18:27:23 -0800167 * Constant for unspecified size.
168 */
169 private static final int SIZE_UNSPECIFIED = -1;
170
171 /**
Doris Liubcfdead2015-06-19 15:00:24 -0700172 * User choice on whether the selector wheel should be wrapped.
173 */
174 private boolean mWrapSelectorWheelPreferred = true;
175
176 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800177 * Use a custom NumberPicker formatting callback to use two-digit minutes
178 * strings like "01". Keeping a static formatter etc. is the most efficient
179 * way to do this; it avoids creating temporary objects on every call to
180 * format().
181 */
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700182 private static class TwoDigitFormatter implements NumberPicker.Formatter {
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800183 final StringBuilder mBuilder = new StringBuilder();
184
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700185 char mZeroDigit;
186 java.util.Formatter mFmt;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800187
188 final Object[] mArgs = new Object[1];
189
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700190 TwoDigitFormatter() {
191 final Locale locale = Locale.getDefault();
192 init(locale);
193 }
194
195 private void init(Locale locale) {
196 mFmt = createFormatter(locale);
197 mZeroDigit = getZeroDigit(locale);
198 }
199
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800200 public String format(int value) {
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700201 final Locale currentLocale = Locale.getDefault();
202 if (mZeroDigit != getZeroDigit(currentLocale)) {
203 init(currentLocale);
204 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800205 mArgs[0] = value;
206 mBuilder.delete(0, mBuilder.length());
207 mFmt.format("%02d", mArgs);
208 return mFmt.toString();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800209 }
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700210
211 private static char getZeroDigit(Locale locale) {
212 return LocaleData.get(locale).zeroDigit;
213 }
214
215 private java.util.Formatter createFormatter(Locale locale) {
216 return new java.util.Formatter(mBuilder, locale);
217 }
218 }
219
220 private static final TwoDigitFormatter sTwoDigitFormatter = new TwoDigitFormatter();
221
Fabrice Di Meglioa65fe882012-09-23 11:54:51 -0700222 /**
223 * @hide
224 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100225 @UnsupportedAppUsage
Fabrice Di Meglioa65fe882012-09-23 11:54:51 -0700226 public static final Formatter getTwoDigitFormatter() {
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700227 return sTwoDigitFormatter;
228 }
229
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800230 /**
231 * The increment button.
232 */
233 private final ImageButton mIncrementButton;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800234
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800235 /**
236 * The decrement button.
237 */
238 private final ImageButton mDecrementButton;
239
240 /**
241 * The text for showing the current value.
242 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100243 @UnsupportedAppUsage
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800244 private final EditText mInputText;
245
246 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700247 * The distance between the two selection dividers.
248 */
249 private final int mSelectionDividersDistance;
250
251 /**
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700252 * The min height of this widget.
253 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100254 @UnsupportedAppUsage
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700255 private final int mMinHeight;
256
257 /**
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700258 * The max height of this widget.
259 */
260 private final int mMaxHeight;
261
262 /**
263 * The max width of this widget.
264 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100265 @UnsupportedAppUsage
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700266 private final int mMinWidth;
267
268 /**
269 * The max width of this widget.
270 */
271 private int mMaxWidth;
272
273 /**
274 * Flag whether to compute the max width.
275 */
276 private final boolean mComputeMaxWidth;
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700277
278 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800279 * The height of the text.
280 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100281 @UnsupportedAppUsage
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800282 private final int mTextSize;
283
284 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700285 * The height of the gap between text elements if the selector wheel.
286 */
287 private int mSelectorTextGapHeight;
288
289 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800290 * The values to be displayed instead the indices.
291 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800292 private String[] mDisplayedValues;
Paul Westbrook68f2f542010-01-13 12:13:57 -0800293
294 /**
295 * Lower value of the range of numbers allowed for the NumberPicker
296 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800297 private int mMinValue;
Paul Westbrook68f2f542010-01-13 12:13:57 -0800298
299 /**
300 * Upper value of the range of numbers allowed for the NumberPicker
301 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100302 @UnsupportedAppUsage
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800303 private int mMaxValue;
Paul Westbrook68f2f542010-01-13 12:13:57 -0800304
305 /**
306 * Current value of this NumberPicker
307 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800308 private int mValue;
Paul Westbrook68f2f542010-01-13 12:13:57 -0800309
310 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800311 * Listener to be notified upon current value change.
Paul Westbrook68f2f542010-01-13 12:13:57 -0800312 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100313 @UnsupportedAppUsage
Svetoslav Ganovcedc4462011-01-19 19:25:46 -0800314 private OnValueChangeListener mOnValueChangeListener;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800315
316 /**
317 * Listener to be notified upon scroll state change.
318 */
319 private OnScrollListener mOnScrollListener;
Tom Taylorfdf6db62009-09-09 11:37:58 -0700320
Paul Westbrook68f2f542010-01-13 12:13:57 -0800321 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800322 * Formatter for for displaying the current value.
Paul Westbrook68f2f542010-01-13 12:13:57 -0800323 */
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800324 private Formatter mFormatter;
325
326 /**
327 * The speed for updating the value form long press.
328 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800329 private long mLongPressUpdateInterval = DEFAULT_LONG_PRESS_UPDATE_INTERVAL;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800330
331 /**
332 * Cache for the string representation of selector indices.
333 */
334 private final SparseArray<String> mSelectorIndexToStringCache = new SparseArray<String>();
335
336 /**
337 * The selector indices whose value are show by the selector.
338 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100339 @UnsupportedAppUsage
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700340 private final int[] mSelectorIndices = new int[SELECTOR_WHEEL_ITEM_COUNT];
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800341
342 /**
343 * The {@link Paint} for drawing the selector.
344 */
Daniel Santiago Rivera11a57f22019-02-07 10:01:24 -0800345 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700346 private final Paint mSelectorWheelPaint;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800347
348 /**
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700349 * The {@link Drawable} for pressed virtual (increment/decrement) buttons.
350 */
351 private final Drawable mVirtualButtonPressedDrawable;
352
353 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800354 * The height of a selector element (text + gap).
355 */
356 private int mSelectorElementHeight;
357
358 /**
359 * The initial offset of the scroll selector.
360 */
361 private int mInitialScrollOffset = Integer.MIN_VALUE;
362
363 /**
364 * The current offset of the scroll selector.
365 */
366 private int mCurrentScrollOffset;
367
368 /**
369 * The {@link Scroller} responsible for flinging the selector.
370 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100371 @UnsupportedAppUsage
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800372 private final Scroller mFlingScroller;
373
374 /**
375 * The {@link Scroller} responsible for adjusting the selector.
376 */
377 private final Scroller mAdjustScroller;
378
379 /**
380 * The previous Y coordinate while scrolling the selector.
381 */
382 private int mPreviousScrollerY;
383
384 /**
385 * Handle to the reusable command for setting the input text selection.
386 */
387 private SetSelectionCommand mSetSelectionCommand;
388
389 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700390 * Handle to the reusable command for changing the current value from long
391 * press by one.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800392 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700393 private ChangeCurrentByOneFromLongPressCommand mChangeCurrentByOneFromLongPressCommand;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800394
395 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700396 * Command for beginning an edit of the current value via IME on long press.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800397 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700398 private BeginSoftInputOnLongPressCommand mBeginSoftInputOnLongPressCommand;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700399
400 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800401 * The Y position of the last down event.
402 */
403 private float mLastDownEventY;
404
405 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700406 * The time of the last down event.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800407 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700408 private long mLastDownEventTime;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800409
410 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700411 * The Y position of the last down or move event.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800412 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700413 private float mLastDownOrMoveEventY;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800414
415 /**
416 * Determines speed during touch scrolling.
417 */
418 private VelocityTracker mVelocityTracker;
419
420 /**
421 * @see ViewConfiguration#getScaledTouchSlop()
422 */
423 private int mTouchSlop;
424
425 /**
426 * @see ViewConfiguration#getScaledMinimumFlingVelocity()
427 */
428 private int mMinimumFlingVelocity;
429
430 /**
431 * @see ViewConfiguration#getScaledMaximumFlingVelocity()
432 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100433 @UnsupportedAppUsage
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800434 private int mMaximumFlingVelocity;
435
436 /**
437 * Flag whether the selector should wrap around.
438 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800439 private boolean mWrapSelectorWheel;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800440
441 /**
442 * The back ground color used to optimize scroller fading.
443 */
444 private final int mSolidColor;
445
446 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700447 * Flag whether this widget has a selector wheel.
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800448 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700449 private final boolean mHasSelectorWheel;
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800450
451 /**
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800452 * Divider for showing item to be selected while scrolling
453 */
Clara Bayarri370a1b52018-11-22 17:54:10 +0000454 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800455 private final Drawable mSelectionDivider;
456
457 /**
458 * The height of the selection divider.
459 */
Clara Bayarri370a1b52018-11-22 17:54:10 +0000460 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
461 private int mSelectionDividerHeight;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800462
463 /**
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800464 * The current scroll state of the number picker.
465 */
466 private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
467
468 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700469 * Flag whether to ignore move events - we ignore such when we show in IME
470 * to prevent the content from scrolling.
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800471 */
Svetoslav46a27ef2014-03-03 15:37:14 -0800472 private boolean mIgnoreMoveEvents;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800473
474 /**
Svetoslav46a27ef2014-03-03 15:37:14 -0800475 * Flag whether to perform a click on tap.
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700476 */
Svetoslav46a27ef2014-03-03 15:37:14 -0800477 private boolean mPerformClickOnTap;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700478
479 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700480 * The top of the top selection divider.
Svetoslav Ganova2b41b42012-02-27 15:53:32 -0800481 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700482 private int mTopSelectionDividerTop;
483
484 /**
485 * The bottom of the bottom selection divider.
486 */
487 private int mBottomSelectionDividerBottom;
488
489 /**
490 * The virtual id of the last hovered child.
491 */
492 private int mLastHoveredChildVirtualViewId;
493
494 /**
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700495 * Whether the increment virtual button is pressed.
496 */
497 private boolean mIncrementVirtualButtonPressed;
498
499 /**
500 * Whether the decrement virtual button is pressed.
501 */
502 private boolean mDecrementVirtualButtonPressed;
503
504 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700505 * Provider to report to clients the semantic structure of this widget.
506 */
507 private AccessibilityNodeProviderImpl mAccessibilityNodeProvider;
Svetoslav Ganova2b41b42012-02-27 15:53:32 -0800508
509 /**
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700510 * Helper class for managing pressed state of the virtual buttons.
511 */
512 private final PressedStateHelper mPressedStateHelper;
513
514 /**
Svetoslav Ganov5dc21d92012-10-07 18:54:42 -0700515 * The keycode of the last handled DPAD down event.
516 */
517 private int mLastHandledDownDpadKeyCode = -1;
518
519 /**
Craig Stout9eef3f42014-06-23 14:13:43 -0700520 * If true then the selector wheel is hidden until the picker has focus.
521 */
522 private boolean mHideWheelUntilFocused;
523
524 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800525 * Interface to listen for changes of the current value.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800526 */
Svetoslav Ganovcedc4462011-01-19 19:25:46 -0800527 public interface OnValueChangeListener {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800528
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800529 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800530 * Called upon a change of the current value.
531 *
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800532 * @param picker The NumberPicker associated with this listener.
533 * @param oldVal The previous value.
534 * @param newVal The new value.
535 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800536 void onValueChange(NumberPicker picker, int oldVal, int newVal);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800537 }
538
539 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800540 * Interface to listen for the picker scroll state.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800541 */
542 public interface OnScrollListener {
Tor Norbyed9273d62013-05-30 15:59:53 -0700543 /** @hide */
Jeff Sharkeyce8db992017-12-13 20:05:05 -0700544 @IntDef(prefix = { "SCROLL_STATE_" }, value = {
545 SCROLL_STATE_IDLE,
546 SCROLL_STATE_TOUCH_SCROLL,
547 SCROLL_STATE_FLING
548 })
Tor Norbyed9273d62013-05-30 15:59:53 -0700549 @Retention(RetentionPolicy.SOURCE)
550 public @interface ScrollState {}
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800551
552 /**
553 * The view is not scrolling.
554 */
555 public static int SCROLL_STATE_IDLE = 0;
556
557 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700558 * The user is scrolling using touch, and his finger is still on the screen.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800559 */
560 public static int SCROLL_STATE_TOUCH_SCROLL = 1;
561
562 /**
563 * The user had previously been scrolling using touch and performed a fling.
564 */
565 public static int SCROLL_STATE_FLING = 2;
566
567 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800568 * Callback invoked while the number picker scroll state has changed.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800569 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800570 * @param view The view whose scroll state is being reported.
571 * @param scrollState The current scroll state. One of
572 * {@link #SCROLL_STATE_IDLE},
573 * {@link #SCROLL_STATE_TOUCH_SCROLL} or
574 * {@link #SCROLL_STATE_IDLE}.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800575 */
Tor Norbyed9273d62013-05-30 15:59:53 -0700576 public void onScrollStateChange(NumberPicker view, @ScrollState int scrollState);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800577 }
578
579 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800580 * Interface used to format current value into a string for presentation.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800581 */
582 public interface Formatter {
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800583
584 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800585 * Formats a string representation of the current value.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800586 *
587 * @param value The currently selected value.
588 * @return A formatted string representation.
589 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800590 public String format(int value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800591 }
Tom Taylorfdf6db62009-09-09 11:37:58 -0700592
Paul Westbrook68f2f542010-01-13 12:13:57 -0800593 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800594 * Create a new number picker.
595 *
596 * @param context The application environment.
597 */
598 public NumberPicker(Context context) {
599 this(context, null);
600 }
601
602 /**
603 * Create a new number picker.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800604 *
605 * @param context The application environment.
606 * @param attrs A collection of attributes.
Paul Westbrook68f2f542010-01-13 12:13:57 -0800607 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800608 public NumberPicker(Context context, AttributeSet attrs) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800609 this(context, attrs, R.attr.numberPickerStyle);
610 }
611
612 /**
613 * Create a new number picker
614 *
615 * @param context the application environment.
616 * @param attrs a collection of attributes.
Alan Viverette617feb92013-09-09 18:09:13 -0700617 * @param defStyleAttr An attribute in the current theme that contains a
618 * reference to a style resource that supplies default values for
619 * the view. Can be 0 to not look for defaults.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800620 */
Alan Viverette617feb92013-09-09 18:09:13 -0700621 public NumberPicker(Context context, AttributeSet attrs, int defStyleAttr) {
622 this(context, attrs, defStyleAttr, 0);
623 }
624
625 /**
626 * Create a new number picker
627 *
628 * @param context the application environment.
629 * @param attrs a collection of attributes.
630 * @param defStyleAttr An attribute in the current theme that contains a
631 * reference to a style resource that supplies default values for
632 * the view. Can be 0 to not look for defaults.
633 * @param defStyleRes A resource identifier of a style resource that
634 * supplies default values for the view, used only if
635 * defStyleAttr is 0 or can not be found in the theme. Can be 0
636 * to not look for defaults.
637 */
638 public NumberPicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
639 super(context, attrs, defStyleAttr, defStyleRes);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800640
641 // process style attributes
Alan Viverette617feb92013-09-09 18:09:13 -0700642 final TypedArray attributesArray = context.obtainStyledAttributes(
643 attrs, R.styleable.NumberPicker, defStyleAttr, defStyleRes);
Aurimas Liutikasab324cf2019-02-07 16:46:38 -0800644 saveAttributeDataForStyleable(context, R.styleable.NumberPicker,
645 attrs, attributesArray, defStyleAttr, defStyleRes);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700646 final int layoutResId = attributesArray.getResourceId(
647 R.styleable.NumberPicker_internalLayout, DEFAULT_LAYOUT_RESOURCE_ID);
648
649 mHasSelectorWheel = (layoutResId != DEFAULT_LAYOUT_RESOURCE_ID);
650
Craig Stout9eef3f42014-06-23 14:13:43 -0700651 mHideWheelUntilFocused = attributesArray.getBoolean(
652 R.styleable.NumberPicker_hideWheelUntilFocused, false);
653
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800654 mSolidColor = attributesArray.getColor(R.styleable.NumberPicker_solidColor, 0);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700655
Alan Viveretted2814282015-06-02 15:47:37 -0700656 final Drawable selectionDivider = attributesArray.getDrawable(
657 R.styleable.NumberPicker_selectionDivider);
658 if (selectionDivider != null) {
659 selectionDivider.setCallback(this);
660 selectionDivider.setLayoutDirection(getLayoutDirection());
661 if (selectionDivider.isStateful()) {
662 selectionDivider.setState(getDrawableState());
663 }
664 }
665 mSelectionDivider = selectionDivider;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700666
667 final int defSelectionDividerHeight = (int) TypedValue.applyDimension(
668 TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT,
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800669 getResources().getDisplayMetrics());
670 mSelectionDividerHeight = attributesArray.getDimensionPixelSize(
671 R.styleable.NumberPicker_selectionDividerHeight, defSelectionDividerHeight);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700672
673 final int defSelectionDividerDistance = (int) TypedValue.applyDimension(
674 TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE,
675 getResources().getDisplayMetrics());
676 mSelectionDividersDistance = attributesArray.getDimensionPixelSize(
677 R.styleable.NumberPicker_selectionDividersDistance, defSelectionDividerDistance);
678
Svetoslav Ganove8331bd2012-03-02 15:15:35 -0800679 mMinHeight = attributesArray.getDimensionPixelSize(
680 R.styleable.NumberPicker_internalMinHeight, SIZE_UNSPECIFIED);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700681
Svetoslav Ganove8331bd2012-03-02 15:15:35 -0800682 mMaxHeight = attributesArray.getDimensionPixelSize(
683 R.styleable.NumberPicker_internalMaxHeight, SIZE_UNSPECIFIED);
Svetoslav Ganov9f086d82011-11-29 18:27:23 -0800684 if (mMinHeight != SIZE_UNSPECIFIED && mMaxHeight != SIZE_UNSPECIFIED
685 && mMinHeight > mMaxHeight) {
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700686 throw new IllegalArgumentException("minHeight > maxHeight");
687 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700688
689 mMinWidth = attributesArray.getDimensionPixelSize(
690 R.styleable.NumberPicker_internalMinWidth, SIZE_UNSPECIFIED);
691
692 mMaxWidth = attributesArray.getDimensionPixelSize(
693 R.styleable.NumberPicker_internalMaxWidth, SIZE_UNSPECIFIED);
Svetoslav Ganov9f086d82011-11-29 18:27:23 -0800694 if (mMinWidth != SIZE_UNSPECIFIED && mMaxWidth != SIZE_UNSPECIFIED
695 && mMinWidth > mMaxWidth) {
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700696 throw new IllegalArgumentException("minWidth > maxWidth");
697 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800698
Svetoslav Ganovf7c83bc2012-04-12 16:00:13 -0700699 mComputeMaxWidth = (mMaxWidth == SIZE_UNSPECIFIED);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700700
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700701 mVirtualButtonPressedDrawable = attributesArray.getDrawable(
702 R.styleable.NumberPicker_virtualButtonPressedDrawable);
703
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700704 attributesArray.recycle();
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800705
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700706 mPressedStateHelper = new PressedStateHelper();
707
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800708 // By default Linearlayout that we extend is not drawn. This is
709 // its draw() method is not called but dispatchDraw() is called
710 // directly (see ViewGroup.drawChild()). However, this class uses
711 // the fading edge effect implemented by View and we need our
712 // draw() method to be called. Therefore, we declare we will draw.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700713 setWillNotDraw(!mHasSelectorWheel);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800714
715 LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
716 Context.LAYOUT_INFLATER_SERVICE);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700717 inflater.inflate(layoutResId, this, true);
Paul Westbrook68f2f542010-01-13 12:13:57 -0800718
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800719 OnClickListener onClickListener = new OnClickListener() {
Paul Westbrook68f2f542010-01-13 12:13:57 -0800720 public void onClick(View v) {
Svetoslav Ganovb52d9722011-11-07 14:53:34 -0800721 hideSoftInput();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800722 mInputText.clearFocus();
723 if (v.getId() == R.id.increment) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700724 changeValueByOne(true);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800725 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700726 changeValueByOne(false);
Paul Westbrook68f2f542010-01-13 12:13:57 -0800727 }
728 }
729 };
730
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800731 OnLongClickListener onLongClickListener = new OnLongClickListener() {
Paul Westbrook68f2f542010-01-13 12:13:57 -0800732 public boolean onLongClick(View v) {
Svetoslav Ganovb52d9722011-11-07 14:53:34 -0800733 hideSoftInput();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800734 mInputText.clearFocus();
735 if (v.getId() == R.id.increment) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700736 postChangeCurrentByOneFromLongPress(true, 0);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800737 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700738 postChangeCurrentByOneFromLongPress(false, 0);
Paul Westbrook68f2f542010-01-13 12:13:57 -0800739 }
740 return true;
741 }
742 };
743
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800744 // increment button
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700745 if (!mHasSelectorWheel) {
Alan Viverette51efddb2017-04-05 10:00:01 -0400746 mIncrementButton = findViewById(R.id.increment);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700747 mIncrementButton.setOnClickListener(onClickListener);
748 mIncrementButton.setOnLongClickListener(onLongClickListener);
749 } else {
750 mIncrementButton = null;
751 }
Paul Westbrook68f2f542010-01-13 12:13:57 -0800752
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800753 // decrement button
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700754 if (!mHasSelectorWheel) {
Alan Viverette51efddb2017-04-05 10:00:01 -0400755 mDecrementButton = findViewById(R.id.decrement);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700756 mDecrementButton.setOnClickListener(onClickListener);
757 mDecrementButton.setOnLongClickListener(onLongClickListener);
758 } else {
759 mDecrementButton = null;
760 }
Tom Taylorfdf6db62009-09-09 11:37:58 -0700761
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800762 // input text
Alan Viverette51efddb2017-04-05 10:00:01 -0400763 mInputText = findViewById(R.id.numberpicker_input);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800764 mInputText.setOnFocusChangeListener(new OnFocusChangeListener() {
765 public void onFocusChange(View v, boolean hasFocus) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700766 if (hasFocus) {
767 mInputText.selectAll();
768 } else {
769 mInputText.setSelection(0, 0);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800770 validateInputTextView(v);
771 }
772 }
773 });
774 mInputText.setFilters(new InputFilter[] {
775 new InputTextFilter()
776 });
Phil Weaver2b94bbe2017-06-06 10:48:47 -0700777 mInputText.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800778
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800779 mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
Svetoslav Ganova2b41b42012-02-27 15:53:32 -0800780 mInputText.setImeOptions(EditorInfo.IME_ACTION_DONE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800781
782 // initialize constants
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800783 ViewConfiguration configuration = ViewConfiguration.get(context);
784 mTouchSlop = configuration.getScaledTouchSlop();
785 mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
786 mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity()
787 / SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT;
788 mTextSize = (int) mInputText.getTextSize();
789
790 // create the selector wheel paint
791 Paint paint = new Paint();
792 paint.setAntiAlias(true);
793 paint.setTextAlign(Align.CENTER);
794 paint.setTextSize(mTextSize);
795 paint.setTypeface(mInputText.getTypeface());
796 ColorStateList colors = mInputText.getTextColors();
797 int color = colors.getColorForState(ENABLED_STATE_SET, Color.WHITE);
798 paint.setColor(color);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700799 mSelectorWheelPaint = paint;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800800
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800801 // create the fling and adjust scrollers
Svetoslav Ganovbf805622010-12-17 16:00:18 -0800802 mFlingScroller = new Scroller(getContext(), null, true);
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800803 mAdjustScroller = new Scroller(getContext(), new DecelerateInterpolator(2.5f));
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800804
805 updateInputTextView();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700806
807 // If not explicitly specified this view is important for accessibility.
808 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
809 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
810 }
Phil Weaver2b94bbe2017-06-06 10:48:47 -0700811
812 // Should be focusable by default, as the text view whose visibility changes is focusable
813 if (getFocusable() == View.FOCUSABLE_AUTO) {
814 setFocusable(View.FOCUSABLE);
815 setFocusableInTouchMode(true);
816 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800817 }
818
819 @Override
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800820 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700821 if (!mHasSelectorWheel) {
822 super.onLayout(changed, left, top, right, bottom);
823 return;
824 }
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700825 final int msrdWdth = getMeasuredWidth();
826 final int msrdHght = getMeasuredHeight();
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700827
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700828 // Input text centered horizontally.
829 final int inptTxtMsrdWdth = mInputText.getMeasuredWidth();
830 final int inptTxtMsrdHght = mInputText.getMeasuredHeight();
831 final int inptTxtLeft = (msrdWdth - inptTxtMsrdWdth) / 2;
832 final int inptTxtTop = (msrdHght - inptTxtMsrdHght) / 2;
833 final int inptTxtRight = inptTxtLeft + inptTxtMsrdWdth;
834 final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght;
835 mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700836
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700837 if (changed) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700838 // need to do all this when we know our size
839 initializeSelectorWheel();
840 initializeFadingEdges();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700841 mTopSelectionDividerTop = (getHeight() - mSelectionDividersDistance) / 2
842 - mSelectionDividerHeight;
843 mBottomSelectionDividerBottom = mTopSelectionDividerTop + 2 * mSelectionDividerHeight
844 + mSelectionDividersDistance;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700845 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800846 }
847
848 @Override
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700849 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700850 if (!mHasSelectorWheel) {
851 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
852 return;
853 }
Svetoslav Ganov698e1d52011-11-07 18:43:01 -0800854 // Try greedily to fit the max width and height.
855 final int newWidthMeasureSpec = makeMeasureSpec(widthMeasureSpec, mMaxWidth);
856 final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMaxHeight);
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700857 super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec);
Svetoslav Ganov698e1d52011-11-07 18:43:01 -0800858 // Flag if we are measured with width or height less than the respective min.
Svetoslav Ganov9f086d82011-11-29 18:27:23 -0800859 final int widthSize = resolveSizeAndStateRespectingMinSize(mMinWidth, getMeasuredWidth(),
860 widthMeasureSpec);
861 final int heightSize = resolveSizeAndStateRespectingMinSize(mMinHeight, getMeasuredHeight(),
862 heightMeasureSpec);
Svetoslav Ganov698e1d52011-11-07 18:43:01 -0800863 setMeasuredDimension(widthSize, heightSize);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700864 }
865
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700866 /**
867 * Move to the final position of a scroller. Ensures to force finish the scroller
868 * and if it is not at its final position a scroll of the selector wheel is
869 * performed to fast forward to the final position.
870 *
871 * @param scroller The scroller to whose final position to get.
872 * @return True of the a move was performed, i.e. the scroller was not in final position.
873 */
874 private boolean moveToFinalScrollerPosition(Scroller scroller) {
875 scroller.forceFinished(true);
876 int amountToScroll = scroller.getFinalY() - scroller.getCurrY();
877 int futureScrollOffset = (mCurrentScrollOffset + amountToScroll) % mSelectorElementHeight;
878 int overshootAdjustment = mInitialScrollOffset - futureScrollOffset;
879 if (overshootAdjustment != 0) {
880 if (Math.abs(overshootAdjustment) > mSelectorElementHeight / 2) {
881 if (overshootAdjustment > 0) {
882 overshootAdjustment -= mSelectorElementHeight;
883 } else {
884 overshootAdjustment += mSelectorElementHeight;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800885 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700886 }
887 amountToScroll += overshootAdjustment;
888 scrollBy(0, amountToScroll);
889 return true;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800890 }
891 return false;
892 }
893
894 @Override
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700895 public boolean onInterceptTouchEvent(MotionEvent event) {
896 if (!mHasSelectorWheel || !isEnabled()) {
897 return false;
898 }
899 final int action = event.getActionMasked();
900 switch (action) {
901 case MotionEvent.ACTION_DOWN: {
902 removeAllCallbacks();
Phil Weaver2b94bbe2017-06-06 10:48:47 -0700903 hideSoftInput();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700904 mLastDownOrMoveEventY = mLastDownEventY = event.getY();
905 mLastDownEventTime = event.getEventTime();
Svetoslav46a27ef2014-03-03 15:37:14 -0800906 mIgnoreMoveEvents = false;
907 mPerformClickOnTap = false;
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700908 // Handle pressed state before any state change.
909 if (mLastDownEventY < mTopSelectionDividerTop) {
910 if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
911 mPressedStateHelper.buttonPressDelayed(
912 PressedStateHelper.BUTTON_DECREMENT);
913 }
914 } else if (mLastDownEventY > mBottomSelectionDividerBottom) {
915 if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
916 mPressedStateHelper.buttonPressDelayed(
917 PressedStateHelper.BUTTON_INCREMENT);
918 }
919 }
920 // Make sure we support flinging inside scrollables.
Svetoslav Ganov83dc45c2012-04-12 16:19:32 -0700921 getParent().requestDisallowInterceptTouchEvent(true);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700922 if (!mFlingScroller.isFinished()) {
923 mFlingScroller.forceFinished(true);
924 mAdjustScroller.forceFinished(true);
925 onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
926 } else if (!mAdjustScroller.isFinished()) {
927 mFlingScroller.forceFinished(true);
928 mAdjustScroller.forceFinished(true);
929 } else if (mLastDownEventY < mTopSelectionDividerTop) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700930 postChangeCurrentByOneFromLongPress(
931 false, ViewConfiguration.getLongPressTimeout());
932 } else if (mLastDownEventY > mBottomSelectionDividerBottom) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700933 postChangeCurrentByOneFromLongPress(
934 true, ViewConfiguration.getLongPressTimeout());
935 } else {
Svetoslav46a27ef2014-03-03 15:37:14 -0800936 mPerformClickOnTap = true;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700937 postBeginSoftInputOnLongPressCommand();
938 }
939 return true;
940 }
941 }
942 return false;
943 }
944
945 @Override
946 public boolean onTouchEvent(MotionEvent event) {
947 if (!isEnabled() || !mHasSelectorWheel) {
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800948 return false;
949 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800950 if (mVelocityTracker == null) {
951 mVelocityTracker = VelocityTracker.obtain();
952 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700953 mVelocityTracker.addMovement(event);
954 int action = event.getActionMasked();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800955 switch (action) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700956 case MotionEvent.ACTION_MOVE: {
Svetoslav46a27ef2014-03-03 15:37:14 -0800957 if (mIgnoreMoveEvents) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700958 break;
959 }
960 float currentMoveY = event.getY();
961 if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800962 int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY);
963 if (deltaDownY > mTouchSlop) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700964 removeAllCallbacks();
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800965 onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800966 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700967 } else {
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700968 int deltaMoveY = (int) ((currentMoveY - mLastDownOrMoveEventY));
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700969 scrollBy(0, deltaMoveY);
970 invalidate();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800971 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700972 mLastDownOrMoveEventY = currentMoveY;
973 } break;
974 case MotionEvent.ACTION_UP: {
975 removeBeginSoftInputCommand();
976 removeChangeCurrentByOneFromLongPress();
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700977 mPressedStateHelper.cancel();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800978 VelocityTracker velocityTracker = mVelocityTracker;
979 velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
980 int initialVelocity = (int) velocityTracker.getYVelocity();
981 if (Math.abs(initialVelocity) > mMinimumFlingVelocity) {
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700982 fling(initialVelocity);
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800983 onScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800984 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700985 int eventY = (int) event.getY();
986 int deltaMoveY = (int) Math.abs(eventY - mLastDownEventY);
987 long deltaTime = event.getEventTime() - mLastDownEventTime;
988 if (deltaMoveY <= mTouchSlop && deltaTime < ViewConfiguration.getTapTimeout()) {
Svetoslav46a27ef2014-03-03 15:37:14 -0800989 if (mPerformClickOnTap) {
990 mPerformClickOnTap = false;
991 performClick();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700992 } else {
993 int selectorIndexOffset = (eventY / mSelectorElementHeight)
994 - SELECTOR_MIDDLE_ITEM_INDEX;
995 if (selectorIndexOffset > 0) {
996 changeValueByOne(true);
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700997 mPressedStateHelper.buttonTapped(
998 PressedStateHelper.BUTTON_INCREMENT);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700999 } else if (selectorIndexOffset < 0) {
1000 changeValueByOne(false);
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001001 mPressedStateHelper.buttonTapped(
1002 PressedStateHelper.BUTTON_DECREMENT);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001003 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001004 }
1005 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001006 ensureScrollWheelAdjusted();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001007 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001008 onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001009 }
1010 mVelocityTracker.recycle();
1011 mVelocityTracker = null;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001012 } break;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001013 }
1014 return true;
1015 }
1016
1017 @Override
1018 public boolean dispatchTouchEvent(MotionEvent event) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001019 final int action = event.getActionMasked();
1020 switch (action) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001021 case MotionEvent.ACTION_CANCEL:
1022 case MotionEvent.ACTION_UP:
1023 removeAllCallbacks();
1024 break;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001025 }
1026 return super.dispatchTouchEvent(event);
1027 }
1028
1029 @Override
1030 public boolean dispatchKeyEvent(KeyEvent event) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001031 final int keyCode = event.getKeyCode();
1032 switch (keyCode) {
1033 case KeyEvent.KEYCODE_DPAD_CENTER:
1034 case KeyEvent.KEYCODE_ENTER:
1035 removeAllCallbacks();
1036 break;
Svetoslav Ganov5dc21d92012-10-07 18:54:42 -07001037 case KeyEvent.KEYCODE_DPAD_DOWN:
1038 case KeyEvent.KEYCODE_DPAD_UP:
1039 if (!mHasSelectorWheel) {
1040 break;
1041 }
1042 switch (event.getAction()) {
1043 case KeyEvent.ACTION_DOWN:
Craig Stout9eef3f42014-06-23 14:13:43 -07001044 if (mWrapSelectorWheel || ((keyCode == KeyEvent.KEYCODE_DPAD_DOWN)
1045 ? getValue() < getMaxValue() : getValue() > getMinValue())) {
Svetoslav Ganov5dc21d92012-10-07 18:54:42 -07001046 requestFocus();
1047 mLastHandledDownDpadKeyCode = keyCode;
1048 removeAllCallbacks();
1049 if (mFlingScroller.isFinished()) {
Justin Mattson86cf0cd2012-10-08 11:46:28 -07001050 changeValueByOne(keyCode == KeyEvent.KEYCODE_DPAD_DOWN);
Svetoslav Ganov5dc21d92012-10-07 18:54:42 -07001051 }
1052 return true;
1053 }
1054 break;
1055 case KeyEvent.ACTION_UP:
1056 if (mLastHandledDownDpadKeyCode == keyCode) {
1057 mLastHandledDownDpadKeyCode = -1;
1058 return true;
1059 }
1060 break;
1061 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001062 }
1063 return super.dispatchKeyEvent(event);
1064 }
1065
1066 @Override
1067 public boolean dispatchTrackballEvent(MotionEvent event) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001068 final int action = event.getActionMasked();
1069 switch (action) {
1070 case MotionEvent.ACTION_CANCEL:
1071 case MotionEvent.ACTION_UP:
1072 removeAllCallbacks();
1073 break;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001074 }
1075 return super.dispatchTrackballEvent(event);
1076 }
1077
1078 @Override
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001079 protected boolean dispatchHoverEvent(MotionEvent event) {
1080 if (!mHasSelectorWheel) {
1081 return super.dispatchHoverEvent(event);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001082 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001083 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
1084 final int eventY = (int) event.getY();
1085 final int hoveredVirtualViewId;
1086 if (eventY < mTopSelectionDividerTop) {
1087 hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_DECREMENT;
1088 } else if (eventY > mBottomSelectionDividerBottom) {
1089 hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INCREMENT;
1090 } else {
1091 hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INPUT;
1092 }
1093 final int action = event.getActionMasked();
1094 AccessibilityNodeProviderImpl provider =
1095 (AccessibilityNodeProviderImpl) getAccessibilityNodeProvider();
1096 switch (action) {
1097 case MotionEvent.ACTION_HOVER_ENTER: {
1098 provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
1099 AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
1100 mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07001101 provider.performAction(hoveredVirtualViewId,
1102 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001103 } break;
1104 case MotionEvent.ACTION_HOVER_MOVE: {
1105 if (mLastHoveredChildVirtualViewId != hoveredVirtualViewId
1106 && mLastHoveredChildVirtualViewId != View.NO_ID) {
1107 provider.sendAccessibilityEventForVirtualView(
1108 mLastHoveredChildVirtualViewId,
1109 AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
1110 provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
1111 AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
1112 mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07001113 provider.performAction(hoveredVirtualViewId,
1114 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001115 }
1116 } break;
1117 case MotionEvent.ACTION_HOVER_EXIT: {
1118 provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
1119 AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
1120 mLastHoveredChildVirtualViewId = View.NO_ID;
1121 } break;
1122 }
1123 }
1124 return false;
1125 }
1126
1127 @Override
1128 public void computeScroll() {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001129 Scroller scroller = mFlingScroller;
1130 if (scroller.isFinished()) {
1131 scroller = mAdjustScroller;
1132 if (scroller.isFinished()) {
1133 return;
1134 }
1135 }
1136 scroller.computeScrollOffset();
1137 int currentScrollerY = scroller.getCurrY();
1138 if (mPreviousScrollerY == 0) {
1139 mPreviousScrollerY = scroller.getStartY();
1140 }
1141 scrollBy(0, currentScrollerY - mPreviousScrollerY);
1142 mPreviousScrollerY = currentScrollerY;
1143 if (scroller.isFinished()) {
1144 onScrollerFinished(scroller);
1145 } else {
1146 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001147 }
1148 }
Tom Taylorfdf6db62009-09-09 11:37:58 -07001149
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001150 @Override
1151 public void setEnabled(boolean enabled) {
1152 super.setEnabled(enabled);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001153 if (!mHasSelectorWheel) {
1154 mIncrementButton.setEnabled(enabled);
1155 }
1156 if (!mHasSelectorWheel) {
1157 mDecrementButton.setEnabled(enabled);
1158 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001159 mInputText.setEnabled(enabled);
1160 }
1161
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001162 @Override
1163 public void scrollBy(int x, int y) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001164 int[] selectorIndices = mSelectorIndices;
Phil Weaver2b94bbe2017-06-06 10:48:47 -07001165 int startScrollOffset = mCurrentScrollOffset;
Svetoslav Ganov34c06882011-01-03 01:32:34 -08001166 if (!mWrapSelectorWheel && y > 0
1167 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001168 mCurrentScrollOffset = mInitialScrollOffset;
1169 return;
1170 }
Svetoslav Ganov34c06882011-01-03 01:32:34 -08001171 if (!mWrapSelectorWheel && y < 0
1172 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001173 mCurrentScrollOffset = mInitialScrollOffset;
1174 return;
1175 }
1176 mCurrentScrollOffset += y;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001177 while (mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapHeight) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001178 mCurrentScrollOffset -= mSelectorElementHeight;
1179 decrementSelectorIndices(selectorIndices);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001180 setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001181 if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001182 mCurrentScrollOffset = mInitialScrollOffset;
1183 }
1184 }
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001185 while (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapHeight) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001186 mCurrentScrollOffset += mSelectorElementHeight;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001187 incrementSelectorIndices(selectorIndices);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001188 setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001189 if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001190 mCurrentScrollOffset = mInitialScrollOffset;
1191 }
1192 }
Phil Weaver2b94bbe2017-06-06 10:48:47 -07001193 if (startScrollOffset != mCurrentScrollOffset) {
1194 onScrollChanged(0, mCurrentScrollOffset, 0, startScrollOffset);
1195 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001196 }
Tom Taylorfdf6db62009-09-09 11:37:58 -07001197
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001198 @Override
Alan Viverettefd639172013-09-18 11:20:38 -07001199 protected int computeVerticalScrollOffset() {
Alan Viverette5ba99f52013-09-13 12:23:20 -07001200 return mCurrentScrollOffset;
1201 }
1202
1203 @Override
Alan Viverettefd639172013-09-18 11:20:38 -07001204 protected int computeVerticalScrollRange() {
1205 return (mMaxValue - mMinValue + 1) * mSelectorElementHeight;
1206 }
1207
1208 @Override
1209 protected int computeVerticalScrollExtent() {
1210 return getHeight();
Alan Viverette5ba99f52013-09-13 12:23:20 -07001211 }
1212
1213 @Override
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001214 public int getSolidColor() {
1215 return mSolidColor;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001216 }
1217
1218 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001219 * Sets the listener to be notified on change of the current value.
1220 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001221 * @param onValueChangedListener The listener.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001222 */
Svetoslav Ganovcedc4462011-01-19 19:25:46 -08001223 public void setOnValueChangedListener(OnValueChangeListener onValueChangedListener) {
1224 mOnValueChangeListener = onValueChangedListener;
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001225 }
1226
1227 /**
1228 * Set listener to be notified for scroll state changes.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001229 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001230 * @param onScrollListener The listener.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001231 */
1232 public void setOnScrollListener(OnScrollListener onScrollListener) {
1233 mOnScrollListener = onScrollListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001234 }
Tom Taylorfdf6db62009-09-09 11:37:58 -07001235
Paul Westbrook68f2f542010-01-13 12:13:57 -08001236 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001237 * Set the formatter to be used for formatting the current value.
1238 * <p>
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001239 * Note: If you have provided alternative values for the values this
1240 * formatter is never invoked.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001241 * </p>
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001242 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001243 * @param formatter The formatter object. If formatter is <code>null</code>,
1244 * {@link String#valueOf(int)} will be used.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001245 *@see #setDisplayedValues(String[])
Paul Westbrook68f2f542010-01-13 12:13:57 -08001246 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001247 public void setFormatter(Formatter formatter) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001248 if (formatter == mFormatter) {
Svetoslav Ganova911d4a2010-12-08 16:11:30 -08001249 return;
1250 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001251 mFormatter = formatter;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001252 initializeSelectorWheelIndices();
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001253 updateInputTextView();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001254 }
Tom Taylorfdf6db62009-09-09 11:37:58 -07001255
Paul Westbrook68f2f542010-01-13 12:13:57 -08001256 /**
1257 * Set the current value for the number picker.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001258 * <p>
1259 * If the argument is less than the {@link NumberPicker#getMinValue()} and
1260 * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the
1261 * current value is set to the {@link NumberPicker#getMinValue()} value.
1262 * </p>
1263 * <p>
1264 * If the argument is less than the {@link NumberPicker#getMinValue()} and
1265 * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the
1266 * current value is set to the {@link NumberPicker#getMaxValue()} value.
1267 * </p>
1268 * <p>
1269 * If the argument is less than the {@link NumberPicker#getMaxValue()} and
1270 * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the
1271 * current value is set to the {@link NumberPicker#getMaxValue()} value.
1272 * </p>
1273 * <p>
1274 * If the argument is less than the {@link NumberPicker#getMaxValue()} and
1275 * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the
1276 * current value is set to the {@link NumberPicker#getMinValue()} value.
1277 * </p>
Paul Westbrook68f2f542010-01-13 12:13:57 -08001278 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001279 * @param value The current value.
1280 * @see #setWrapSelectorWheel(boolean)
1281 * @see #setMinValue(int)
1282 * @see #setMaxValue(int)
Paul Westbrook68f2f542010-01-13 12:13:57 -08001283 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001284 public void setValue(int value) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001285 setValueInternal(value, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001286 }
1287
Svetoslav46a27ef2014-03-03 15:37:14 -08001288 @Override
1289 public boolean performClick() {
1290 if (!mHasSelectorWheel) {
1291 return super.performClick();
1292 } else if (!super.performClick()) {
1293 showSoftInput();
1294 }
1295 return true;
1296 }
1297
1298 @Override
1299 public boolean performLongClick() {
1300 if (!mHasSelectorWheel) {
1301 return super.performLongClick();
1302 } else if (!super.performLongClick()) {
1303 showSoftInput();
1304 mIgnoreMoveEvents = true;
1305 }
1306 return true;
1307 }
1308
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001309 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001310 * Shows the soft input for its input text.
1311 */
1312 private void showSoftInput() {
Yohei Yukawa484d4af2018-09-17 16:47:08 -07001313 InputMethodManager inputMethodManager =
1314 getContext().getSystemService(InputMethodManager.class);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001315 if (inputMethodManager != null) {
1316 if (mHasSelectorWheel) {
1317 mInputText.setVisibility(View.VISIBLE);
1318 }
1319 mInputText.requestFocus();
1320 inputMethodManager.showSoftInput(mInputText, 0);
1321 }
1322 }
1323
1324 /**
1325 * Hides the soft input if it is active for the input text.
Svetoslav Ganovb52d9722011-11-07 14:53:34 -08001326 */
1327 private void hideSoftInput() {
Yohei Yukawa484d4af2018-09-17 16:47:08 -07001328 InputMethodManager inputMethodManager =
1329 getContext().getSystemService(InputMethodManager.class);
Svetoslav Ganovb52d9722011-11-07 14:53:34 -08001330 if (inputMethodManager != null && inputMethodManager.isActive(mInputText)) {
1331 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
Phil Weaver2b94bbe2017-06-06 10:48:47 -07001332 }
1333 if (mHasSelectorWheel) {
1334 mInputText.setVisibility(View.INVISIBLE);
Svetoslav Ganovb52d9722011-11-07 14:53:34 -08001335 }
1336 }
1337
1338 /**
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001339 * Computes the max width if no such specified as an attribute.
1340 */
1341 private void tryComputeMaxWidth() {
1342 if (!mComputeMaxWidth) {
1343 return;
1344 }
1345 int maxTextWidth = 0;
1346 if (mDisplayedValues == null) {
1347 float maxDigitWidth = 0;
1348 for (int i = 0; i <= 9; i++) {
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07001349 final float digitWidth = mSelectorWheelPaint.measureText(formatNumberWithLocale(i));
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001350 if (digitWidth > maxDigitWidth) {
1351 maxDigitWidth = digitWidth;
1352 }
1353 }
1354 int numberOfDigits = 0;
1355 int current = mMaxValue;
1356 while (current > 0) {
1357 numberOfDigits++;
1358 current = current / 10;
1359 }
1360 maxTextWidth = (int) (numberOfDigits * maxDigitWidth);
1361 } else {
1362 final int valueCount = mDisplayedValues.length;
1363 for (int i = 0; i < valueCount; i++) {
1364 final float textWidth = mSelectorWheelPaint.measureText(mDisplayedValues[i]);
1365 if (textWidth > maxTextWidth) {
1366 maxTextWidth = (int) textWidth;
1367 }
1368 }
1369 }
1370 maxTextWidth += mInputText.getPaddingLeft() + mInputText.getPaddingRight();
1371 if (mMaxWidth != maxTextWidth) {
1372 if (maxTextWidth > mMinWidth) {
1373 mMaxWidth = maxTextWidth;
1374 } else {
1375 mMaxWidth = mMinWidth;
1376 }
1377 invalidate();
1378 }
1379 }
1380
1381 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001382 * Gets whether the selector wheel wraps when reaching the min/max value.
1383 *
1384 * @return True if the selector wheel wraps.
1385 *
1386 * @see #getMinValue()
1387 * @see #getMaxValue()
1388 */
1389 public boolean getWrapSelectorWheel() {
1390 return mWrapSelectorWheel;
1391 }
1392
1393 /**
1394 * Sets whether the selector wheel shown during flinging/scrolling should
1395 * wrap around the {@link NumberPicker#getMinValue()} and
1396 * {@link NumberPicker#getMaxValue()} values.
1397 * <p>
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001398 * By default if the range (max - min) is more than the number of items shown
1399 * on the selector wheel the selector wheel wrapping is enabled.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001400 * </p>
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -08001401 * <p>
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001402 * <strong>Note:</strong> If the number of items, i.e. the range (
1403 * {@link #getMaxValue()} - {@link #getMinValue()}) is less than
1404 * the number of items shown on the selector wheel, the selector wheel will
1405 * not wrap. Hence, in such a case calling this method is a NOP.
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -08001406 * </p>
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001407 *
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001408 * @param wrapSelectorWheel Whether to wrap.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001409 */
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001410 public void setWrapSelectorWheel(boolean wrapSelectorWheel) {
Doris Liubcfdead2015-06-19 15:00:24 -07001411 mWrapSelectorWheelPreferred = wrapSelectorWheel;
1412 updateWrapSelectorWheel();
1413
1414 }
1415
1416 /**
1417 * Whether or not the selector wheel should be wrapped is determined by user choice and whether
1418 * the choice is allowed. The former comes from {@link #setWrapSelectorWheel(boolean)}, the
1419 * latter is calculated based on min & max value set vs selector's visual length. Therefore,
1420 * this method should be called any time any of the 3 values (i.e. user choice, min and max
1421 * value) gets updated.
1422 */
1423 private void updateWrapSelectorWheel() {
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -08001424 final boolean wrappingAllowed = (mMaxValue - mMinValue) >= mSelectorIndices.length;
Doris Liubcfdead2015-06-19 15:00:24 -07001425 mWrapSelectorWheel = wrappingAllowed && mWrapSelectorWheelPreferred;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001426 }
1427
1428 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001429 * Sets the speed at which the numbers be incremented and decremented when
1430 * the up and down buttons are long pressed respectively.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001431 * <p>
1432 * The default value is 300 ms.
1433 * </p>
Paul Westbrook68f2f542010-01-13 12:13:57 -08001434 *
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001435 * @param intervalMillis The speed (in milliseconds) at which the numbers
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001436 * will be incremented and decremented.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001437 */
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001438 public void setOnLongPressUpdateInterval(long intervalMillis) {
1439 mLongPressUpdateInterval = intervalMillis;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001440 }
1441
1442 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001443 * Returns the value of the picker.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001444 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001445 * @return The value.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001446 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001447 public int getValue() {
1448 return mValue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001449 }
Paul Westbrook68f2f542010-01-13 12:13:57 -08001450
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001451 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001452 * Returns the min value of the picker.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001453 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001454 * @return The min value
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001455 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001456 public int getMinValue() {
1457 return mMinValue;
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001458 }
1459
1460 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001461 * Sets the min value of the picker.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001462 *
Svetoslav Ganov7018cfdc2012-11-19 12:33:41 -08001463 * @param minValue The min value inclusive.
1464 *
1465 * <strong>Note:</strong> The length of the displayed values array
1466 * set via {@link #setDisplayedValues(String[])} must be equal to the
1467 * range of selectable numbers which is equal to
1468 * {@link #getMaxValue()} - {@link #getMinValue()} + 1.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001469 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001470 public void setMinValue(int minValue) {
1471 if (mMinValue == minValue) {
1472 return;
1473 }
1474 if (minValue < 0) {
1475 throw new IllegalArgumentException("minValue must be >= 0");
1476 }
1477 mMinValue = minValue;
1478 if (mMinValue > mValue) {
1479 mValue = mMinValue;
1480 }
Doris Liubcfdead2015-06-19 15:00:24 -07001481 updateWrapSelectorWheel();
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001482 initializeSelectorWheelIndices();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001483 updateInputTextView();
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001484 tryComputeMaxWidth();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001485 invalidate();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001486 }
1487
1488 /**
1489 * Returns the max value of the picker.
1490 *
1491 * @return The max value.
1492 */
1493 public int getMaxValue() {
1494 return mMaxValue;
1495 }
1496
1497 /**
1498 * Sets the max value of the picker.
1499 *
Svetoslav Ganov7018cfdc2012-11-19 12:33:41 -08001500 * @param maxValue The max value inclusive.
1501 *
1502 * <strong>Note:</strong> The length of the displayed values array
1503 * set via {@link #setDisplayedValues(String[])} must be equal to the
1504 * range of selectable numbers which is equal to
1505 * {@link #getMaxValue()} - {@link #getMinValue()} + 1.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001506 */
1507 public void setMaxValue(int maxValue) {
1508 if (mMaxValue == maxValue) {
1509 return;
1510 }
1511 if (maxValue < 0) {
1512 throw new IllegalArgumentException("maxValue must be >= 0");
1513 }
1514 mMaxValue = maxValue;
1515 if (mMaxValue < mValue) {
1516 mValue = mMaxValue;
1517 }
Doris Liubcfdead2015-06-19 15:00:24 -07001518 updateWrapSelectorWheel();
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001519 initializeSelectorWheelIndices();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001520 updateInputTextView();
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001521 tryComputeMaxWidth();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001522 invalidate();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001523 }
1524
1525 /**
1526 * Gets the values to be displayed instead of string values.
1527 *
1528 * @return The displayed values.
1529 */
1530 public String[] getDisplayedValues() {
1531 return mDisplayedValues;
1532 }
1533
1534 /**
1535 * Sets the values to be displayed.
1536 *
1537 * @param displayedValues The displayed values.
Svetoslav Ganov7018cfdc2012-11-19 12:33:41 -08001538 *
1539 * <strong>Note:</strong> The length of the displayed values array
1540 * must be equal to the range of selectable numbers which is equal to
1541 * {@link #getMaxValue()} - {@link #getMinValue()} + 1.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001542 */
1543 public void setDisplayedValues(String[] displayedValues) {
1544 if (mDisplayedValues == displayedValues) {
1545 return;
1546 }
1547 mDisplayedValues = displayedValues;
1548 if (mDisplayedValues != null) {
1549 // Allow text entry rather than strictly numeric entry.
1550 mInputText.setRawInputType(InputType.TYPE_CLASS_TEXT
1551 | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
1552 } else {
1553 mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
1554 }
1555 updateInputTextView();
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001556 initializeSelectorWheelIndices();
Svetoslav Ganov9f086d82011-11-29 18:27:23 -08001557 tryComputeMaxWidth();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001558 }
1559
Kirill Grouchnikov1d1e7db2016-04-14 16:20:14 -04001560 /**
1561 * Retrieves the displayed value for the current selection in this picker.
1562 *
1563 * @hide
1564 */
1565 @TestApi
1566 public CharSequence getDisplayedValueForCurrentSelection() {
1567 // The cache field itself is initialized at declaration time, and since it's final, it
1568 // can't be null here. The cache is updated in ensureCachedScrollSelectorValue which is
1569 // called, directly or indirectly, on every call to setDisplayedValues, setFormatter,
1570 // setMinValue, setMaxValue and setValue, as well as user-driven interaction with the
1571 // picker. As such, the contents of the cache are always synced to the latest state of
1572 // the widget.
1573 return mSelectorIndexToStringCache.get(getValue());
1574 }
1575
Clara Bayarri370a1b52018-11-22 17:54:10 +00001576 /**
1577 * Set the height for the divider that separates the currently selected value from the others.
1578 * @param height The height to be set
1579 */
1580 public void setSelectionDividerHeight(@IntRange(from = 0) @Px int height) {
1581 mSelectionDividerHeight = height;
1582 invalidate();
1583 }
1584
1585 /**
1586 * Retrieve the height for the divider that separates the currently selected value from the
1587 * others.
1588 * @return The height of the divider
1589 */
Clara Bayarri596a6ed2019-03-11 13:51:37 +00001590 @Px
Clara Bayarri370a1b52018-11-22 17:54:10 +00001591 public int getSelectionDividerHeight() {
1592 return mSelectionDividerHeight;
1593 }
1594
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001595 @Override
1596 protected float getTopFadingEdgeStrength() {
1597 return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
1598 }
1599
1600 @Override
1601 protected float getBottomFadingEdgeStrength() {
1602 return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
1603 }
1604
1605 @Override
1606 protected void onDetachedFromWindow() {
Romain Guy46bfc482013-08-16 18:38:29 -07001607 super.onDetachedFromWindow();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001608 removeAllCallbacks();
1609 }
1610
Alan Viveretted2814282015-06-02 15:47:37 -07001611 @CallSuper
1612 @Override
1613 protected void drawableStateChanged() {
1614 super.drawableStateChanged();
1615
Alan Viverettead0020f2015-09-04 10:10:42 -04001616 final Drawable selectionDivider = mSelectionDivider;
1617 if (selectionDivider != null && selectionDivider.isStateful()
1618 && selectionDivider.setState(getDrawableState())) {
1619 invalidateDrawable(selectionDivider);
Alan Viveretted2814282015-06-02 15:47:37 -07001620 }
1621 }
1622
1623 @CallSuper
1624 @Override
1625 public void jumpDrawablesToCurrentState() {
1626 super.jumpDrawablesToCurrentState();
1627
1628 if (mSelectionDivider != null) {
1629 mSelectionDivider.jumpToCurrentState();
1630 }
1631 }
1632
1633 /** @hide */
1634 @Override
1635 public void onResolveDrawables(@ResolvedLayoutDir int layoutDirection) {
1636 super.onResolveDrawables(layoutDirection);
1637
1638 if (mSelectionDivider != null) {
1639 mSelectionDivider.setLayoutDirection(layoutDirection);
1640 }
1641 }
1642
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001643 @Override
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001644 protected void onDraw(Canvas canvas) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001645 if (!mHasSelectorWheel) {
1646 super.onDraw(canvas);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001647 return;
1648 }
Craig Stout9eef3f42014-06-23 14:13:43 -07001649 final boolean showSelectorWheel = mHideWheelUntilFocused ? hasFocus() : true;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001650 float x = (mRight - mLeft) / 2;
1651 float y = mCurrentScrollOffset;
1652
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001653 // draw the virtual buttons pressed state if needed
Craig Stout9eef3f42014-06-23 14:13:43 -07001654 if (showSelectorWheel && mVirtualButtonPressedDrawable != null
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001655 && mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
1656 if (mDecrementVirtualButtonPressed) {
1657 mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET);
1658 mVirtualButtonPressedDrawable.setBounds(0, 0, mRight, mTopSelectionDividerTop);
1659 mVirtualButtonPressedDrawable.draw(canvas);
1660 }
1661 if (mIncrementVirtualButtonPressed) {
1662 mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET);
1663 mVirtualButtonPressedDrawable.setBounds(0, mBottomSelectionDividerBottom, mRight,
1664 mBottom);
1665 mVirtualButtonPressedDrawable.draw(canvas);
1666 }
1667 }
1668
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001669 // draw the selector wheel
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001670 int[] selectorIndices = mSelectorIndices;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001671 for (int i = 0; i < selectorIndices.length; i++) {
1672 int selectorIndex = selectorIndices[i];
1673 String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001674 // Do not draw the middle item if input is visible since the input
1675 // is shown only if the wheel is static and it covers the middle
1676 // item. Otherwise, if the user starts editing the text via the
1677 // IME he may see a dimmed version of the old value intermixed
1678 // with the new one.
Craig Stout9eef3f42014-06-23 14:13:43 -07001679 if ((showSelectorWheel && i != SELECTOR_MIDDLE_ITEM_INDEX) ||
1680 (i == SELECTOR_MIDDLE_ITEM_INDEX && mInputText.getVisibility() != VISIBLE)) {
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001681 canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint);
1682 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001683 y += mSelectorElementHeight;
1684 }
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001685
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001686 // draw the selection dividers
Craig Stout9eef3f42014-06-23 14:13:43 -07001687 if (showSelectorWheel && mSelectionDivider != null) {
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001688 // draw the top divider
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001689 int topOfTopDivider = mTopSelectionDividerTop;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001690 int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight;
1691 mSelectionDivider.setBounds(0, topOfTopDivider, mRight, bottomOfTopDivider);
1692 mSelectionDivider.draw(canvas);
1693
1694 // draw the bottom divider
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001695 int bottomOfBottomDivider = mBottomSelectionDividerBottom;
1696 int topOfBottomDivider = bottomOfBottomDivider - mSelectionDividerHeight;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001697 mSelectionDivider.setBounds(0, topOfBottomDivider, mRight, bottomOfBottomDivider);
1698 mSelectionDivider.draw(canvas);
1699 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001700 }
1701
Alan Viverettea54956a2015-01-07 16:05:02 -08001702 /** @hide */
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -07001703 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001704 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
1705 super.onInitializeAccessibilityEventInternal(event);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001706 event.setClassName(NumberPicker.class.getName());
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001707 event.setScrollable(true);
1708 event.setScrollY((mMinValue + mValue) * mSelectorElementHeight);
1709 event.setMaxScrollY((mMaxValue - mMinValue) * mSelectorElementHeight);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001710 }
1711
1712 @Override
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001713 public AccessibilityNodeProvider getAccessibilityNodeProvider() {
1714 if (!mHasSelectorWheel) {
1715 return super.getAccessibilityNodeProvider();
1716 }
1717 if (mAccessibilityNodeProvider == null) {
1718 mAccessibilityNodeProvider = new AccessibilityNodeProviderImpl();
1719 }
1720 return mAccessibilityNodeProvider;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001721 }
1722
Paul Westbrook68f2f542010-01-13 12:13:57 -08001723 /**
Daniel Santiago Rivera11a57f22019-02-07 10:01:24 -08001724 * Sets the text color for all the states (normal, selected, focused) to be the given color.
1725 *
1726 * @param color A color value in the form 0xAARRGGBB.
1727 */
1728 public void setTextColor(@ColorInt int color) {
1729 mSelectorWheelPaint.setColor(color);
1730 mInputText.setTextColor(color);
1731 invalidate();
1732 }
1733
1734 /**
1735 * @return the text color.
1736 */
1737 @ColorInt
1738 public int getTextColor() {
1739 return mSelectorWheelPaint.getColor();
1740 }
1741
1742 /**
1743 * Sets the text size to the given value. This value must be > 0
1744 *
1745 * @param size The size in pixel units.
1746 */
1747 public void setTextSize(@FloatRange(from = 0.0, fromInclusive = false) float size) {
1748 mSelectorWheelPaint.setTextSize(size);
1749 mInputText.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
1750 invalidate();
1751 }
1752
1753 /**
1754 * @return the size (in pixels) of the text size in this NumberPicker.
1755 */
1756 @FloatRange(from = 0.0, fromInclusive = false)
1757 public float getTextSize() {
1758 return mSelectorWheelPaint.getTextSize();
1759 }
1760
1761 /**
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001762 * Makes a measure spec that tries greedily to use the max value.
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001763 *
1764 * @param measureSpec The measure spec.
Svetoslav Ganov698e1d52011-11-07 18:43:01 -08001765 * @param maxSize The max value for the size.
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001766 * @return A measure spec greedily imposing the max size.
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001767 */
Svetoslav Ganov698e1d52011-11-07 18:43:01 -08001768 private int makeMeasureSpec(int measureSpec, int maxSize) {
Svetoslav Ganov9f086d82011-11-29 18:27:23 -08001769 if (maxSize == SIZE_UNSPECIFIED) {
1770 return measureSpec;
1771 }
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001772 final int size = MeasureSpec.getSize(measureSpec);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001773 final int mode = MeasureSpec.getMode(measureSpec);
1774 switch (mode) {
1775 case MeasureSpec.EXACTLY:
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001776 return measureSpec;
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001777 case MeasureSpec.AT_MOST:
Svetoslav Ganov698e1d52011-11-07 18:43:01 -08001778 return MeasureSpec.makeMeasureSpec(Math.min(size, maxSize), MeasureSpec.EXACTLY);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001779 case MeasureSpec.UNSPECIFIED:
Svetoslav Ganov698e1d52011-11-07 18:43:01 -08001780 return MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.EXACTLY);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001781 default:
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001782 throw new IllegalArgumentException("Unknown measure mode: " + mode);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001783 }
1784 }
1785
1786 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001787 * Utility to reconcile a desired size and state, with constraints imposed
1788 * by a MeasureSpec. Tries to respect the min size, unless a different size
1789 * is imposed by the constraints.
Svetoslav Ganov9f086d82011-11-29 18:27:23 -08001790 *
1791 * @param minSize The minimal desired size.
1792 * @param measuredSize The currently measured size.
1793 * @param measureSpec The current measure spec.
1794 * @return The resolved size and state.
1795 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001796 private int resolveSizeAndStateRespectingMinSize(
1797 int minSize, int measuredSize, int measureSpec) {
Svetoslav Ganov9f086d82011-11-29 18:27:23 -08001798 if (minSize != SIZE_UNSPECIFIED) {
1799 final int desiredWidth = Math.max(minSize, measuredSize);
1800 return resolveSizeAndState(desiredWidth, measureSpec, 0);
1801 } else {
1802 return measuredSize;
1803 }
1804 }
1805
1806 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001807 * Resets the selector indices and clear the cached string representation of
1808 * these indices.
Svetoslav Ganova911d4a2010-12-08 16:11:30 -08001809 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01001810 @UnsupportedAppUsage
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001811 private void initializeSelectorWheelIndices() {
Svetoslav Ganova911d4a2010-12-08 16:11:30 -08001812 mSelectorIndexToStringCache.clear();
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001813 int[] selectorIndices = mSelectorIndices;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001814 int current = getValue();
1815 for (int i = 0; i < mSelectorIndices.length; i++) {
1816 int selectorIndex = current + (i - SELECTOR_MIDDLE_ITEM_INDEX);
1817 if (mWrapSelectorWheel) {
1818 selectorIndex = getWrappedSelectorIndex(selectorIndex);
1819 }
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001820 selectorIndices[i] = selectorIndex;
1821 ensureCachedScrollSelectorValue(selectorIndices[i]);
Svetoslav Ganova911d4a2010-12-08 16:11:30 -08001822 }
1823 }
1824
1825 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001826 * Sets the current value of this NumberPicker.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001827 *
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001828 * @param current The new value of the NumberPicker.
1829 * @param notifyChange Whether to notify if the current value changed.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001830 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001831 private void setValueInternal(int current, boolean notifyChange) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001832 if (mValue == current) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001833 return;
1834 }
1835 // Wrap around the values if we go past the start or end
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001836 if (mWrapSelectorWheel) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001837 current = getWrappedSelectorIndex(current);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001838 } else {
1839 current = Math.max(current, mMinValue);
1840 current = Math.min(current, mMaxValue);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001841 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001842 int previous = mValue;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001843 mValue = current;
Phil Weaver2b94bbe2017-06-06 10:48:47 -07001844 // If we're flinging, we'll update the text view at the end when it becomes visible
1845 if (mScrollState != OnScrollListener.SCROLL_STATE_FLING) {
1846 updateInputTextView();
1847 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001848 if (notifyChange) {
1849 notifyChange(previous, current);
1850 }
Svetoslav Ganovfac14f92012-04-12 16:51:04 -07001851 initializeSelectorWheelIndices();
1852 invalidate();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001853 }
1854
1855 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001856 * Changes the current value by one which is increment or
1857 * decrement based on the passes argument.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001858 * decrement the current value.
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001859 *
1860 * @param increment True to increment, false to decrement.
1861 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01001862 @UnsupportedAppUsage
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001863 private void changeValueByOne(boolean increment) {
1864 if (mHasSelectorWheel) {
Phil Weaver2b94bbe2017-06-06 10:48:47 -07001865 hideSoftInput();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001866 if (!moveToFinalScrollerPosition(mFlingScroller)) {
1867 moveToFinalScrollerPosition(mAdjustScroller);
1868 }
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001869 mPreviousScrollerY = 0;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001870 if (increment) {
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -07001871 mFlingScroller.startScroll(0, 0, 0, -mSelectorElementHeight, SNAP_SCROLL_DURATION);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001872 } else {
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -07001873 mFlingScroller.startScroll(0, 0, 0, mSelectorElementHeight, SNAP_SCROLL_DURATION);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001874 }
1875 invalidate();
1876 } else {
1877 if (increment) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001878 setValueInternal(mValue + 1, true);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001879 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001880 setValueInternal(mValue - 1, true);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001881 }
1882 }
1883 }
1884
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001885 private void initializeSelectorWheel() {
1886 initializeSelectorWheelIndices();
1887 int[] selectorIndices = mSelectorIndices;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001888 int totalTextHeight = selectorIndices.length * mTextSize;
Svetoslav Ganov01fa0d72011-06-28 22:08:23 -07001889 float totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001890 float textGapCount = selectorIndices.length;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001891 mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f);
1892 mSelectorElementHeight = mTextSize + mSelectorTextGapHeight;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001893 // Ensure that the middle item is positioned the same as the text in
1894 // mInputText
Chet Haaseeeafd422011-08-17 18:26:56 -07001895 int editTextTextPosition = mInputText.getBaseline() + mInputText.getTop();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001896 mInitialScrollOffset = editTextTextPosition
1897 - (mSelectorElementHeight * SELECTOR_MIDDLE_ITEM_INDEX);
Svetoslav Ganov6a19fcd2011-06-29 12:26:11 -07001898 mCurrentScrollOffset = mInitialScrollOffset;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001899 updateInputTextView();
1900 }
1901
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001902 private void initializeFadingEdges() {
1903 setVerticalFadingEdgeEnabled(true);
1904 setFadingEdgeLength((mBottom - mTop - mTextSize) / 2);
1905 }
1906
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001907 /**
1908 * Callback invoked upon completion of a given <code>scroller</code>.
1909 */
1910 private void onScrollerFinished(Scroller scroller) {
1911 if (scroller == mFlingScroller) {
Phil Weaver2b94bbe2017-06-06 10:48:47 -07001912 ensureScrollWheelAdjusted();
1913 updateInputTextView();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001914 onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001915 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001916 if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
1917 updateInputTextView();
1918 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001919 }
1920 }
1921
1922 /**
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001923 * Handles transition to a given <code>scrollState</code>
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001924 */
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001925 private void onScrollStateChange(int scrollState) {
1926 if (mScrollState == scrollState) {
1927 return;
1928 }
1929 mScrollState = scrollState;
1930 if (mOnScrollListener != null) {
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001931 mOnScrollListener.onScrollStateChange(this, scrollState);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001932 }
1933 }
1934
1935 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001936 * Flings the selector with the given <code>velocityY</code>.
1937 */
1938 private void fling(int velocityY) {
1939 mPreviousScrollerY = 0;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001940
Svetoslav Ganov234484a2011-12-07 19:06:35 -08001941 if (velocityY > 0) {
1942 mFlingScroller.fling(0, 0, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001943 } else {
Svetoslav Ganov234484a2011-12-07 19:06:35 -08001944 mFlingScroller.fling(0, Integer.MAX_VALUE, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001945 }
1946
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001947 invalidate();
1948 }
1949
1950 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001951 * @return The wrapped index <code>selectorIndex</code> value.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001952 */
1953 private int getWrappedSelectorIndex(int selectorIndex) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001954 if (selectorIndex > mMaxValue) {
1955 return mMinValue + (selectorIndex - mMaxValue) % (mMaxValue - mMinValue) - 1;
1956 } else if (selectorIndex < mMinValue) {
1957 return mMaxValue - (mMinValue - selectorIndex) % (mMaxValue - mMinValue) + 1;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001958 }
1959 return selectorIndex;
1960 }
1961
1962 /**
1963 * Increments the <code>selectorIndices</code> whose string representations
1964 * will be displayed in the selector.
1965 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001966 private void incrementSelectorIndices(int[] selectorIndices) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001967 for (int i = 0; i < selectorIndices.length - 1; i++) {
1968 selectorIndices[i] = selectorIndices[i + 1];
1969 }
1970 int nextScrollSelectorIndex = selectorIndices[selectorIndices.length - 2] + 1;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001971 if (mWrapSelectorWheel && nextScrollSelectorIndex > mMaxValue) {
1972 nextScrollSelectorIndex = mMinValue;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001973 }
1974 selectorIndices[selectorIndices.length - 1] = nextScrollSelectorIndex;
1975 ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
1976 }
1977
1978 /**
1979 * Decrements the <code>selectorIndices</code> whose string representations
1980 * will be displayed in the selector.
1981 */
1982 private void decrementSelectorIndices(int[] selectorIndices) {
1983 for (int i = selectorIndices.length - 1; i > 0; i--) {
1984 selectorIndices[i] = selectorIndices[i - 1];
1985 }
1986 int nextScrollSelectorIndex = selectorIndices[1] - 1;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001987 if (mWrapSelectorWheel && nextScrollSelectorIndex < mMinValue) {
1988 nextScrollSelectorIndex = mMaxValue;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001989 }
1990 selectorIndices[0] = nextScrollSelectorIndex;
1991 ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
1992 }
1993
1994 /**
1995 * Ensures we have a cached string representation of the given <code>
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001996 * selectorIndex</code> to avoid multiple instantiations of the same string.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001997 */
1998 private void ensureCachedScrollSelectorValue(int selectorIndex) {
1999 SparseArray<String> cache = mSelectorIndexToStringCache;
2000 String scrollSelectorValue = cache.get(selectorIndex);
2001 if (scrollSelectorValue != null) {
2002 return;
2003 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08002004 if (selectorIndex < mMinValue || selectorIndex > mMaxValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002005 scrollSelectorValue = "";
2006 } else {
2007 if (mDisplayedValues != null) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08002008 int displayedValueIndex = selectorIndex - mMinValue;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08002009 scrollSelectorValue = mDisplayedValues[displayedValueIndex];
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002010 } else {
2011 scrollSelectorValue = formatNumber(selectorIndex);
2012 }
2013 }
2014 cache.put(selectorIndex, scrollSelectorValue);
2015 }
2016
2017 private String formatNumber(int value) {
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07002018 return (mFormatter != null) ? mFormatter.format(value) : formatNumberWithLocale(value);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002019 }
2020
2021 private void validateInputTextView(View v) {
2022 String str = String.valueOf(((TextView) v).getText());
2023 if (TextUtils.isEmpty(str)) {
2024 // Restore to the old value as we don't allow empty values
2025 updateInputTextView();
2026 } else {
2027 // Check the new value and ensure it's in range
2028 int current = getSelectedPos(str.toString());
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002029 setValueInternal(current, true);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002030 }
2031 }
2032
2033 /**
2034 * Updates the view of this NumberPicker. If displayValues were specified in
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08002035 * the string corresponding to the index specified by the current value will
2036 * be returned. Otherwise, the formatter specified in {@link #setFormatter}
2037 * will be used to format the number.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002038 *
2039 * @return Whether the text was updated.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002040 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002041 private boolean updateInputTextView() {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002042 /*
2043 * If we don't have displayed values then use the current number else
2044 * find the correct value in the displayed values for the current
2045 * number.
2046 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002047 String text = (mDisplayedValues == null) ? formatNumber(mValue)
2048 : mDisplayedValues[mValue - mMinValue];
Phil Weaver2b94bbe2017-06-06 10:48:47 -07002049 if (!TextUtils.isEmpty(text)) {
2050 CharSequence beforeText = mInputText.getText();
2051 if (!text.equals(beforeText.toString())) {
2052 mInputText.setText(text);
Eugene Suslad4128ec2017-12-04 19:48:41 +00002053 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
Phil Weaver2b94bbe2017-06-06 10:48:47 -07002054 AccessibilityEvent event = AccessibilityEvent.obtain(
2055 AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
2056 mInputText.onInitializeAccessibilityEvent(event);
2057 mInputText.onPopulateAccessibilityEvent(event);
2058 event.setFromIndex(0);
2059 event.setRemovedCount(beforeText.length());
2060 event.setAddedCount(text.length());
2061 event.setBeforeText(beforeText);
2062 event.setSource(NumberPicker.this,
2063 AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INPUT);
2064 requestSendAccessibilityEvent(NumberPicker.this, event);
2065 }
2066 return true;
2067 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002068 }
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -07002069
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002070 return false;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002071 }
2072
2073 /**
2074 * Notifies the listener, if registered, of a change of the value of this
2075 * NumberPicker.
2076 */
2077 private void notifyChange(int previous, int current) {
Svetoslav Ganovcedc4462011-01-19 19:25:46 -08002078 if (mOnValueChangeListener != null) {
2079 mOnValueChangeListener.onValueChange(this, previous, mValue);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002080 }
2081 }
2082
2083 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07002084 * Posts a command for changing the current value by one.
2085 *
2086 * @param increment Whether to increment or decrement the value.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002087 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002088 private void postChangeCurrentByOneFromLongPress(boolean increment, long delayMillis) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07002089 if (mChangeCurrentByOneFromLongPressCommand == null) {
2090 mChangeCurrentByOneFromLongPressCommand = new ChangeCurrentByOneFromLongPressCommand();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002091 } else {
2092 removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002093 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002094 mChangeCurrentByOneFromLongPressCommand.setStep(increment);
2095 postDelayed(mChangeCurrentByOneFromLongPressCommand, delayMillis);
2096 }
2097
2098 /**
2099 * Removes the command for changing the current value by one.
2100 */
2101 private void removeChangeCurrentByOneFromLongPress() {
2102 if (mChangeCurrentByOneFromLongPressCommand != null) {
2103 removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
2104 }
2105 }
2106
2107 /**
2108 * Posts a command for beginning an edit of the current value via IME on
2109 * long press.
2110 */
2111 private void postBeginSoftInputOnLongPressCommand() {
2112 if (mBeginSoftInputOnLongPressCommand == null) {
2113 mBeginSoftInputOnLongPressCommand = new BeginSoftInputOnLongPressCommand();
2114 } else {
2115 removeCallbacks(mBeginSoftInputOnLongPressCommand);
2116 }
2117 postDelayed(mBeginSoftInputOnLongPressCommand, ViewConfiguration.getLongPressTimeout());
2118 }
2119
2120 /**
2121 * Removes the command for beginning an edit of the current value via IME.
2122 */
2123 private void removeBeginSoftInputCommand() {
2124 if (mBeginSoftInputOnLongPressCommand != null) {
2125 removeCallbacks(mBeginSoftInputOnLongPressCommand);
2126 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002127 }
2128
2129 /**
2130 * Removes all pending callback from the message queue.
2131 */
2132 private void removeAllCallbacks() {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07002133 if (mChangeCurrentByOneFromLongPressCommand != null) {
2134 removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002135 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002136 if (mSetSelectionCommand != null) {
Alan Viverette57fe7012016-09-09 14:29:35 -04002137 mSetSelectionCommand.cancel();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002138 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002139 if (mBeginSoftInputOnLongPressCommand != null) {
2140 removeCallbacks(mBeginSoftInputOnLongPressCommand);
2141 }
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07002142 mPressedStateHelper.cancel();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002143 }
2144
2145 /**
2146 * @return The selected index given its displayed <code>value</code>.
2147 */
2148 private int getSelectedPos(String value) {
2149 if (mDisplayedValues == null) {
2150 try {
2151 return Integer.parseInt(value);
2152 } catch (NumberFormatException e) {
2153 // Ignore as if it's not a number we don't care
2154 }
2155 } else {
2156 for (int i = 0; i < mDisplayedValues.length; i++) {
2157 // Don't force the user to type in jan when ja will do
2158 value = value.toLowerCase();
2159 if (mDisplayedValues[i].toLowerCase().startsWith(value)) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08002160 return mMinValue + i;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002161 }
2162 }
2163
2164 /*
2165 * The user might have typed in a number into the month field i.e.
2166 * 10 instead of OCT so support that too.
2167 */
2168 try {
2169 return Integer.parseInt(value);
2170 } catch (NumberFormatException e) {
2171
2172 // Ignore as if it's not a number we don't care
2173 }
2174 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08002175 return mMinValue;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002176 }
2177
2178 /**
Alan Viverette57fe7012016-09-09 14:29:35 -04002179 * Posts a {@link SetSelectionCommand} from the given
2180 * {@code selectionStart} to {@code selectionEnd}.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002181 */
2182 private void postSetSelectionCommand(int selectionStart, int selectionEnd) {
2183 if (mSetSelectionCommand == null) {
Alan Viverette57fe7012016-09-09 14:29:35 -04002184 mSetSelectionCommand = new SetSelectionCommand(mInputText);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002185 }
Alan Viverette57fe7012016-09-09 14:29:35 -04002186 mSetSelectionCommand.post(selectionStart, selectionEnd);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002187 }
2188
2189 /**
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07002190 * The numbers accepted by the input text's {@link Filter}
2191 */
2192 private static final char[] DIGIT_CHARACTERS = new char[] {
2193 // Latin digits are the common case
2194 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
2195 // Arabic-Indic
2196 '\u0660', '\u0661', '\u0662', '\u0663', '\u0664', '\u0665', '\u0666', '\u0667', '\u0668'
2197 , '\u0669',
2198 // Extended Arabic-Indic
2199 '\u06f0', '\u06f1', '\u06f2', '\u06f3', '\u06f4', '\u06f5', '\u06f6', '\u06f7', '\u06f8'
Martin Olsson 4962dde72013-07-05 07:07:33 +02002200 , '\u06f9',
2201 // Hindi and Marathi (Devanagari script)
2202 '\u0966', '\u0967', '\u0968', '\u0969', '\u096a', '\u096b', '\u096c', '\u096d', '\u096e'
2203 , '\u096f',
2204 // Bengali
2205 '\u09e6', '\u09e7', '\u09e8', '\u09e9', '\u09ea', '\u09eb', '\u09ec', '\u09ed', '\u09ee'
2206 , '\u09ef',
2207 // Kannada
2208 '\u0ce6', '\u0ce7', '\u0ce8', '\u0ce9', '\u0cea', '\u0ceb', '\u0cec', '\u0ced', '\u0cee'
2209 , '\u0cef'
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07002210 };
2211
2212 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002213 * Filter for accepting only valid indices or prefixes of the string
2214 * representation of valid indices.
2215 */
2216 class InputTextFilter extends NumberKeyListener {
2217
2218 // XXX This doesn't allow for range limits when controlled by a
2219 // soft input method!
2220 public int getInputType() {
2221 return InputType.TYPE_CLASS_TEXT;
2222 }
2223
2224 @Override
2225 protected char[] getAcceptedChars() {
2226 return DIGIT_CHARACTERS;
2227 }
2228
2229 @Override
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002230 public CharSequence filter(
2231 CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
Alan Viverette57fe7012016-09-09 14:29:35 -04002232 // We don't know what the output will be, so always cancel any
2233 // pending set selection command.
2234 if (mSetSelectionCommand != null) {
2235 mSetSelectionCommand.cancel();
2236 }
2237
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002238 if (mDisplayedValues == null) {
2239 CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
2240 if (filtered == null) {
2241 filtered = source.subSequence(start, end);
2242 }
2243
2244 String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
2245 + dest.subSequence(dend, dest.length());
2246
2247 if ("".equals(result)) {
2248 return result;
2249 }
2250 int val = getSelectedPos(result);
2251
2252 /*
2253 * Ensure the user can't type in a value greater than the max
2254 * allowed. We have to allow less than min as the user might
2255 * want to delete some numbers and then type a new number.
Sungmin Choi6d8a99f2013-01-25 18:26:46 +09002256 * And prevent multiple-"0" that exceeds the length of upper
2257 * bound number.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002258 */
Sungmin Choi6d8a99f2013-01-25 18:26:46 +09002259 if (val > mMaxValue || result.length() > String.valueOf(mMaxValue).length()) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002260 return "";
2261 } else {
2262 return filtered;
2263 }
2264 } else {
2265 CharSequence filtered = String.valueOf(source.subSequence(start, end));
2266 if (TextUtils.isEmpty(filtered)) {
2267 return "";
2268 }
2269 String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
2270 + dest.subSequence(dend, dest.length());
2271 String str = String.valueOf(result).toLowerCase();
2272 for (String val : mDisplayedValues) {
2273 String valLowerCase = val.toLowerCase();
2274 if (valLowerCase.startsWith(str)) {
2275 postSetSelectionCommand(result.length(), val.length());
2276 return val.subSequence(dstart, val.length());
2277 }
2278 }
2279 return "";
2280 }
2281 }
2282 }
2283
2284 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002285 * Ensures that the scroll wheel is adjusted i.e. there is no offset and the
2286 * middle element is in the middle of the widget.
2287 *
2288 * @return Whether an adjustment has been made.
2289 */
2290 private boolean ensureScrollWheelAdjusted() {
2291 // adjust to the closest value
2292 int deltaY = mInitialScrollOffset - mCurrentScrollOffset;
2293 if (deltaY != 0) {
2294 mPreviousScrollerY = 0;
2295 if (Math.abs(deltaY) > mSelectorElementHeight / 2) {
2296 deltaY += (deltaY > 0) ? -mSelectorElementHeight : mSelectorElementHeight;
2297 }
2298 mAdjustScroller.startScroll(0, 0, 0, deltaY, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
2299 invalidate();
2300 return true;
2301 }
2302 return false;
2303 }
2304
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07002305 class PressedStateHelper implements Runnable {
2306 public static final int BUTTON_INCREMENT = 1;
2307 public static final int BUTTON_DECREMENT = 2;
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -07002308
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07002309 private final int MODE_PRESS = 1;
2310 private final int MODE_TAPPED = 2;
2311
2312 private int mManagedButton;
2313 private int mMode;
2314
2315 public void cancel() {
2316 mMode = 0;
2317 mManagedButton = 0;
2318 NumberPicker.this.removeCallbacks(this);
2319 if (mIncrementVirtualButtonPressed) {
2320 mIncrementVirtualButtonPressed = false;
2321 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -07002322 }
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07002323 mDecrementVirtualButtonPressed = false;
2324 if (mDecrementVirtualButtonPressed) {
2325 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2326 }
2327 }
2328
2329 public void buttonPressDelayed(int button) {
2330 cancel();
2331 mMode = MODE_PRESS;
2332 mManagedButton = button;
2333 NumberPicker.this.postDelayed(this, ViewConfiguration.getTapTimeout());
2334 }
2335
2336 public void buttonTapped(int button) {
2337 cancel();
2338 mMode = MODE_TAPPED;
2339 mManagedButton = button;
2340 NumberPicker.this.post(this);
2341 }
2342
2343 @Override
2344 public void run() {
2345 switch (mMode) {
2346 case MODE_PRESS: {
2347 switch (mManagedButton) {
2348 case BUTTON_INCREMENT: {
2349 mIncrementVirtualButtonPressed = true;
2350 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
2351 } break;
2352 case BUTTON_DECREMENT: {
2353 mDecrementVirtualButtonPressed = true;
2354 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2355 }
2356 }
2357 } break;
2358 case MODE_TAPPED: {
2359 switch (mManagedButton) {
2360 case BUTTON_INCREMENT: {
2361 if (!mIncrementVirtualButtonPressed) {
2362 NumberPicker.this.postDelayed(this,
2363 ViewConfiguration.getPressedStateDuration());
2364 }
2365 mIncrementVirtualButtonPressed ^= true;
2366 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
2367 } break;
2368 case BUTTON_DECREMENT: {
2369 if (!mDecrementVirtualButtonPressed) {
2370 NumberPicker.this.postDelayed(this,
2371 ViewConfiguration.getPressedStateDuration());
2372 }
2373 mDecrementVirtualButtonPressed ^= true;
2374 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2375 }
2376 }
2377 } break;
2378 }
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -07002379 }
2380 }
2381
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002382 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002383 * Command for setting the input text selection.
2384 */
Alan Viverette57fe7012016-09-09 14:29:35 -04002385 private static class SetSelectionCommand implements Runnable {
2386 private final EditText mInputText;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002387
Alan Viverette57fe7012016-09-09 14:29:35 -04002388 private int mSelectionStart;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002389 private int mSelectionEnd;
2390
Alan Viverette57fe7012016-09-09 14:29:35 -04002391 /** Whether this runnable is currently posted. */
2392 private boolean mPosted;
2393
2394 public SetSelectionCommand(EditText inputText) {
2395 mInputText = inputText;
2396 }
2397
2398 public void post(int selectionStart, int selectionEnd) {
2399 mSelectionStart = selectionStart;
2400 mSelectionEnd = selectionEnd;
2401
2402 if (!mPosted) {
2403 mInputText.post(this);
2404 mPosted = true;
2405 }
2406 }
2407
2408 public void cancel() {
2409 if (mPosted) {
2410 mInputText.removeCallbacks(this);
2411 mPosted = false;
2412 }
2413 }
2414
2415 @Override
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002416 public void run() {
Alan Viverette57fe7012016-09-09 14:29:35 -04002417 mPosted = false;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002418 mInputText.setSelection(mSelectionStart, mSelectionEnd);
2419 }
2420 }
2421
2422 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07002423 * Command for changing the current value from a long press by one.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002424 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07002425 class ChangeCurrentByOneFromLongPressCommand implements Runnable {
2426 private boolean mIncrement;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002427
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002428 private void setStep(boolean increment) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07002429 mIncrement = increment;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002430 }
2431
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002432 @Override
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002433 public void run() {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002434 changeValueByOne(mIncrement);
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08002435 postDelayed(this, mLongPressUpdateInterval);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002436 }
2437 }
Svetoslav Ganova2b41b42012-02-27 15:53:32 -08002438
2439 /**
2440 * @hide
2441 */
2442 public static class CustomEditText extends EditText {
2443
2444 public CustomEditText(Context context, AttributeSet attrs) {
2445 super(context, attrs);
2446 }
2447
2448 @Override
2449 public void onEditorAction(int actionCode) {
2450 super.onEditorAction(actionCode);
2451 if (actionCode == EditorInfo.IME_ACTION_DONE) {
2452 clearFocus();
2453 }
2454 }
2455 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002456
2457 /**
2458 * Command for beginning soft input on long press.
2459 */
2460 class BeginSoftInputOnLongPressCommand implements Runnable {
2461
2462 @Override
2463 public void run() {
Svetoslav46a27ef2014-03-03 15:37:14 -08002464 performLongClick();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002465 }
2466 }
2467
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002468 /**
2469 * Class for managing virtual view tree rooted at this picker.
2470 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002471 class AccessibilityNodeProviderImpl extends AccessibilityNodeProvider {
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002472 private static final int UNDEFINED = Integer.MIN_VALUE;
2473
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002474 private static final int VIRTUAL_VIEW_ID_INCREMENT = 1;
2475
2476 private static final int VIRTUAL_VIEW_ID_INPUT = 2;
2477
2478 private static final int VIRTUAL_VIEW_ID_DECREMENT = 3;
2479
2480 private final Rect mTempRect = new Rect();
2481
2482 private final int[] mTempArray = new int[2];
2483
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002484 private int mAccessibilityFocusedView = UNDEFINED;
2485
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002486 @Override
2487 public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
2488 switch (virtualViewId) {
2489 case View.NO_ID:
2490 return createAccessibilityNodeInfoForNumberPicker( mScrollX, mScrollY,
2491 mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop));
2492 case VIRTUAL_VIEW_ID_DECREMENT:
2493 return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_DECREMENT,
2494 getVirtualDecrementButtonText(), mScrollX, mScrollY,
2495 mScrollX + (mRight - mLeft),
2496 mTopSelectionDividerTop + mSelectionDividerHeight);
2497 case VIRTUAL_VIEW_ID_INPUT:
Alan Viverette0e2d2812013-05-21 17:15:30 -07002498 return createAccessibiltyNodeInfoForInputText(mScrollX,
2499 mTopSelectionDividerTop + mSelectionDividerHeight,
2500 mScrollX + (mRight - mLeft),
2501 mBottomSelectionDividerBottom - mSelectionDividerHeight);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002502 case VIRTUAL_VIEW_ID_INCREMENT:
2503 return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_INCREMENT,
2504 getVirtualIncrementButtonText(), mScrollX,
2505 mBottomSelectionDividerBottom - mSelectionDividerHeight,
2506 mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop));
2507 }
2508 return super.createAccessibilityNodeInfo(virtualViewId);
2509 }
2510
2511 @Override
2512 public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String searched,
2513 int virtualViewId) {
2514 if (TextUtils.isEmpty(searched)) {
2515 return Collections.emptyList();
2516 }
2517 String searchedLowerCase = searched.toLowerCase();
2518 List<AccessibilityNodeInfo> result = new ArrayList<AccessibilityNodeInfo>();
2519 switch (virtualViewId) {
2520 case View.NO_ID: {
2521 findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
2522 VIRTUAL_VIEW_ID_DECREMENT, result);
2523 findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
2524 VIRTUAL_VIEW_ID_INPUT, result);
2525 findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
2526 VIRTUAL_VIEW_ID_INCREMENT, result);
2527 return result;
2528 }
2529 case VIRTUAL_VIEW_ID_DECREMENT:
2530 case VIRTUAL_VIEW_ID_INCREMENT:
2531 case VIRTUAL_VIEW_ID_INPUT: {
2532 findAccessibilityNodeInfosByTextInChild(searchedLowerCase, virtualViewId,
2533 result);
2534 return result;
2535 }
2536 }
2537 return super.findAccessibilityNodeInfosByText(searched, virtualViewId);
2538 }
2539
2540 @Override
Svetoslav Ganovaa780c12012-04-19 23:01:39 -07002541 public boolean performAction(int virtualViewId, int action, Bundle arguments) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002542 switch (virtualViewId) {
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002543 case View.NO_ID: {
2544 switch (action) {
2545 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
2546 if (mAccessibilityFocusedView != virtualViewId) {
2547 mAccessibilityFocusedView = virtualViewId;
2548 requestAccessibilityFocus();
2549 return true;
2550 }
2551 } return false;
2552 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
2553 if (mAccessibilityFocusedView == virtualViewId) {
2554 mAccessibilityFocusedView = UNDEFINED;
2555 clearAccessibilityFocus();
2556 return true;
2557 }
2558 return false;
2559 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07002560 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002561 if (NumberPicker.this.isEnabled()
2562 && (getWrapSelectorWheel() || getValue() < getMaxValue())) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07002563 changeValueByOne(true);
2564 return true;
2565 }
2566 } return false;
2567 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002568 if (NumberPicker.this.isEnabled()
2569 && (getWrapSelectorWheel() || getValue() > getMinValue())) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07002570 changeValueByOne(false);
2571 return true;
2572 }
2573 } return false;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002574 }
2575 } break;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002576 case VIRTUAL_VIEW_ID_INPUT: {
2577 switch (action) {
2578 case AccessibilityNodeInfo.ACTION_FOCUS: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002579 if (NumberPicker.this.isEnabled() && !mInputText.isFocused()) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002580 return mInputText.requestFocus();
2581 }
2582 } break;
2583 case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002584 if (NumberPicker.this.isEnabled() && mInputText.isFocused()) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002585 mInputText.clearFocus();
2586 return true;
2587 }
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002588 return false;
2589 }
2590 case AccessibilityNodeInfo.ACTION_CLICK: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002591 if (NumberPicker.this.isEnabled()) {
Svetoslav46a27ef2014-03-03 15:37:14 -08002592 performClick();
2593 return true;
2594 }
2595 return false;
2596 }
2597 case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
2598 if (NumberPicker.this.isEnabled()) {
2599 performLongClick();
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002600 return true;
2601 }
2602 return false;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002603 }
2604 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
2605 if (mAccessibilityFocusedView != virtualViewId) {
2606 mAccessibilityFocusedView = virtualViewId;
2607 sendAccessibilityEventForVirtualView(virtualViewId,
2608 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
2609 mInputText.invalidate();
2610 return true;
2611 }
2612 } return false;
2613 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
2614 if (mAccessibilityFocusedView == virtualViewId) {
2615 mAccessibilityFocusedView = UNDEFINED;
2616 sendAccessibilityEventForVirtualView(virtualViewId,
2617 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
2618 mInputText.invalidate();
2619 return true;
2620 }
2621 } return false;
2622 default: {
2623 return mInputText.performAccessibilityAction(action, arguments);
2624 }
2625 }
2626 } return false;
2627 case VIRTUAL_VIEW_ID_INCREMENT: {
2628 switch (action) {
2629 case AccessibilityNodeInfo.ACTION_CLICK: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002630 if (NumberPicker.this.isEnabled()) {
2631 NumberPicker.this.changeValueByOne(true);
2632 sendAccessibilityEventForVirtualView(virtualViewId,
2633 AccessibilityEvent.TYPE_VIEW_CLICKED);
2634 return true;
2635 }
2636 } return false;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002637 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
2638 if (mAccessibilityFocusedView != virtualViewId) {
2639 mAccessibilityFocusedView = virtualViewId;
2640 sendAccessibilityEventForVirtualView(virtualViewId,
2641 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
2642 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
2643 return true;
2644 }
2645 } return false;
2646 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
2647 if (mAccessibilityFocusedView == virtualViewId) {
2648 mAccessibilityFocusedView = UNDEFINED;
2649 sendAccessibilityEventForVirtualView(virtualViewId,
2650 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
2651 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
2652 return true;
2653 }
2654 } return false;
2655 }
2656 } return false;
2657 case VIRTUAL_VIEW_ID_DECREMENT: {
2658 switch (action) {
2659 case AccessibilityNodeInfo.ACTION_CLICK: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002660 if (NumberPicker.this.isEnabled()) {
2661 final boolean increment = (virtualViewId == VIRTUAL_VIEW_ID_INCREMENT);
2662 NumberPicker.this.changeValueByOne(increment);
2663 sendAccessibilityEventForVirtualView(virtualViewId,
2664 AccessibilityEvent.TYPE_VIEW_CLICKED);
2665 return true;
2666 }
2667 } return false;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002668 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
2669 if (mAccessibilityFocusedView != virtualViewId) {
2670 mAccessibilityFocusedView = virtualViewId;
2671 sendAccessibilityEventForVirtualView(virtualViewId,
2672 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
2673 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2674 return true;
2675 }
2676 } return false;
2677 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
2678 if (mAccessibilityFocusedView == virtualViewId) {
2679 mAccessibilityFocusedView = UNDEFINED;
2680 sendAccessibilityEventForVirtualView(virtualViewId,
2681 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
2682 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2683 return true;
2684 }
2685 } return false;
2686 }
2687 } return false;
2688 }
2689 return super.performAction(virtualViewId, action, arguments);
2690 }
2691
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002692 public void sendAccessibilityEventForVirtualView(int virtualViewId, int eventType) {
2693 switch (virtualViewId) {
2694 case VIRTUAL_VIEW_ID_DECREMENT: {
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002695 if (hasVirtualDecrementButton()) {
2696 sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
2697 getVirtualDecrementButtonText());
2698 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002699 } break;
2700 case VIRTUAL_VIEW_ID_INPUT: {
2701 sendAccessibilityEventForVirtualText(eventType);
2702 } break;
2703 case VIRTUAL_VIEW_ID_INCREMENT: {
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002704 if (hasVirtualIncrementButton()) {
2705 sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
2706 getVirtualIncrementButtonText());
2707 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002708 } break;
2709 }
2710 }
2711
2712 private void sendAccessibilityEventForVirtualText(int eventType) {
Eugene Suslad4128ec2017-12-04 19:48:41 +00002713 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
Svetoslav Ganova9092762012-09-06 19:57:00 -07002714 AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
2715 mInputText.onInitializeAccessibilityEvent(event);
2716 mInputText.onPopulateAccessibilityEvent(event);
2717 event.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
2718 requestSendAccessibilityEvent(NumberPicker.this, event);
2719 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002720 }
2721
2722 private void sendAccessibilityEventForVirtualButton(int virtualViewId, int eventType,
2723 String text) {
Eugene Suslad4128ec2017-12-04 19:48:41 +00002724 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
Svetoslav Ganova9092762012-09-06 19:57:00 -07002725 AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
2726 event.setClassName(Button.class.getName());
2727 event.setPackageName(mContext.getPackageName());
2728 event.getText().add(text);
2729 event.setEnabled(NumberPicker.this.isEnabled());
2730 event.setSource(NumberPicker.this, virtualViewId);
2731 requestSendAccessibilityEvent(NumberPicker.this, event);
2732 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002733 }
2734
2735 private void findAccessibilityNodeInfosByTextInChild(String searchedLowerCase,
2736 int virtualViewId, List<AccessibilityNodeInfo> outResult) {
2737 switch (virtualViewId) {
2738 case VIRTUAL_VIEW_ID_DECREMENT: {
2739 String text = getVirtualDecrementButtonText();
2740 if (!TextUtils.isEmpty(text)
2741 && text.toString().toLowerCase().contains(searchedLowerCase)) {
2742 outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT));
2743 }
2744 } return;
2745 case VIRTUAL_VIEW_ID_INPUT: {
2746 CharSequence text = mInputText.getText();
2747 if (!TextUtils.isEmpty(text) &&
2748 text.toString().toLowerCase().contains(searchedLowerCase)) {
2749 outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT));
2750 return;
2751 }
2752 CharSequence contentDesc = mInputText.getText();
2753 if (!TextUtils.isEmpty(contentDesc) &&
2754 contentDesc.toString().toLowerCase().contains(searchedLowerCase)) {
2755 outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT));
2756 return;
2757 }
2758 } break;
2759 case VIRTUAL_VIEW_ID_INCREMENT: {
2760 String text = getVirtualIncrementButtonText();
2761 if (!TextUtils.isEmpty(text)
2762 && text.toString().toLowerCase().contains(searchedLowerCase)) {
2763 outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT));
2764 }
2765 } return;
2766 }
2767 }
2768
Alan Viverette0e2d2812013-05-21 17:15:30 -07002769 private AccessibilityNodeInfo createAccessibiltyNodeInfoForInputText(
2770 int left, int top, int right, int bottom) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002771 AccessibilityNodeInfo info = mInputText.createAccessibilityNodeInfo();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002772 info.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002773 if (mAccessibilityFocusedView != VIRTUAL_VIEW_ID_INPUT) {
2774 info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
2775 }
2776 if (mAccessibilityFocusedView == VIRTUAL_VIEW_ID_INPUT) {
2777 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
2778 }
Alan Viverette0e2d2812013-05-21 17:15:30 -07002779 Rect boundsInParent = mTempRect;
2780 boundsInParent.set(left, top, right, bottom);
2781 info.setVisibleToUser(isVisibleToUser(boundsInParent));
2782 info.setBoundsInParent(boundsInParent);
2783 Rect boundsInScreen = boundsInParent;
2784 int[] locationOnScreen = mTempArray;
2785 getLocationOnScreen(locationOnScreen);
2786 boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
2787 info.setBoundsInScreen(boundsInScreen);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002788 return info;
2789 }
2790
2791 private AccessibilityNodeInfo createAccessibilityNodeInfoForVirtualButton(int virtualViewId,
2792 String text, int left, int top, int right, int bottom) {
2793 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
2794 info.setClassName(Button.class.getName());
2795 info.setPackageName(mContext.getPackageName());
2796 info.setSource(NumberPicker.this, virtualViewId);
2797 info.setParent(NumberPicker.this);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002798 info.setText(text);
2799 info.setClickable(true);
2800 info.setLongClickable(true);
2801 info.setEnabled(NumberPicker.this.isEnabled());
2802 Rect boundsInParent = mTempRect;
2803 boundsInParent.set(left, top, right, bottom);
Guang Zhu0d607fb2012-05-11 19:34:56 -07002804 info.setVisibleToUser(isVisibleToUser(boundsInParent));
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002805 info.setBoundsInParent(boundsInParent);
2806 Rect boundsInScreen = boundsInParent;
2807 int[] locationOnScreen = mTempArray;
2808 getLocationOnScreen(locationOnScreen);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002809 boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
2810 info.setBoundsInScreen(boundsInScreen);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002811
2812 if (mAccessibilityFocusedView != virtualViewId) {
2813 info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
2814 }
2815 if (mAccessibilityFocusedView == virtualViewId) {
2816 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
2817 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002818 if (NumberPicker.this.isEnabled()) {
2819 info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
2820 }
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002821
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002822 return info;
2823 }
2824
2825 private AccessibilityNodeInfo createAccessibilityNodeInfoForNumberPicker(int left, int top,
2826 int right, int bottom) {
2827 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
Guang Zhu0d607fb2012-05-11 19:34:56 -07002828 info.setClassName(NumberPicker.class.getName());
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002829 info.setPackageName(mContext.getPackageName());
2830 info.setSource(NumberPicker.this);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002831
2832 if (hasVirtualDecrementButton()) {
2833 info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_DECREMENT);
2834 }
Guang Zhu0d607fb2012-05-11 19:34:56 -07002835 info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002836 if (hasVirtualIncrementButton()) {
2837 info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INCREMENT);
2838 }
2839
Svetoslav Ganov4528b4e2012-05-15 18:24:10 -07002840 info.setParent((View) getParentForAccessibility());
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002841 info.setEnabled(NumberPicker.this.isEnabled());
2842 info.setScrollable(true);
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002843
2844 final float applicationScale =
2845 getContext().getResources().getCompatibilityInfo().applicationScale;
2846
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002847 Rect boundsInParent = mTempRect;
2848 boundsInParent.set(left, top, right, bottom);
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002849 boundsInParent.scale(applicationScale);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002850 info.setBoundsInParent(boundsInParent);
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002851
Guang Zhu0d607fb2012-05-11 19:34:56 -07002852 info.setVisibleToUser(isVisibleToUser());
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002853
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002854 Rect boundsInScreen = boundsInParent;
2855 int[] locationOnScreen = mTempArray;
2856 getLocationOnScreen(locationOnScreen);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002857 boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002858 boundsInScreen.scale(applicationScale);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002859 info.setBoundsInScreen(boundsInScreen);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002860
2861 if (mAccessibilityFocusedView != View.NO_ID) {
2862 info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
2863 }
2864 if (mAccessibilityFocusedView == View.NO_ID) {
2865 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
2866 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002867 if (NumberPicker.this.isEnabled()) {
2868 if (getWrapSelectorWheel() || getValue() < getMaxValue()) {
2869 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
2870 }
2871 if (getWrapSelectorWheel() || getValue() > getMinValue()) {
2872 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
2873 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07002874 }
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002875
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002876 return info;
2877 }
2878
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002879 private boolean hasVirtualDecrementButton() {
2880 return getWrapSelectorWheel() || getValue() > getMinValue();
2881 }
2882
2883 private boolean hasVirtualIncrementButton() {
2884 return getWrapSelectorWheel() || getValue() < getMaxValue();
2885 }
2886
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002887 private String getVirtualDecrementButtonText() {
2888 int value = mValue - 1;
2889 if (mWrapSelectorWheel) {
2890 value = getWrappedSelectorIndex(value);
2891 }
2892 if (value >= mMinValue) {
2893 return (mDisplayedValues == null) ? formatNumber(value)
2894 : mDisplayedValues[value - mMinValue];
2895 }
2896 return null;
2897 }
2898
2899 private String getVirtualIncrementButtonText() {
2900 int value = mValue + 1;
2901 if (mWrapSelectorWheel) {
2902 value = getWrappedSelectorIndex(value);
2903 }
2904 if (value <= mMaxValue) {
2905 return (mDisplayedValues == null) ? formatNumber(value)
2906 : mDisplayedValues[value - mMinValue];
2907 }
2908 return null;
2909 }
2910 }
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07002911
2912 static private String formatNumberWithLocale(int value) {
2913 return String.format(Locale.getDefault(), "%d", value);
2914 }
Paul Westbrook7762d932009-12-11 14:13:48 -08002915}