blob: 44c49874b0a04a5b550e17bc604141345127e17b [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
Tor Norbyed9273d62013-05-30 15:59:53 -070019import android.annotation.IntDef;
Paul Westbrook68f2f542010-01-13 12:13:57 -080020import android.annotation.Widget;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.Context;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080022import android.content.res.ColorStateList;
23import android.content.res.TypedArray;
24import android.graphics.Canvas;
25import android.graphics.Color;
26import android.graphics.Paint;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080027import android.graphics.Paint.Align;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -070028import android.graphics.Rect;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -080029import android.graphics.drawable.Drawable;
Svetoslav Ganovaa780c12012-04-19 23:01:39 -070030import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.text.InputFilter;
32import android.text.InputType;
33import android.text.Spanned;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080034import android.text.TextUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.text.method.NumberKeyListener;
36import android.util.AttributeSet;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080037import android.util.SparseArray;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -080038import android.util.TypedValue;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080039import android.view.KeyEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import android.view.LayoutInflater;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -070041import android.view.LayoutInflater.Filter;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080042import android.view.MotionEvent;
43import android.view.VelocityTracker;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import android.view.View;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080045import android.view.ViewConfiguration;
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -070046import android.view.accessibility.AccessibilityEvent;
47import android.view.accessibility.AccessibilityManager;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080048import android.view.accessibility.AccessibilityNodeInfo;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -070049import android.view.accessibility.AccessibilityNodeProvider;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -080050import android.view.animation.DecelerateInterpolator;
Svetoslav Ganova2b41b42012-02-27 15:53:32 -080051import android.view.inputmethod.EditorInfo;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080052import android.view.inputmethod.InputMethodManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -070054import com.android.internal.R;
Fabrice Di Megliod88e3052012-09-21 12:15:23 -070055import libcore.icu.LocaleData;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -070056
Tor Norbyed9273d62013-05-30 15:59:53 -070057import java.lang.annotation.Retention;
58import java.lang.annotation.RetentionPolicy;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -070059import java.util.ArrayList;
60import java.util.Collections;
61import java.util.List;
Fabrice Di Megliod88e3052012-09-21 12:15:23 -070062import java.util.Locale;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -070063
Paul Westbrook68f2f542010-01-13 12:13:57 -080064/**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -080065 * A widget that enables the user to select a number form a predefined range.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -070066 * There are two flavors of this widget and which one is presented to the user
67 * depends on the current theme.
68 * <ul>
69 * <li>
70 * If the current theme is derived from {@link android.R.style#Theme} the widget
71 * presents the current value as an editable input field with an increment button
72 * above and a decrement button below. Long pressing the buttons allows for a quick
73 * change of the current value. Tapping on the input field allows to type in
74 * a desired value.
75 * </li>
76 * <li>
77 * If the current theme is derived from {@link android.R.style#Theme_Holo} or
78 * {@link android.R.style#Theme_Holo_Light} the widget presents the current
79 * value as an editable input field with a lesser value above and a greater
80 * value below. Tapping on the lesser or greater value selects it by animating
81 * the number axis up or down to make the chosen value current. Flinging up
82 * or down allows for multiple increments or decrements of the current value.
83 * Long pressing on the lesser and greater values also allows for a quick change
84 * of the current value. Tapping on the current value allows to type in a
85 * desired value.
86 * </li>
87 * </ul>
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -080088 * <p>
89 * For an example of using this widget, see {@link android.widget.TimePicker}.
90 * </p>
Paul Westbrook68f2f542010-01-13 12:13:57 -080091 */
92@Widget
93public class NumberPicker extends LinearLayout {
Tom Taylorfdf6db62009-09-09 11:37:58 -070094
Paul Westbrook68f2f542010-01-13 12:13:57 -080095 /**
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -080096 * The number of items show in the selector wheel.
97 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -070098 private static final int SELECTOR_WHEEL_ITEM_COUNT = 3;
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -080099
100 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800101 * The default update interval during long press.
102 */
103 private static final long DEFAULT_LONG_PRESS_UPDATE_INTERVAL = 300;
104
105 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800106 * The index of the middle selector item.
Paul Westbrook68f2f542010-01-13 12:13:57 -0800107 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700108 private static final int SELECTOR_MIDDLE_ITEM_INDEX = SELECTOR_WHEEL_ITEM_COUNT / 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800109
Paul Westbrook68f2f542010-01-13 12:13:57 -0800110 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800111 * The coefficient by which to adjust (divide) the max fling velocity.
Paul Westbrook68f2f542010-01-13 12:13:57 -0800112 */
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800113 private static final int SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT = 8;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800115 /**
116 * The the duration for adjusting the selector wheel.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 */
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800118 private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800;
Tom Taylorfdf6db62009-09-09 11:37:58 -0700119
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800120 /**
Svetoslav Ganov42138042012-03-20 11:51:39 -0700121 * The duration of scrolling while snapping to a given position.
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700122 */
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -0700123 private static final int SNAP_SCROLL_DURATION = 300;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700124
125 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800126 * The strength of fading in the top and bottom while drawing the selector.
127 */
128 private static final float TOP_AND_BOTTOM_FADING_EDGE_STRENGTH = 0.9f;
129
130 /**
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800131 * The default unscaled height of the selection divider.
132 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700133 private static final int UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT = 2;
134
135 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700136 * The default unscaled distance between the selection dividers.
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700137 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700138 private static final int UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE = 48;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700139
140 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700141 * The resource id for the default layout.
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700142 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700143 private static final int DEFAULT_LAYOUT_RESOURCE_ID = R.layout.number_picker;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800144
145 /**
Svetoslav Ganov9f086d82011-11-29 18:27:23 -0800146 * Constant for unspecified size.
147 */
148 private static final int SIZE_UNSPECIFIED = -1;
149
150 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800151 * Use a custom NumberPicker formatting callback to use two-digit minutes
152 * strings like "01". Keeping a static formatter etc. is the most efficient
153 * way to do this; it avoids creating temporary objects on every call to
154 * format().
155 */
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700156 private static class TwoDigitFormatter implements NumberPicker.Formatter {
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800157 final StringBuilder mBuilder = new StringBuilder();
158
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700159 char mZeroDigit;
160 java.util.Formatter mFmt;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800161
162 final Object[] mArgs = new Object[1];
163
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700164 TwoDigitFormatter() {
165 final Locale locale = Locale.getDefault();
166 init(locale);
167 }
168
169 private void init(Locale locale) {
170 mFmt = createFormatter(locale);
171 mZeroDigit = getZeroDigit(locale);
172 }
173
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800174 public String format(int value) {
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700175 final Locale currentLocale = Locale.getDefault();
176 if (mZeroDigit != getZeroDigit(currentLocale)) {
177 init(currentLocale);
178 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800179 mArgs[0] = value;
180 mBuilder.delete(0, mBuilder.length());
181 mFmt.format("%02d", mArgs);
182 return mFmt.toString();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183 }
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700184
185 private static char getZeroDigit(Locale locale) {
186 return LocaleData.get(locale).zeroDigit;
187 }
188
189 private java.util.Formatter createFormatter(Locale locale) {
190 return new java.util.Formatter(mBuilder, locale);
191 }
192 }
193
194 private static final TwoDigitFormatter sTwoDigitFormatter = new TwoDigitFormatter();
195
Fabrice Di Meglioa65fe882012-09-23 11:54:51 -0700196 /**
197 * @hide
198 */
199 public static final Formatter getTwoDigitFormatter() {
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700200 return sTwoDigitFormatter;
201 }
202
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800203 /**
204 * The increment button.
205 */
206 private final ImageButton mIncrementButton;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800207
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800208 /**
209 * The decrement button.
210 */
211 private final ImageButton mDecrementButton;
212
213 /**
214 * The text for showing the current value.
215 */
216 private final EditText mInputText;
217
218 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700219 * The distance between the two selection dividers.
220 */
221 private final int mSelectionDividersDistance;
222
223 /**
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700224 * The min height of this widget.
225 */
226 private final int mMinHeight;
227
228 /**
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700229 * The max height of this widget.
230 */
231 private final int mMaxHeight;
232
233 /**
234 * The max width of this widget.
235 */
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700236 private final int mMinWidth;
237
238 /**
239 * The max width of this widget.
240 */
241 private int mMaxWidth;
242
243 /**
244 * Flag whether to compute the max width.
245 */
246 private final boolean mComputeMaxWidth;
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700247
248 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800249 * The height of the text.
250 */
251 private final int mTextSize;
252
253 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700254 * The height of the gap between text elements if the selector wheel.
255 */
256 private int mSelectorTextGapHeight;
257
258 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800259 * The values to be displayed instead the indices.
260 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800261 private String[] mDisplayedValues;
Paul Westbrook68f2f542010-01-13 12:13:57 -0800262
263 /**
264 * Lower value of the range of numbers allowed for the NumberPicker
265 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800266 private int mMinValue;
Paul Westbrook68f2f542010-01-13 12:13:57 -0800267
268 /**
269 * Upper value of the range of numbers allowed for the NumberPicker
270 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800271 private int mMaxValue;
Paul Westbrook68f2f542010-01-13 12:13:57 -0800272
273 /**
274 * Current value of this NumberPicker
275 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800276 private int mValue;
Paul Westbrook68f2f542010-01-13 12:13:57 -0800277
278 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800279 * Listener to be notified upon current value change.
Paul Westbrook68f2f542010-01-13 12:13:57 -0800280 */
Svetoslav Ganovcedc4462011-01-19 19:25:46 -0800281 private OnValueChangeListener mOnValueChangeListener;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800282
283 /**
284 * Listener to be notified upon scroll state change.
285 */
286 private OnScrollListener mOnScrollListener;
Tom Taylorfdf6db62009-09-09 11:37:58 -0700287
Paul Westbrook68f2f542010-01-13 12:13:57 -0800288 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800289 * Formatter for for displaying the current value.
Paul Westbrook68f2f542010-01-13 12:13:57 -0800290 */
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800291 private Formatter mFormatter;
292
293 /**
294 * The speed for updating the value form long press.
295 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800296 private long mLongPressUpdateInterval = DEFAULT_LONG_PRESS_UPDATE_INTERVAL;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800297
298 /**
299 * Cache for the string representation of selector indices.
300 */
301 private final SparseArray<String> mSelectorIndexToStringCache = new SparseArray<String>();
302
303 /**
304 * The selector indices whose value are show by the selector.
305 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700306 private final int[] mSelectorIndices = new int[SELECTOR_WHEEL_ITEM_COUNT];
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800307
308 /**
309 * The {@link Paint} for drawing the selector.
310 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700311 private final Paint mSelectorWheelPaint;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800312
313 /**
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700314 * The {@link Drawable} for pressed virtual (increment/decrement) buttons.
315 */
316 private final Drawable mVirtualButtonPressedDrawable;
317
318 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800319 * The height of a selector element (text + gap).
320 */
321 private int mSelectorElementHeight;
322
323 /**
324 * The initial offset of the scroll selector.
325 */
326 private int mInitialScrollOffset = Integer.MIN_VALUE;
327
328 /**
329 * The current offset of the scroll selector.
330 */
331 private int mCurrentScrollOffset;
332
333 /**
334 * The {@link Scroller} responsible for flinging the selector.
335 */
336 private final Scroller mFlingScroller;
337
338 /**
339 * The {@link Scroller} responsible for adjusting the selector.
340 */
341 private final Scroller mAdjustScroller;
342
343 /**
344 * The previous Y coordinate while scrolling the selector.
345 */
346 private int mPreviousScrollerY;
347
348 /**
349 * Handle to the reusable command for setting the input text selection.
350 */
351 private SetSelectionCommand mSetSelectionCommand;
352
353 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700354 * Handle to the reusable command for changing the current value from long
355 * press by one.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800356 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700357 private ChangeCurrentByOneFromLongPressCommand mChangeCurrentByOneFromLongPressCommand;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800358
359 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700360 * Command for beginning an edit of the current value via IME on long press.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800361 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700362 private BeginSoftInputOnLongPressCommand mBeginSoftInputOnLongPressCommand;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700363
364 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800365 * The Y position of the last down event.
366 */
367 private float mLastDownEventY;
368
369 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700370 * The time of the last down event.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800371 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700372 private long mLastDownEventTime;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800373
374 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700375 * The Y position of the last down or move event.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800376 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700377 private float mLastDownOrMoveEventY;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800378
379 /**
380 * Determines speed during touch scrolling.
381 */
382 private VelocityTracker mVelocityTracker;
383
384 /**
385 * @see ViewConfiguration#getScaledTouchSlop()
386 */
387 private int mTouchSlop;
388
389 /**
390 * @see ViewConfiguration#getScaledMinimumFlingVelocity()
391 */
392 private int mMinimumFlingVelocity;
393
394 /**
395 * @see ViewConfiguration#getScaledMaximumFlingVelocity()
396 */
397 private int mMaximumFlingVelocity;
398
399 /**
400 * Flag whether the selector should wrap around.
401 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800402 private boolean mWrapSelectorWheel;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800403
404 /**
405 * The back ground color used to optimize scroller fading.
406 */
407 private final int mSolidColor;
408
409 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700410 * Flag whether this widget has a selector wheel.
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800411 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700412 private final boolean mHasSelectorWheel;
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800413
414 /**
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800415 * Divider for showing item to be selected while scrolling
416 */
417 private final Drawable mSelectionDivider;
418
419 /**
420 * The height of the selection divider.
421 */
422 private final int mSelectionDividerHeight;
423
424 /**
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800425 * The current scroll state of the number picker.
426 */
427 private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
428
429 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700430 * Flag whether to ignore move events - we ignore such when we show in IME
431 * to prevent the content from scrolling.
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800432 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700433 private boolean mIngonreMoveEvents;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800434
435 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700436 * Flag whether to show soft input on tap.
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700437 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700438 private boolean mShowSoftInputOnTap;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700439
440 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700441 * The top of the top selection divider.
Svetoslav Ganova2b41b42012-02-27 15:53:32 -0800442 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700443 private int mTopSelectionDividerTop;
444
445 /**
446 * The bottom of the bottom selection divider.
447 */
448 private int mBottomSelectionDividerBottom;
449
450 /**
451 * The virtual id of the last hovered child.
452 */
453 private int mLastHoveredChildVirtualViewId;
454
455 /**
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700456 * Whether the increment virtual button is pressed.
457 */
458 private boolean mIncrementVirtualButtonPressed;
459
460 /**
461 * Whether the decrement virtual button is pressed.
462 */
463 private boolean mDecrementVirtualButtonPressed;
464
465 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700466 * Provider to report to clients the semantic structure of this widget.
467 */
468 private AccessibilityNodeProviderImpl mAccessibilityNodeProvider;
Svetoslav Ganova2b41b42012-02-27 15:53:32 -0800469
470 /**
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700471 * Helper class for managing pressed state of the virtual buttons.
472 */
473 private final PressedStateHelper mPressedStateHelper;
474
475 /**
Svetoslav Ganov5dc21d92012-10-07 18:54:42 -0700476 * The keycode of the last handled DPAD down event.
477 */
478 private int mLastHandledDownDpadKeyCode = -1;
479
480 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800481 * Interface to listen for changes of the current value.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800482 */
Svetoslav Ganovcedc4462011-01-19 19:25:46 -0800483 public interface OnValueChangeListener {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800484
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800485 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800486 * Called upon a change of the current value.
487 *
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800488 * @param picker The NumberPicker associated with this listener.
489 * @param oldVal The previous value.
490 * @param newVal The new value.
491 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800492 void onValueChange(NumberPicker picker, int oldVal, int newVal);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800493 }
494
495 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800496 * Interface to listen for the picker scroll state.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800497 */
498 public interface OnScrollListener {
Tor Norbyed9273d62013-05-30 15:59:53 -0700499 /** @hide */
500 @IntDef({SCROLL_STATE_IDLE, SCROLL_STATE_TOUCH_SCROLL, SCROLL_STATE_FLING})
501 @Retention(RetentionPolicy.SOURCE)
502 public @interface ScrollState {}
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800503
504 /**
505 * The view is not scrolling.
506 */
507 public static int SCROLL_STATE_IDLE = 0;
508
509 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700510 * The user is scrolling using touch, and his finger is still on the screen.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800511 */
512 public static int SCROLL_STATE_TOUCH_SCROLL = 1;
513
514 /**
515 * The user had previously been scrolling using touch and performed a fling.
516 */
517 public static int SCROLL_STATE_FLING = 2;
518
519 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800520 * Callback invoked while the number picker scroll state has changed.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800521 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800522 * @param view The view whose scroll state is being reported.
523 * @param scrollState The current scroll state. One of
524 * {@link #SCROLL_STATE_IDLE},
525 * {@link #SCROLL_STATE_TOUCH_SCROLL} or
526 * {@link #SCROLL_STATE_IDLE}.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800527 */
Tor Norbyed9273d62013-05-30 15:59:53 -0700528 public void onScrollStateChange(NumberPicker view, @ScrollState int scrollState);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800529 }
530
531 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800532 * Interface used to format current value into a string for presentation.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800533 */
534 public interface Formatter {
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800535
536 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800537 * Formats a string representation of the current value.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800538 *
539 * @param value The currently selected value.
540 * @return A formatted string representation.
541 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800542 public String format(int value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800543 }
Tom Taylorfdf6db62009-09-09 11:37:58 -0700544
Paul Westbrook68f2f542010-01-13 12:13:57 -0800545 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800546 * Create a new number picker.
547 *
548 * @param context The application environment.
549 */
550 public NumberPicker(Context context) {
551 this(context, null);
552 }
553
554 /**
555 * Create a new number picker.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800556 *
557 * @param context The application environment.
558 * @param attrs A collection of attributes.
Paul Westbrook68f2f542010-01-13 12:13:57 -0800559 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800560 public NumberPicker(Context context, AttributeSet attrs) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800561 this(context, attrs, R.attr.numberPickerStyle);
562 }
563
564 /**
565 * Create a new number picker
566 *
567 * @param context the application environment.
568 * @param attrs a collection of attributes.
Alan Viverette617feb92013-09-09 18:09:13 -0700569 * @param defStyleAttr An attribute in the current theme that contains a
570 * reference to a style resource that supplies default values for
571 * the view. Can be 0 to not look for defaults.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800572 */
Alan Viverette617feb92013-09-09 18:09:13 -0700573 public NumberPicker(Context context, AttributeSet attrs, int defStyleAttr) {
574 this(context, attrs, defStyleAttr, 0);
575 }
576
577 /**
578 * Create a new number picker
579 *
580 * @param context the application environment.
581 * @param attrs a collection of attributes.
582 * @param defStyleAttr An attribute in the current theme that contains a
583 * reference to a style resource that supplies default values for
584 * the view. Can be 0 to not look for defaults.
585 * @param defStyleRes A resource identifier of a style resource that
586 * supplies default values for the view, used only if
587 * defStyleAttr is 0 or can not be found in the theme. Can be 0
588 * to not look for defaults.
589 */
590 public NumberPicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
591 super(context, attrs, defStyleAttr, defStyleRes);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800592
593 // process style attributes
Alan Viverette617feb92013-09-09 18:09:13 -0700594 final TypedArray attributesArray = context.obtainStyledAttributes(
595 attrs, R.styleable.NumberPicker, defStyleAttr, defStyleRes);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700596 final int layoutResId = attributesArray.getResourceId(
597 R.styleable.NumberPicker_internalLayout, DEFAULT_LAYOUT_RESOURCE_ID);
598
599 mHasSelectorWheel = (layoutResId != DEFAULT_LAYOUT_RESOURCE_ID);
600
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800601 mSolidColor = attributesArray.getColor(R.styleable.NumberPicker_solidColor, 0);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700602
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800603 mSelectionDivider = attributesArray.getDrawable(R.styleable.NumberPicker_selectionDivider);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700604
605 final int defSelectionDividerHeight = (int) TypedValue.applyDimension(
606 TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT,
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800607 getResources().getDisplayMetrics());
608 mSelectionDividerHeight = attributesArray.getDimensionPixelSize(
609 R.styleable.NumberPicker_selectionDividerHeight, defSelectionDividerHeight);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700610
611 final int defSelectionDividerDistance = (int) TypedValue.applyDimension(
612 TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE,
613 getResources().getDisplayMetrics());
614 mSelectionDividersDistance = attributesArray.getDimensionPixelSize(
615 R.styleable.NumberPicker_selectionDividersDistance, defSelectionDividerDistance);
616
Svetoslav Ganove8331bd2012-03-02 15:15:35 -0800617 mMinHeight = attributesArray.getDimensionPixelSize(
618 R.styleable.NumberPicker_internalMinHeight, SIZE_UNSPECIFIED);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700619
Svetoslav Ganove8331bd2012-03-02 15:15:35 -0800620 mMaxHeight = attributesArray.getDimensionPixelSize(
621 R.styleable.NumberPicker_internalMaxHeight, SIZE_UNSPECIFIED);
Svetoslav Ganov9f086d82011-11-29 18:27:23 -0800622 if (mMinHeight != SIZE_UNSPECIFIED && mMaxHeight != SIZE_UNSPECIFIED
623 && mMinHeight > mMaxHeight) {
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700624 throw new IllegalArgumentException("minHeight > maxHeight");
625 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700626
627 mMinWidth = attributesArray.getDimensionPixelSize(
628 R.styleable.NumberPicker_internalMinWidth, SIZE_UNSPECIFIED);
629
630 mMaxWidth = attributesArray.getDimensionPixelSize(
631 R.styleable.NumberPicker_internalMaxWidth, SIZE_UNSPECIFIED);
Svetoslav Ganov9f086d82011-11-29 18:27:23 -0800632 if (mMinWidth != SIZE_UNSPECIFIED && mMaxWidth != SIZE_UNSPECIFIED
633 && mMinWidth > mMaxWidth) {
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700634 throw new IllegalArgumentException("minWidth > maxWidth");
635 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800636
Svetoslav Ganovf7c83bc2012-04-12 16:00:13 -0700637 mComputeMaxWidth = (mMaxWidth == SIZE_UNSPECIFIED);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700638
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700639 mVirtualButtonPressedDrawable = attributesArray.getDrawable(
640 R.styleable.NumberPicker_virtualButtonPressedDrawable);
641
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700642 attributesArray.recycle();
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800643
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700644 mPressedStateHelper = new PressedStateHelper();
645
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800646 // By default Linearlayout that we extend is not drawn. This is
647 // its draw() method is not called but dispatchDraw() is called
648 // directly (see ViewGroup.drawChild()). However, this class uses
649 // the fading edge effect implemented by View and we need our
650 // draw() method to be called. Therefore, we declare we will draw.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700651 setWillNotDraw(!mHasSelectorWheel);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800652
653 LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
654 Context.LAYOUT_INFLATER_SERVICE);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700655 inflater.inflate(layoutResId, this, true);
Paul Westbrook68f2f542010-01-13 12:13:57 -0800656
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800657 OnClickListener onClickListener = new OnClickListener() {
Paul Westbrook68f2f542010-01-13 12:13:57 -0800658 public void onClick(View v) {
Svetoslav Ganovb52d9722011-11-07 14:53:34 -0800659 hideSoftInput();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800660 mInputText.clearFocus();
661 if (v.getId() == R.id.increment) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700662 changeValueByOne(true);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800663 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700664 changeValueByOne(false);
Paul Westbrook68f2f542010-01-13 12:13:57 -0800665 }
666 }
667 };
668
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800669 OnLongClickListener onLongClickListener = new OnLongClickListener() {
Paul Westbrook68f2f542010-01-13 12:13:57 -0800670 public boolean onLongClick(View v) {
Svetoslav Ganovb52d9722011-11-07 14:53:34 -0800671 hideSoftInput();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800672 mInputText.clearFocus();
673 if (v.getId() == R.id.increment) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700674 postChangeCurrentByOneFromLongPress(true, 0);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800675 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700676 postChangeCurrentByOneFromLongPress(false, 0);
Paul Westbrook68f2f542010-01-13 12:13:57 -0800677 }
678 return true;
679 }
680 };
681
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800682 // increment button
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700683 if (!mHasSelectorWheel) {
684 mIncrementButton = (ImageButton) findViewById(R.id.increment);
685 mIncrementButton.setOnClickListener(onClickListener);
686 mIncrementButton.setOnLongClickListener(onLongClickListener);
687 } else {
688 mIncrementButton = null;
689 }
Paul Westbrook68f2f542010-01-13 12:13:57 -0800690
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800691 // decrement button
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700692 if (!mHasSelectorWheel) {
693 mDecrementButton = (ImageButton) findViewById(R.id.decrement);
694 mDecrementButton.setOnClickListener(onClickListener);
695 mDecrementButton.setOnLongClickListener(onLongClickListener);
696 } else {
697 mDecrementButton = null;
698 }
Tom Taylorfdf6db62009-09-09 11:37:58 -0700699
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800700 // input text
Svetoslav Ganov012dd5a2011-01-11 16:54:01 -0800701 mInputText = (EditText) findViewById(R.id.numberpicker_input);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800702 mInputText.setOnFocusChangeListener(new OnFocusChangeListener() {
703 public void onFocusChange(View v, boolean hasFocus) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700704 if (hasFocus) {
705 mInputText.selectAll();
706 } else {
707 mInputText.setSelection(0, 0);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800708 validateInputTextView(v);
709 }
710 }
711 });
712 mInputText.setFilters(new InputFilter[] {
713 new InputTextFilter()
714 });
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800715
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800716 mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
Svetoslav Ganova2b41b42012-02-27 15:53:32 -0800717 mInputText.setImeOptions(EditorInfo.IME_ACTION_DONE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800718
719 // initialize constants
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800720 ViewConfiguration configuration = ViewConfiguration.get(context);
721 mTouchSlop = configuration.getScaledTouchSlop();
722 mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
723 mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity()
724 / SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT;
725 mTextSize = (int) mInputText.getTextSize();
726
727 // create the selector wheel paint
728 Paint paint = new Paint();
729 paint.setAntiAlias(true);
730 paint.setTextAlign(Align.CENTER);
731 paint.setTextSize(mTextSize);
732 paint.setTypeface(mInputText.getTypeface());
733 ColorStateList colors = mInputText.getTextColors();
734 int color = colors.getColorForState(ENABLED_STATE_SET, Color.WHITE);
735 paint.setColor(color);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700736 mSelectorWheelPaint = paint;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800737
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800738 // create the fling and adjust scrollers
Svetoslav Ganovbf805622010-12-17 16:00:18 -0800739 mFlingScroller = new Scroller(getContext(), null, true);
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800740 mAdjustScroller = new Scroller(getContext(), new DecelerateInterpolator(2.5f));
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800741
742 updateInputTextView();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700743
744 // If not explicitly specified this view is important for accessibility.
745 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
746 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
747 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800748 }
749
750 @Override
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800751 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700752 if (!mHasSelectorWheel) {
753 super.onLayout(changed, left, top, right, bottom);
754 return;
755 }
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700756 final int msrdWdth = getMeasuredWidth();
757 final int msrdHght = getMeasuredHeight();
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700758
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700759 // Input text centered horizontally.
760 final int inptTxtMsrdWdth = mInputText.getMeasuredWidth();
761 final int inptTxtMsrdHght = mInputText.getMeasuredHeight();
762 final int inptTxtLeft = (msrdWdth - inptTxtMsrdWdth) / 2;
763 final int inptTxtTop = (msrdHght - inptTxtMsrdHght) / 2;
764 final int inptTxtRight = inptTxtLeft + inptTxtMsrdWdth;
765 final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght;
766 mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700767
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700768 if (changed) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700769 // need to do all this when we know our size
770 initializeSelectorWheel();
771 initializeFadingEdges();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700772 mTopSelectionDividerTop = (getHeight() - mSelectionDividersDistance) / 2
773 - mSelectionDividerHeight;
774 mBottomSelectionDividerBottom = mTopSelectionDividerTop + 2 * mSelectionDividerHeight
775 + mSelectionDividersDistance;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700776 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800777 }
778
779 @Override
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700780 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700781 if (!mHasSelectorWheel) {
782 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
783 return;
784 }
Svetoslav Ganov698e1d52011-11-07 18:43:01 -0800785 // Try greedily to fit the max width and height.
786 final int newWidthMeasureSpec = makeMeasureSpec(widthMeasureSpec, mMaxWidth);
787 final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMaxHeight);
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700788 super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec);
Svetoslav Ganov698e1d52011-11-07 18:43:01 -0800789 // Flag if we are measured with width or height less than the respective min.
Svetoslav Ganov9f086d82011-11-29 18:27:23 -0800790 final int widthSize = resolveSizeAndStateRespectingMinSize(mMinWidth, getMeasuredWidth(),
791 widthMeasureSpec);
792 final int heightSize = resolveSizeAndStateRespectingMinSize(mMinHeight, getMeasuredHeight(),
793 heightMeasureSpec);
Svetoslav Ganov698e1d52011-11-07 18:43:01 -0800794 setMeasuredDimension(widthSize, heightSize);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700795 }
796
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700797 /**
798 * Move to the final position of a scroller. Ensures to force finish the scroller
799 * and if it is not at its final position a scroll of the selector wheel is
800 * performed to fast forward to the final position.
801 *
802 * @param scroller The scroller to whose final position to get.
803 * @return True of the a move was performed, i.e. the scroller was not in final position.
804 */
805 private boolean moveToFinalScrollerPosition(Scroller scroller) {
806 scroller.forceFinished(true);
807 int amountToScroll = scroller.getFinalY() - scroller.getCurrY();
808 int futureScrollOffset = (mCurrentScrollOffset + amountToScroll) % mSelectorElementHeight;
809 int overshootAdjustment = mInitialScrollOffset - futureScrollOffset;
810 if (overshootAdjustment != 0) {
811 if (Math.abs(overshootAdjustment) > mSelectorElementHeight / 2) {
812 if (overshootAdjustment > 0) {
813 overshootAdjustment -= mSelectorElementHeight;
814 } else {
815 overshootAdjustment += mSelectorElementHeight;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800816 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700817 }
818 amountToScroll += overshootAdjustment;
819 scrollBy(0, amountToScroll);
820 return true;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800821 }
822 return false;
823 }
824
825 @Override
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700826 public boolean onInterceptTouchEvent(MotionEvent event) {
827 if (!mHasSelectorWheel || !isEnabled()) {
828 return false;
829 }
830 final int action = event.getActionMasked();
831 switch (action) {
832 case MotionEvent.ACTION_DOWN: {
833 removeAllCallbacks();
834 mInputText.setVisibility(View.INVISIBLE);
835 mLastDownOrMoveEventY = mLastDownEventY = event.getY();
836 mLastDownEventTime = event.getEventTime();
837 mIngonreMoveEvents = false;
838 mShowSoftInputOnTap = false;
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700839 // Handle pressed state before any state change.
840 if (mLastDownEventY < mTopSelectionDividerTop) {
841 if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
842 mPressedStateHelper.buttonPressDelayed(
843 PressedStateHelper.BUTTON_DECREMENT);
844 }
845 } else if (mLastDownEventY > mBottomSelectionDividerBottom) {
846 if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
847 mPressedStateHelper.buttonPressDelayed(
848 PressedStateHelper.BUTTON_INCREMENT);
849 }
850 }
851 // Make sure we support flinging inside scrollables.
Svetoslav Ganov83dc45c2012-04-12 16:19:32 -0700852 getParent().requestDisallowInterceptTouchEvent(true);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700853 if (!mFlingScroller.isFinished()) {
854 mFlingScroller.forceFinished(true);
855 mAdjustScroller.forceFinished(true);
856 onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
857 } else if (!mAdjustScroller.isFinished()) {
858 mFlingScroller.forceFinished(true);
859 mAdjustScroller.forceFinished(true);
860 } else if (mLastDownEventY < mTopSelectionDividerTop) {
861 hideSoftInput();
862 postChangeCurrentByOneFromLongPress(
863 false, ViewConfiguration.getLongPressTimeout());
864 } else if (mLastDownEventY > mBottomSelectionDividerBottom) {
865 hideSoftInput();
866 postChangeCurrentByOneFromLongPress(
867 true, ViewConfiguration.getLongPressTimeout());
868 } else {
869 mShowSoftInputOnTap = true;
870 postBeginSoftInputOnLongPressCommand();
871 }
872 return true;
873 }
874 }
875 return false;
876 }
877
878 @Override
879 public boolean onTouchEvent(MotionEvent event) {
880 if (!isEnabled() || !mHasSelectorWheel) {
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800881 return false;
882 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800883 if (mVelocityTracker == null) {
884 mVelocityTracker = VelocityTracker.obtain();
885 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700886 mVelocityTracker.addMovement(event);
887 int action = event.getActionMasked();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800888 switch (action) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700889 case MotionEvent.ACTION_MOVE: {
890 if (mIngonreMoveEvents) {
891 break;
892 }
893 float currentMoveY = event.getY();
894 if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800895 int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY);
896 if (deltaDownY > mTouchSlop) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700897 removeAllCallbacks();
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800898 onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800899 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700900 } else {
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700901 int deltaMoveY = (int) ((currentMoveY - mLastDownOrMoveEventY));
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700902 scrollBy(0, deltaMoveY);
903 invalidate();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800904 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700905 mLastDownOrMoveEventY = currentMoveY;
906 } break;
907 case MotionEvent.ACTION_UP: {
908 removeBeginSoftInputCommand();
909 removeChangeCurrentByOneFromLongPress();
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700910 mPressedStateHelper.cancel();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800911 VelocityTracker velocityTracker = mVelocityTracker;
912 velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
913 int initialVelocity = (int) velocityTracker.getYVelocity();
914 if (Math.abs(initialVelocity) > mMinimumFlingVelocity) {
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700915 fling(initialVelocity);
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800916 onScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800917 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700918 int eventY = (int) event.getY();
919 int deltaMoveY = (int) Math.abs(eventY - mLastDownEventY);
920 long deltaTime = event.getEventTime() - mLastDownEventTime;
921 if (deltaMoveY <= mTouchSlop && deltaTime < ViewConfiguration.getTapTimeout()) {
922 if (mShowSoftInputOnTap) {
923 mShowSoftInputOnTap = false;
924 showSoftInput();
925 } else {
926 int selectorIndexOffset = (eventY / mSelectorElementHeight)
927 - SELECTOR_MIDDLE_ITEM_INDEX;
928 if (selectorIndexOffset > 0) {
929 changeValueByOne(true);
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700930 mPressedStateHelper.buttonTapped(
931 PressedStateHelper.BUTTON_INCREMENT);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700932 } else if (selectorIndexOffset < 0) {
933 changeValueByOne(false);
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700934 mPressedStateHelper.buttonTapped(
935 PressedStateHelper.BUTTON_DECREMENT);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700936 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800937 }
938 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700939 ensureScrollWheelAdjusted();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800940 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700941 onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800942 }
943 mVelocityTracker.recycle();
944 mVelocityTracker = null;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700945 } break;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800946 }
947 return true;
948 }
949
950 @Override
951 public boolean dispatchTouchEvent(MotionEvent event) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700952 final int action = event.getActionMasked();
953 switch (action) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700954 case MotionEvent.ACTION_CANCEL:
955 case MotionEvent.ACTION_UP:
956 removeAllCallbacks();
957 break;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800958 }
959 return super.dispatchTouchEvent(event);
960 }
961
962 @Override
963 public boolean dispatchKeyEvent(KeyEvent event) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700964 final int keyCode = event.getKeyCode();
965 switch (keyCode) {
966 case KeyEvent.KEYCODE_DPAD_CENTER:
967 case KeyEvent.KEYCODE_ENTER:
968 removeAllCallbacks();
969 break;
Svetoslav Ganov5dc21d92012-10-07 18:54:42 -0700970 case KeyEvent.KEYCODE_DPAD_DOWN:
971 case KeyEvent.KEYCODE_DPAD_UP:
972 if (!mHasSelectorWheel) {
973 break;
974 }
975 switch (event.getAction()) {
976 case KeyEvent.ACTION_DOWN:
Justin Mattson86cf0cd2012-10-08 11:46:28 -0700977 if (mWrapSelectorWheel || (keyCode == KeyEvent.KEYCODE_DPAD_DOWN)
Svetoslav Ganov5dc21d92012-10-07 18:54:42 -0700978 ? getValue() < getMaxValue() : getValue() > getMinValue()) {
979 requestFocus();
980 mLastHandledDownDpadKeyCode = keyCode;
981 removeAllCallbacks();
982 if (mFlingScroller.isFinished()) {
Justin Mattson86cf0cd2012-10-08 11:46:28 -0700983 changeValueByOne(keyCode == KeyEvent.KEYCODE_DPAD_DOWN);
Svetoslav Ganov5dc21d92012-10-07 18:54:42 -0700984 }
985 return true;
986 }
987 break;
988 case KeyEvent.ACTION_UP:
989 if (mLastHandledDownDpadKeyCode == keyCode) {
990 mLastHandledDownDpadKeyCode = -1;
991 return true;
992 }
993 break;
994 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800995 }
996 return super.dispatchKeyEvent(event);
997 }
998
999 @Override
1000 public boolean dispatchTrackballEvent(MotionEvent event) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001001 final int action = event.getActionMasked();
1002 switch (action) {
1003 case MotionEvent.ACTION_CANCEL:
1004 case MotionEvent.ACTION_UP:
1005 removeAllCallbacks();
1006 break;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001007 }
1008 return super.dispatchTrackballEvent(event);
1009 }
1010
1011 @Override
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001012 protected boolean dispatchHoverEvent(MotionEvent event) {
1013 if (!mHasSelectorWheel) {
1014 return super.dispatchHoverEvent(event);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001015 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001016 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
1017 final int eventY = (int) event.getY();
1018 final int hoveredVirtualViewId;
1019 if (eventY < mTopSelectionDividerTop) {
1020 hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_DECREMENT;
1021 } else if (eventY > mBottomSelectionDividerBottom) {
1022 hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INCREMENT;
1023 } else {
1024 hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INPUT;
1025 }
1026 final int action = event.getActionMasked();
1027 AccessibilityNodeProviderImpl provider =
1028 (AccessibilityNodeProviderImpl) getAccessibilityNodeProvider();
1029 switch (action) {
1030 case MotionEvent.ACTION_HOVER_ENTER: {
1031 provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
1032 AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
1033 mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07001034 provider.performAction(hoveredVirtualViewId,
1035 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001036 } break;
1037 case MotionEvent.ACTION_HOVER_MOVE: {
1038 if (mLastHoveredChildVirtualViewId != hoveredVirtualViewId
1039 && mLastHoveredChildVirtualViewId != View.NO_ID) {
1040 provider.sendAccessibilityEventForVirtualView(
1041 mLastHoveredChildVirtualViewId,
1042 AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
1043 provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
1044 AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
1045 mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07001046 provider.performAction(hoveredVirtualViewId,
1047 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001048 }
1049 } break;
1050 case MotionEvent.ACTION_HOVER_EXIT: {
1051 provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
1052 AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
1053 mLastHoveredChildVirtualViewId = View.NO_ID;
1054 } break;
1055 }
1056 }
1057 return false;
1058 }
1059
1060 @Override
1061 public void computeScroll() {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001062 Scroller scroller = mFlingScroller;
1063 if (scroller.isFinished()) {
1064 scroller = mAdjustScroller;
1065 if (scroller.isFinished()) {
1066 return;
1067 }
1068 }
1069 scroller.computeScrollOffset();
1070 int currentScrollerY = scroller.getCurrY();
1071 if (mPreviousScrollerY == 0) {
1072 mPreviousScrollerY = scroller.getStartY();
1073 }
1074 scrollBy(0, currentScrollerY - mPreviousScrollerY);
1075 mPreviousScrollerY = currentScrollerY;
1076 if (scroller.isFinished()) {
1077 onScrollerFinished(scroller);
1078 } else {
1079 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001080 }
1081 }
Tom Taylorfdf6db62009-09-09 11:37:58 -07001082
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001083 @Override
1084 public void setEnabled(boolean enabled) {
1085 super.setEnabled(enabled);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001086 if (!mHasSelectorWheel) {
1087 mIncrementButton.setEnabled(enabled);
1088 }
1089 if (!mHasSelectorWheel) {
1090 mDecrementButton.setEnabled(enabled);
1091 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001092 mInputText.setEnabled(enabled);
1093 }
1094
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001095 @Override
1096 public void scrollBy(int x, int y) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001097 int[] selectorIndices = mSelectorIndices;
Svetoslav Ganov34c06882011-01-03 01:32:34 -08001098 if (!mWrapSelectorWheel && y > 0
1099 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001100 mCurrentScrollOffset = mInitialScrollOffset;
1101 return;
1102 }
Svetoslav Ganov34c06882011-01-03 01:32:34 -08001103 if (!mWrapSelectorWheel && y < 0
1104 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001105 mCurrentScrollOffset = mInitialScrollOffset;
1106 return;
1107 }
1108 mCurrentScrollOffset += y;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001109 while (mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapHeight) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001110 mCurrentScrollOffset -= mSelectorElementHeight;
1111 decrementSelectorIndices(selectorIndices);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001112 setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001113 if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001114 mCurrentScrollOffset = mInitialScrollOffset;
1115 }
1116 }
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001117 while (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapHeight) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001118 mCurrentScrollOffset += mSelectorElementHeight;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001119 incrementSelectorIndices(selectorIndices);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001120 setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001121 if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001122 mCurrentScrollOffset = mInitialScrollOffset;
1123 }
1124 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001125 }
Tom Taylorfdf6db62009-09-09 11:37:58 -07001126
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001127 @Override
Alan Viverettefd639172013-09-18 11:20:38 -07001128 protected int computeVerticalScrollOffset() {
Alan Viverette5ba99f52013-09-13 12:23:20 -07001129 return mCurrentScrollOffset;
1130 }
1131
1132 @Override
Alan Viverettefd639172013-09-18 11:20:38 -07001133 protected int computeVerticalScrollRange() {
1134 return (mMaxValue - mMinValue + 1) * mSelectorElementHeight;
1135 }
1136
1137 @Override
1138 protected int computeVerticalScrollExtent() {
1139 return getHeight();
Alan Viverette5ba99f52013-09-13 12:23:20 -07001140 }
1141
1142 @Override
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001143 public int getSolidColor() {
1144 return mSolidColor;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001145 }
1146
1147 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001148 * Sets the listener to be notified on change of the current value.
1149 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001150 * @param onValueChangedListener The listener.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001151 */
Svetoslav Ganovcedc4462011-01-19 19:25:46 -08001152 public void setOnValueChangedListener(OnValueChangeListener onValueChangedListener) {
1153 mOnValueChangeListener = onValueChangedListener;
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001154 }
1155
1156 /**
1157 * Set listener to be notified for scroll state changes.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001158 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001159 * @param onScrollListener The listener.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001160 */
1161 public void setOnScrollListener(OnScrollListener onScrollListener) {
1162 mOnScrollListener = onScrollListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001163 }
Tom Taylorfdf6db62009-09-09 11:37:58 -07001164
Paul Westbrook68f2f542010-01-13 12:13:57 -08001165 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001166 * Set the formatter to be used for formatting the current value.
1167 * <p>
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001168 * Note: If you have provided alternative values for the values this
1169 * formatter is never invoked.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001170 * </p>
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001171 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001172 * @param formatter The formatter object. If formatter is <code>null</code>,
1173 * {@link String#valueOf(int)} will be used.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001174 *@see #setDisplayedValues(String[])
Paul Westbrook68f2f542010-01-13 12:13:57 -08001175 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001176 public void setFormatter(Formatter formatter) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001177 if (formatter == mFormatter) {
Svetoslav Ganova911d4a2010-12-08 16:11:30 -08001178 return;
1179 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001180 mFormatter = formatter;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001181 initializeSelectorWheelIndices();
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001182 updateInputTextView();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001183 }
Tom Taylorfdf6db62009-09-09 11:37:58 -07001184
Paul Westbrook68f2f542010-01-13 12:13:57 -08001185 /**
1186 * Set the current value for the number picker.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001187 * <p>
1188 * If the argument is less than the {@link NumberPicker#getMinValue()} and
1189 * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the
1190 * current value is set to the {@link NumberPicker#getMinValue()} value.
1191 * </p>
1192 * <p>
1193 * If the argument is less than the {@link NumberPicker#getMinValue()} and
1194 * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the
1195 * current value is set to the {@link NumberPicker#getMaxValue()} value.
1196 * </p>
1197 * <p>
1198 * If the argument is less than the {@link NumberPicker#getMaxValue()} and
1199 * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the
1200 * current value is set to the {@link NumberPicker#getMaxValue()} value.
1201 * </p>
1202 * <p>
1203 * If the argument is less than the {@link NumberPicker#getMaxValue()} and
1204 * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the
1205 * current value is set to the {@link NumberPicker#getMinValue()} value.
1206 * </p>
Paul Westbrook68f2f542010-01-13 12:13:57 -08001207 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001208 * @param value The current value.
1209 * @see #setWrapSelectorWheel(boolean)
1210 * @see #setMinValue(int)
1211 * @see #setMaxValue(int)
Paul Westbrook68f2f542010-01-13 12:13:57 -08001212 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001213 public void setValue(int value) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001214 setValueInternal(value, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001215 }
1216
1217 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001218 * Shows the soft input for its input text.
1219 */
1220 private void showSoftInput() {
1221 InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
1222 if (inputMethodManager != null) {
1223 if (mHasSelectorWheel) {
1224 mInputText.setVisibility(View.VISIBLE);
1225 }
1226 mInputText.requestFocus();
1227 inputMethodManager.showSoftInput(mInputText, 0);
1228 }
1229 }
1230
1231 /**
1232 * Hides the soft input if it is active for the input text.
Svetoslav Ganovb52d9722011-11-07 14:53:34 -08001233 */
1234 private void hideSoftInput() {
1235 InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
1236 if (inputMethodManager != null && inputMethodManager.isActive(mInputText)) {
1237 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001238 if (mHasSelectorWheel) {
1239 mInputText.setVisibility(View.INVISIBLE);
1240 }
Svetoslav Ganovb52d9722011-11-07 14:53:34 -08001241 }
1242 }
1243
1244 /**
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001245 * Computes the max width if no such specified as an attribute.
1246 */
1247 private void tryComputeMaxWidth() {
1248 if (!mComputeMaxWidth) {
1249 return;
1250 }
1251 int maxTextWidth = 0;
1252 if (mDisplayedValues == null) {
1253 float maxDigitWidth = 0;
1254 for (int i = 0; i <= 9; i++) {
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07001255 final float digitWidth = mSelectorWheelPaint.measureText(formatNumberWithLocale(i));
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001256 if (digitWidth > maxDigitWidth) {
1257 maxDigitWidth = digitWidth;
1258 }
1259 }
1260 int numberOfDigits = 0;
1261 int current = mMaxValue;
1262 while (current > 0) {
1263 numberOfDigits++;
1264 current = current / 10;
1265 }
1266 maxTextWidth = (int) (numberOfDigits * maxDigitWidth);
1267 } else {
1268 final int valueCount = mDisplayedValues.length;
1269 for (int i = 0; i < valueCount; i++) {
1270 final float textWidth = mSelectorWheelPaint.measureText(mDisplayedValues[i]);
1271 if (textWidth > maxTextWidth) {
1272 maxTextWidth = (int) textWidth;
1273 }
1274 }
1275 }
1276 maxTextWidth += mInputText.getPaddingLeft() + mInputText.getPaddingRight();
1277 if (mMaxWidth != maxTextWidth) {
1278 if (maxTextWidth > mMinWidth) {
1279 mMaxWidth = maxTextWidth;
1280 } else {
1281 mMaxWidth = mMinWidth;
1282 }
1283 invalidate();
1284 }
1285 }
1286
1287 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001288 * Gets whether the selector wheel wraps when reaching the min/max value.
1289 *
1290 * @return True if the selector wheel wraps.
1291 *
1292 * @see #getMinValue()
1293 * @see #getMaxValue()
1294 */
1295 public boolean getWrapSelectorWheel() {
1296 return mWrapSelectorWheel;
1297 }
1298
1299 /**
1300 * Sets whether the selector wheel shown during flinging/scrolling should
1301 * wrap around the {@link NumberPicker#getMinValue()} and
1302 * {@link NumberPicker#getMaxValue()} values.
1303 * <p>
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001304 * By default if the range (max - min) is more than the number of items shown
1305 * on the selector wheel the selector wheel wrapping is enabled.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001306 * </p>
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -08001307 * <p>
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001308 * <strong>Note:</strong> If the number of items, i.e. the range (
1309 * {@link #getMaxValue()} - {@link #getMinValue()}) is less than
1310 * the number of items shown on the selector wheel, the selector wheel will
1311 * not wrap. Hence, in such a case calling this method is a NOP.
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -08001312 * </p>
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001313 *
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001314 * @param wrapSelectorWheel Whether to wrap.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001315 */
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001316 public void setWrapSelectorWheel(boolean wrapSelectorWheel) {
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -08001317 final boolean wrappingAllowed = (mMaxValue - mMinValue) >= mSelectorIndices.length;
1318 if ((!wrapSelectorWheel || wrappingAllowed) && wrapSelectorWheel != mWrapSelectorWheel) {
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001319 mWrapSelectorWheel = wrapSelectorWheel;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001320 }
1321 }
1322
1323 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001324 * Sets the speed at which the numbers be incremented and decremented when
1325 * the up and down buttons are long pressed respectively.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001326 * <p>
1327 * The default value is 300 ms.
1328 * </p>
Paul Westbrook68f2f542010-01-13 12:13:57 -08001329 *
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001330 * @param intervalMillis The speed (in milliseconds) at which the numbers
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001331 * will be incremented and decremented.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001332 */
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001333 public void setOnLongPressUpdateInterval(long intervalMillis) {
1334 mLongPressUpdateInterval = intervalMillis;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001335 }
1336
1337 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001338 * Returns the value of the picker.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001339 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001340 * @return The value.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001341 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001342 public int getValue() {
1343 return mValue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001344 }
Paul Westbrook68f2f542010-01-13 12:13:57 -08001345
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001346 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001347 * Returns the min value of the picker.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001348 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001349 * @return The min value
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001350 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001351 public int getMinValue() {
1352 return mMinValue;
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001353 }
1354
1355 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001356 * Sets the min value of the picker.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001357 *
Svetoslav Ganov7018cfdc2012-11-19 12:33:41 -08001358 * @param minValue The min value inclusive.
1359 *
1360 * <strong>Note:</strong> The length of the displayed values array
1361 * set via {@link #setDisplayedValues(String[])} must be equal to the
1362 * range of selectable numbers which is equal to
1363 * {@link #getMaxValue()} - {@link #getMinValue()} + 1.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001364 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001365 public void setMinValue(int minValue) {
1366 if (mMinValue == minValue) {
1367 return;
1368 }
1369 if (minValue < 0) {
1370 throw new IllegalArgumentException("minValue must be >= 0");
1371 }
1372 mMinValue = minValue;
1373 if (mMinValue > mValue) {
1374 mValue = mMinValue;
1375 }
1376 boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
1377 setWrapSelectorWheel(wrapSelectorWheel);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001378 initializeSelectorWheelIndices();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001379 updateInputTextView();
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001380 tryComputeMaxWidth();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001381 invalidate();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001382 }
1383
1384 /**
1385 * Returns the max value of the picker.
1386 *
1387 * @return The max value.
1388 */
1389 public int getMaxValue() {
1390 return mMaxValue;
1391 }
1392
1393 /**
1394 * Sets the max value of the picker.
1395 *
Svetoslav Ganov7018cfdc2012-11-19 12:33:41 -08001396 * @param maxValue The max value inclusive.
1397 *
1398 * <strong>Note:</strong> The length of the displayed values array
1399 * set via {@link #setDisplayedValues(String[])} must be equal to the
1400 * range of selectable numbers which is equal to
1401 * {@link #getMaxValue()} - {@link #getMinValue()} + 1.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001402 */
1403 public void setMaxValue(int maxValue) {
1404 if (mMaxValue == maxValue) {
1405 return;
1406 }
1407 if (maxValue < 0) {
1408 throw new IllegalArgumentException("maxValue must be >= 0");
1409 }
1410 mMaxValue = maxValue;
1411 if (mMaxValue < mValue) {
1412 mValue = mMaxValue;
1413 }
1414 boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
1415 setWrapSelectorWheel(wrapSelectorWheel);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001416 initializeSelectorWheelIndices();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001417 updateInputTextView();
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001418 tryComputeMaxWidth();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001419 invalidate();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001420 }
1421
1422 /**
1423 * Gets the values to be displayed instead of string values.
1424 *
1425 * @return The displayed values.
1426 */
1427 public String[] getDisplayedValues() {
1428 return mDisplayedValues;
1429 }
1430
1431 /**
1432 * Sets the values to be displayed.
1433 *
1434 * @param displayedValues The displayed values.
Svetoslav Ganov7018cfdc2012-11-19 12:33:41 -08001435 *
1436 * <strong>Note:</strong> The length of the displayed values array
1437 * must be equal to the range of selectable numbers which is equal to
1438 * {@link #getMaxValue()} - {@link #getMinValue()} + 1.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001439 */
1440 public void setDisplayedValues(String[] displayedValues) {
1441 if (mDisplayedValues == displayedValues) {
1442 return;
1443 }
1444 mDisplayedValues = displayedValues;
1445 if (mDisplayedValues != null) {
1446 // Allow text entry rather than strictly numeric entry.
1447 mInputText.setRawInputType(InputType.TYPE_CLASS_TEXT
1448 | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
1449 } else {
1450 mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
1451 }
1452 updateInputTextView();
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001453 initializeSelectorWheelIndices();
Svetoslav Ganov9f086d82011-11-29 18:27:23 -08001454 tryComputeMaxWidth();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001455 }
1456
1457 @Override
1458 protected float getTopFadingEdgeStrength() {
1459 return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
1460 }
1461
1462 @Override
1463 protected float getBottomFadingEdgeStrength() {
1464 return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
1465 }
1466
1467 @Override
1468 protected void onDetachedFromWindow() {
Romain Guy46bfc482013-08-16 18:38:29 -07001469 super.onDetachedFromWindow();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001470 removeAllCallbacks();
1471 }
1472
1473 @Override
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001474 protected void onDraw(Canvas canvas) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001475 if (!mHasSelectorWheel) {
1476 super.onDraw(canvas);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001477 return;
1478 }
1479 float x = (mRight - mLeft) / 2;
1480 float y = mCurrentScrollOffset;
1481
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001482 // draw the virtual buttons pressed state if needed
1483 if (mVirtualButtonPressedDrawable != null
1484 && mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
1485 if (mDecrementVirtualButtonPressed) {
1486 mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET);
1487 mVirtualButtonPressedDrawable.setBounds(0, 0, mRight, mTopSelectionDividerTop);
1488 mVirtualButtonPressedDrawable.draw(canvas);
1489 }
1490 if (mIncrementVirtualButtonPressed) {
1491 mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET);
1492 mVirtualButtonPressedDrawable.setBounds(0, mBottomSelectionDividerBottom, mRight,
1493 mBottom);
1494 mVirtualButtonPressedDrawable.draw(canvas);
1495 }
1496 }
1497
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001498 // draw the selector wheel
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001499 int[] selectorIndices = mSelectorIndices;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001500 for (int i = 0; i < selectorIndices.length; i++) {
1501 int selectorIndex = selectorIndices[i];
1502 String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001503 // Do not draw the middle item if input is visible since the input
1504 // is shown only if the wheel is static and it covers the middle
1505 // item. Otherwise, if the user starts editing the text via the
1506 // IME he may see a dimmed version of the old value intermixed
1507 // with the new one.
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001508 if (i != SELECTOR_MIDDLE_ITEM_INDEX || mInputText.getVisibility() != VISIBLE) {
1509 canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint);
1510 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001511 y += mSelectorElementHeight;
1512 }
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001513
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001514 // draw the selection dividers
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001515 if (mSelectionDivider != null) {
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001516 // draw the top divider
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001517 int topOfTopDivider = mTopSelectionDividerTop;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001518 int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight;
1519 mSelectionDivider.setBounds(0, topOfTopDivider, mRight, bottomOfTopDivider);
1520 mSelectionDivider.draw(canvas);
1521
1522 // draw the bottom divider
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001523 int bottomOfBottomDivider = mBottomSelectionDividerBottom;
1524 int topOfBottomDivider = bottomOfBottomDivider - mSelectionDividerHeight;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001525 mSelectionDivider.setBounds(0, topOfBottomDivider, mRight, bottomOfBottomDivider);
1526 mSelectionDivider.draw(canvas);
1527 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001528 }
1529
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -07001530 @Override
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001531 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1532 super.onInitializeAccessibilityEvent(event);
1533 event.setClassName(NumberPicker.class.getName());
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001534 event.setScrollable(true);
1535 event.setScrollY((mMinValue + mValue) * mSelectorElementHeight);
1536 event.setMaxScrollY((mMaxValue - mMinValue) * mSelectorElementHeight);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001537 }
1538
1539 @Override
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001540 public AccessibilityNodeProvider getAccessibilityNodeProvider() {
1541 if (!mHasSelectorWheel) {
1542 return super.getAccessibilityNodeProvider();
1543 }
1544 if (mAccessibilityNodeProvider == null) {
1545 mAccessibilityNodeProvider = new AccessibilityNodeProviderImpl();
1546 }
1547 return mAccessibilityNodeProvider;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001548 }
1549
Paul Westbrook68f2f542010-01-13 12:13:57 -08001550 /**
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001551 * Makes a measure spec that tries greedily to use the max value.
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001552 *
1553 * @param measureSpec The measure spec.
Svetoslav Ganov698e1d52011-11-07 18:43:01 -08001554 * @param maxSize The max value for the size.
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001555 * @return A measure spec greedily imposing the max size.
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001556 */
Svetoslav Ganov698e1d52011-11-07 18:43:01 -08001557 private int makeMeasureSpec(int measureSpec, int maxSize) {
Svetoslav Ganov9f086d82011-11-29 18:27:23 -08001558 if (maxSize == SIZE_UNSPECIFIED) {
1559 return measureSpec;
1560 }
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001561 final int size = MeasureSpec.getSize(measureSpec);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001562 final int mode = MeasureSpec.getMode(measureSpec);
1563 switch (mode) {
1564 case MeasureSpec.EXACTLY:
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001565 return measureSpec;
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001566 case MeasureSpec.AT_MOST:
Svetoslav Ganov698e1d52011-11-07 18:43:01 -08001567 return MeasureSpec.makeMeasureSpec(Math.min(size, maxSize), MeasureSpec.EXACTLY);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001568 case MeasureSpec.UNSPECIFIED:
Svetoslav Ganov698e1d52011-11-07 18:43:01 -08001569 return MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.EXACTLY);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001570 default:
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001571 throw new IllegalArgumentException("Unknown measure mode: " + mode);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001572 }
1573 }
1574
1575 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001576 * Utility to reconcile a desired size and state, with constraints imposed
1577 * by a MeasureSpec. Tries to respect the min size, unless a different size
1578 * is imposed by the constraints.
Svetoslav Ganov9f086d82011-11-29 18:27:23 -08001579 *
1580 * @param minSize The minimal desired size.
1581 * @param measuredSize The currently measured size.
1582 * @param measureSpec The current measure spec.
1583 * @return The resolved size and state.
1584 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001585 private int resolveSizeAndStateRespectingMinSize(
1586 int minSize, int measuredSize, int measureSpec) {
Svetoslav Ganov9f086d82011-11-29 18:27:23 -08001587 if (minSize != SIZE_UNSPECIFIED) {
1588 final int desiredWidth = Math.max(minSize, measuredSize);
1589 return resolveSizeAndState(desiredWidth, measureSpec, 0);
1590 } else {
1591 return measuredSize;
1592 }
1593 }
1594
1595 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001596 * Resets the selector indices and clear the cached string representation of
1597 * these indices.
Svetoslav Ganova911d4a2010-12-08 16:11:30 -08001598 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001599 private void initializeSelectorWheelIndices() {
Svetoslav Ganova911d4a2010-12-08 16:11:30 -08001600 mSelectorIndexToStringCache.clear();
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001601 int[] selectorIndices = mSelectorIndices;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001602 int current = getValue();
1603 for (int i = 0; i < mSelectorIndices.length; i++) {
1604 int selectorIndex = current + (i - SELECTOR_MIDDLE_ITEM_INDEX);
1605 if (mWrapSelectorWheel) {
1606 selectorIndex = getWrappedSelectorIndex(selectorIndex);
1607 }
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001608 selectorIndices[i] = selectorIndex;
1609 ensureCachedScrollSelectorValue(selectorIndices[i]);
Svetoslav Ganova911d4a2010-12-08 16:11:30 -08001610 }
1611 }
1612
1613 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001614 * Sets the current value of this NumberPicker.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001615 *
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001616 * @param current The new value of the NumberPicker.
1617 * @param notifyChange Whether to notify if the current value changed.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001618 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001619 private void setValueInternal(int current, boolean notifyChange) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001620 if (mValue == current) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001621 return;
1622 }
1623 // Wrap around the values if we go past the start or end
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001624 if (mWrapSelectorWheel) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001625 current = getWrappedSelectorIndex(current);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001626 } else {
1627 current = Math.max(current, mMinValue);
1628 current = Math.min(current, mMaxValue);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001629 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001630 int previous = mValue;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001631 mValue = current;
1632 updateInputTextView();
1633 if (notifyChange) {
1634 notifyChange(previous, current);
1635 }
Svetoslav Ganovfac14f92012-04-12 16:51:04 -07001636 initializeSelectorWheelIndices();
1637 invalidate();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001638 }
1639
1640 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001641 * Changes the current value by one which is increment or
1642 * decrement based on the passes argument.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001643 * decrement the current value.
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001644 *
1645 * @param increment True to increment, false to decrement.
1646 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001647 private void changeValueByOne(boolean increment) {
1648 if (mHasSelectorWheel) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001649 mInputText.setVisibility(View.INVISIBLE);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001650 if (!moveToFinalScrollerPosition(mFlingScroller)) {
1651 moveToFinalScrollerPosition(mAdjustScroller);
1652 }
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001653 mPreviousScrollerY = 0;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001654 if (increment) {
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -07001655 mFlingScroller.startScroll(0, 0, 0, -mSelectorElementHeight, SNAP_SCROLL_DURATION);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001656 } else {
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -07001657 mFlingScroller.startScroll(0, 0, 0, mSelectorElementHeight, SNAP_SCROLL_DURATION);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001658 }
1659 invalidate();
1660 } else {
1661 if (increment) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001662 setValueInternal(mValue + 1, true);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001663 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001664 setValueInternal(mValue - 1, true);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001665 }
1666 }
1667 }
1668
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001669 private void initializeSelectorWheel() {
1670 initializeSelectorWheelIndices();
1671 int[] selectorIndices = mSelectorIndices;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001672 int totalTextHeight = selectorIndices.length * mTextSize;
Svetoslav Ganov01fa0d72011-06-28 22:08:23 -07001673 float totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001674 float textGapCount = selectorIndices.length;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001675 mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f);
1676 mSelectorElementHeight = mTextSize + mSelectorTextGapHeight;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001677 // Ensure that the middle item is positioned the same as the text in
1678 // mInputText
Chet Haaseeeafd422011-08-17 18:26:56 -07001679 int editTextTextPosition = mInputText.getBaseline() + mInputText.getTop();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001680 mInitialScrollOffset = editTextTextPosition
1681 - (mSelectorElementHeight * SELECTOR_MIDDLE_ITEM_INDEX);
Svetoslav Ganov6a19fcd2011-06-29 12:26:11 -07001682 mCurrentScrollOffset = mInitialScrollOffset;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001683 updateInputTextView();
1684 }
1685
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001686 private void initializeFadingEdges() {
1687 setVerticalFadingEdgeEnabled(true);
1688 setFadingEdgeLength((mBottom - mTop - mTextSize) / 2);
1689 }
1690
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001691 /**
1692 * Callback invoked upon completion of a given <code>scroller</code>.
1693 */
1694 private void onScrollerFinished(Scroller scroller) {
1695 if (scroller == mFlingScroller) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001696 if (!ensureScrollWheelAdjusted()) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001697 updateInputTextView();
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001698 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001699 onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001700 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001701 if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
1702 updateInputTextView();
1703 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001704 }
1705 }
1706
1707 /**
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001708 * Handles transition to a given <code>scrollState</code>
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001709 */
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001710 private void onScrollStateChange(int scrollState) {
1711 if (mScrollState == scrollState) {
1712 return;
1713 }
1714 mScrollState = scrollState;
1715 if (mOnScrollListener != null) {
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001716 mOnScrollListener.onScrollStateChange(this, scrollState);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001717 }
1718 }
1719
1720 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001721 * Flings the selector with the given <code>velocityY</code>.
1722 */
1723 private void fling(int velocityY) {
1724 mPreviousScrollerY = 0;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001725
Svetoslav Ganov234484a2011-12-07 19:06:35 -08001726 if (velocityY > 0) {
1727 mFlingScroller.fling(0, 0, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001728 } else {
Svetoslav Ganov234484a2011-12-07 19:06:35 -08001729 mFlingScroller.fling(0, Integer.MAX_VALUE, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001730 }
1731
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001732 invalidate();
1733 }
1734
1735 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001736 * @return The wrapped index <code>selectorIndex</code> value.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001737 */
1738 private int getWrappedSelectorIndex(int selectorIndex) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001739 if (selectorIndex > mMaxValue) {
1740 return mMinValue + (selectorIndex - mMaxValue) % (mMaxValue - mMinValue) - 1;
1741 } else if (selectorIndex < mMinValue) {
1742 return mMaxValue - (mMinValue - selectorIndex) % (mMaxValue - mMinValue) + 1;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001743 }
1744 return selectorIndex;
1745 }
1746
1747 /**
1748 * Increments the <code>selectorIndices</code> whose string representations
1749 * will be displayed in the selector.
1750 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001751 private void incrementSelectorIndices(int[] selectorIndices) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001752 for (int i = 0; i < selectorIndices.length - 1; i++) {
1753 selectorIndices[i] = selectorIndices[i + 1];
1754 }
1755 int nextScrollSelectorIndex = selectorIndices[selectorIndices.length - 2] + 1;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001756 if (mWrapSelectorWheel && nextScrollSelectorIndex > mMaxValue) {
1757 nextScrollSelectorIndex = mMinValue;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001758 }
1759 selectorIndices[selectorIndices.length - 1] = nextScrollSelectorIndex;
1760 ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
1761 }
1762
1763 /**
1764 * Decrements the <code>selectorIndices</code> whose string representations
1765 * will be displayed in the selector.
1766 */
1767 private void decrementSelectorIndices(int[] selectorIndices) {
1768 for (int i = selectorIndices.length - 1; i > 0; i--) {
1769 selectorIndices[i] = selectorIndices[i - 1];
1770 }
1771 int nextScrollSelectorIndex = selectorIndices[1] - 1;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001772 if (mWrapSelectorWheel && nextScrollSelectorIndex < mMinValue) {
1773 nextScrollSelectorIndex = mMaxValue;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001774 }
1775 selectorIndices[0] = nextScrollSelectorIndex;
1776 ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
1777 }
1778
1779 /**
1780 * Ensures we have a cached string representation of the given <code>
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001781 * selectorIndex</code> to avoid multiple instantiations of the same string.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001782 */
1783 private void ensureCachedScrollSelectorValue(int selectorIndex) {
1784 SparseArray<String> cache = mSelectorIndexToStringCache;
1785 String scrollSelectorValue = cache.get(selectorIndex);
1786 if (scrollSelectorValue != null) {
1787 return;
1788 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001789 if (selectorIndex < mMinValue || selectorIndex > mMaxValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001790 scrollSelectorValue = "";
1791 } else {
1792 if (mDisplayedValues != null) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001793 int displayedValueIndex = selectorIndex - mMinValue;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001794 scrollSelectorValue = mDisplayedValues[displayedValueIndex];
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001795 } else {
1796 scrollSelectorValue = formatNumber(selectorIndex);
1797 }
1798 }
1799 cache.put(selectorIndex, scrollSelectorValue);
1800 }
1801
1802 private String formatNumber(int value) {
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07001803 return (mFormatter != null) ? mFormatter.format(value) : formatNumberWithLocale(value);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001804 }
1805
1806 private void validateInputTextView(View v) {
1807 String str = String.valueOf(((TextView) v).getText());
1808 if (TextUtils.isEmpty(str)) {
1809 // Restore to the old value as we don't allow empty values
1810 updateInputTextView();
1811 } else {
1812 // Check the new value and ensure it's in range
1813 int current = getSelectedPos(str.toString());
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001814 setValueInternal(current, true);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001815 }
1816 }
1817
1818 /**
1819 * Updates the view of this NumberPicker. If displayValues were specified in
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001820 * the string corresponding to the index specified by the current value will
1821 * be returned. Otherwise, the formatter specified in {@link #setFormatter}
1822 * will be used to format the number.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001823 *
1824 * @return Whether the text was updated.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001825 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001826 private boolean updateInputTextView() {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001827 /*
1828 * If we don't have displayed values then use the current number else
1829 * find the correct value in the displayed values for the current
1830 * number.
1831 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001832 String text = (mDisplayedValues == null) ? formatNumber(mValue)
1833 : mDisplayedValues[mValue - mMinValue];
1834 if (!TextUtils.isEmpty(text) && !text.equals(mInputText.getText().toString())) {
1835 mInputText.setText(text);
1836 return true;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001837 }
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -07001838
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001839 return false;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001840 }
1841
1842 /**
1843 * Notifies the listener, if registered, of a change of the value of this
1844 * NumberPicker.
1845 */
1846 private void notifyChange(int previous, int current) {
Svetoslav Ganovcedc4462011-01-19 19:25:46 -08001847 if (mOnValueChangeListener != null) {
1848 mOnValueChangeListener.onValueChange(this, previous, mValue);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001849 }
1850 }
1851
1852 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001853 * Posts a command for changing the current value by one.
1854 *
1855 * @param increment Whether to increment or decrement the value.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001856 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001857 private void postChangeCurrentByOneFromLongPress(boolean increment, long delayMillis) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001858 if (mChangeCurrentByOneFromLongPressCommand == null) {
1859 mChangeCurrentByOneFromLongPressCommand = new ChangeCurrentByOneFromLongPressCommand();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001860 } else {
1861 removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001862 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001863 mChangeCurrentByOneFromLongPressCommand.setStep(increment);
1864 postDelayed(mChangeCurrentByOneFromLongPressCommand, delayMillis);
1865 }
1866
1867 /**
1868 * Removes the command for changing the current value by one.
1869 */
1870 private void removeChangeCurrentByOneFromLongPress() {
1871 if (mChangeCurrentByOneFromLongPressCommand != null) {
1872 removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
1873 }
1874 }
1875
1876 /**
1877 * Posts a command for beginning an edit of the current value via IME on
1878 * long press.
1879 */
1880 private void postBeginSoftInputOnLongPressCommand() {
1881 if (mBeginSoftInputOnLongPressCommand == null) {
1882 mBeginSoftInputOnLongPressCommand = new BeginSoftInputOnLongPressCommand();
1883 } else {
1884 removeCallbacks(mBeginSoftInputOnLongPressCommand);
1885 }
1886 postDelayed(mBeginSoftInputOnLongPressCommand, ViewConfiguration.getLongPressTimeout());
1887 }
1888
1889 /**
1890 * Removes the command for beginning an edit of the current value via IME.
1891 */
1892 private void removeBeginSoftInputCommand() {
1893 if (mBeginSoftInputOnLongPressCommand != null) {
1894 removeCallbacks(mBeginSoftInputOnLongPressCommand);
1895 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001896 }
1897
1898 /**
1899 * Removes all pending callback from the message queue.
1900 */
1901 private void removeAllCallbacks() {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001902 if (mChangeCurrentByOneFromLongPressCommand != null) {
1903 removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001904 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001905 if (mSetSelectionCommand != null) {
1906 removeCallbacks(mSetSelectionCommand);
1907 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001908 if (mBeginSoftInputOnLongPressCommand != null) {
1909 removeCallbacks(mBeginSoftInputOnLongPressCommand);
1910 }
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001911 mPressedStateHelper.cancel();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001912 }
1913
1914 /**
1915 * @return The selected index given its displayed <code>value</code>.
1916 */
1917 private int getSelectedPos(String value) {
1918 if (mDisplayedValues == null) {
1919 try {
1920 return Integer.parseInt(value);
1921 } catch (NumberFormatException e) {
1922 // Ignore as if it's not a number we don't care
1923 }
1924 } else {
1925 for (int i = 0; i < mDisplayedValues.length; i++) {
1926 // Don't force the user to type in jan when ja will do
1927 value = value.toLowerCase();
1928 if (mDisplayedValues[i].toLowerCase().startsWith(value)) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001929 return mMinValue + i;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001930 }
1931 }
1932
1933 /*
1934 * The user might have typed in a number into the month field i.e.
1935 * 10 instead of OCT so support that too.
1936 */
1937 try {
1938 return Integer.parseInt(value);
1939 } catch (NumberFormatException e) {
1940
1941 // Ignore as if it's not a number we don't care
1942 }
1943 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001944 return mMinValue;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001945 }
1946
1947 /**
1948 * Posts an {@link SetSelectionCommand} from the given <code>selectionStart
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001949 * </code> to <code>selectionEnd</code>.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001950 */
1951 private void postSetSelectionCommand(int selectionStart, int selectionEnd) {
1952 if (mSetSelectionCommand == null) {
1953 mSetSelectionCommand = new SetSelectionCommand();
1954 } else {
1955 removeCallbacks(mSetSelectionCommand);
1956 }
1957 mSetSelectionCommand.mSelectionStart = selectionStart;
1958 mSetSelectionCommand.mSelectionEnd = selectionEnd;
1959 post(mSetSelectionCommand);
1960 }
1961
1962 /**
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07001963 * The numbers accepted by the input text's {@link Filter}
1964 */
1965 private static final char[] DIGIT_CHARACTERS = new char[] {
1966 // Latin digits are the common case
1967 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1968 // Arabic-Indic
1969 '\u0660', '\u0661', '\u0662', '\u0663', '\u0664', '\u0665', '\u0666', '\u0667', '\u0668'
1970 , '\u0669',
1971 // Extended Arabic-Indic
1972 '\u06f0', '\u06f1', '\u06f2', '\u06f3', '\u06f4', '\u06f5', '\u06f6', '\u06f7', '\u06f8'
1973 , '\u06f9'
1974 };
1975
1976 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001977 * Filter for accepting only valid indices or prefixes of the string
1978 * representation of valid indices.
1979 */
1980 class InputTextFilter extends NumberKeyListener {
1981
1982 // XXX This doesn't allow for range limits when controlled by a
1983 // soft input method!
1984 public int getInputType() {
1985 return InputType.TYPE_CLASS_TEXT;
1986 }
1987
1988 @Override
1989 protected char[] getAcceptedChars() {
1990 return DIGIT_CHARACTERS;
1991 }
1992
1993 @Override
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001994 public CharSequence filter(
1995 CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001996 if (mDisplayedValues == null) {
1997 CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
1998 if (filtered == null) {
1999 filtered = source.subSequence(start, end);
2000 }
2001
2002 String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
2003 + dest.subSequence(dend, dest.length());
2004
2005 if ("".equals(result)) {
2006 return result;
2007 }
2008 int val = getSelectedPos(result);
2009
2010 /*
2011 * Ensure the user can't type in a value greater than the max
2012 * allowed. We have to allow less than min as the user might
2013 * want to delete some numbers and then type a new number.
Sungmin Choi6d8a99f2013-01-25 18:26:46 +09002014 * And prevent multiple-"0" that exceeds the length of upper
2015 * bound number.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002016 */
Sungmin Choi6d8a99f2013-01-25 18:26:46 +09002017 if (val > mMaxValue || result.length() > String.valueOf(mMaxValue).length()) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002018 return "";
2019 } else {
2020 return filtered;
2021 }
2022 } else {
2023 CharSequence filtered = String.valueOf(source.subSequence(start, end));
2024 if (TextUtils.isEmpty(filtered)) {
2025 return "";
2026 }
2027 String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
2028 + dest.subSequence(dend, dest.length());
2029 String str = String.valueOf(result).toLowerCase();
2030 for (String val : mDisplayedValues) {
2031 String valLowerCase = val.toLowerCase();
2032 if (valLowerCase.startsWith(str)) {
2033 postSetSelectionCommand(result.length(), val.length());
2034 return val.subSequence(dstart, val.length());
2035 }
2036 }
2037 return "";
2038 }
2039 }
2040 }
2041
2042 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002043 * Ensures that the scroll wheel is adjusted i.e. there is no offset and the
2044 * middle element is in the middle of the widget.
2045 *
2046 * @return Whether an adjustment has been made.
2047 */
2048 private boolean ensureScrollWheelAdjusted() {
2049 // adjust to the closest value
2050 int deltaY = mInitialScrollOffset - mCurrentScrollOffset;
2051 if (deltaY != 0) {
2052 mPreviousScrollerY = 0;
2053 if (Math.abs(deltaY) > mSelectorElementHeight / 2) {
2054 deltaY += (deltaY > 0) ? -mSelectorElementHeight : mSelectorElementHeight;
2055 }
2056 mAdjustScroller.startScroll(0, 0, 0, deltaY, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
2057 invalidate();
2058 return true;
2059 }
2060 return false;
2061 }
2062
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07002063 class PressedStateHelper implements Runnable {
2064 public static final int BUTTON_INCREMENT = 1;
2065 public static final int BUTTON_DECREMENT = 2;
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -07002066
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07002067 private final int MODE_PRESS = 1;
2068 private final int MODE_TAPPED = 2;
2069
2070 private int mManagedButton;
2071 private int mMode;
2072
2073 public void cancel() {
2074 mMode = 0;
2075 mManagedButton = 0;
2076 NumberPicker.this.removeCallbacks(this);
2077 if (mIncrementVirtualButtonPressed) {
2078 mIncrementVirtualButtonPressed = false;
2079 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -07002080 }
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07002081 mDecrementVirtualButtonPressed = false;
2082 if (mDecrementVirtualButtonPressed) {
2083 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2084 }
2085 }
2086
2087 public void buttonPressDelayed(int button) {
2088 cancel();
2089 mMode = MODE_PRESS;
2090 mManagedButton = button;
2091 NumberPicker.this.postDelayed(this, ViewConfiguration.getTapTimeout());
2092 }
2093
2094 public void buttonTapped(int button) {
2095 cancel();
2096 mMode = MODE_TAPPED;
2097 mManagedButton = button;
2098 NumberPicker.this.post(this);
2099 }
2100
2101 @Override
2102 public void run() {
2103 switch (mMode) {
2104 case MODE_PRESS: {
2105 switch (mManagedButton) {
2106 case BUTTON_INCREMENT: {
2107 mIncrementVirtualButtonPressed = true;
2108 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
2109 } break;
2110 case BUTTON_DECREMENT: {
2111 mDecrementVirtualButtonPressed = true;
2112 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2113 }
2114 }
2115 } break;
2116 case MODE_TAPPED: {
2117 switch (mManagedButton) {
2118 case BUTTON_INCREMENT: {
2119 if (!mIncrementVirtualButtonPressed) {
2120 NumberPicker.this.postDelayed(this,
2121 ViewConfiguration.getPressedStateDuration());
2122 }
2123 mIncrementVirtualButtonPressed ^= true;
2124 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
2125 } break;
2126 case BUTTON_DECREMENT: {
2127 if (!mDecrementVirtualButtonPressed) {
2128 NumberPicker.this.postDelayed(this,
2129 ViewConfiguration.getPressedStateDuration());
2130 }
2131 mDecrementVirtualButtonPressed ^= true;
2132 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2133 }
2134 }
2135 } break;
2136 }
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -07002137 }
2138 }
2139
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002140 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002141 * Command for setting the input text selection.
2142 */
2143 class SetSelectionCommand implements Runnable {
2144 private int mSelectionStart;
2145
2146 private int mSelectionEnd;
2147
2148 public void run() {
2149 mInputText.setSelection(mSelectionStart, mSelectionEnd);
2150 }
2151 }
2152
2153 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07002154 * Command for changing the current value from a long press by one.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002155 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07002156 class ChangeCurrentByOneFromLongPressCommand implements Runnable {
2157 private boolean mIncrement;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002158
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002159 private void setStep(boolean increment) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07002160 mIncrement = increment;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002161 }
2162
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002163 @Override
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002164 public void run() {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002165 changeValueByOne(mIncrement);
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08002166 postDelayed(this, mLongPressUpdateInterval);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002167 }
2168 }
Svetoslav Ganova2b41b42012-02-27 15:53:32 -08002169
2170 /**
2171 * @hide
2172 */
2173 public static class CustomEditText extends EditText {
2174
2175 public CustomEditText(Context context, AttributeSet attrs) {
2176 super(context, attrs);
2177 }
2178
2179 @Override
2180 public void onEditorAction(int actionCode) {
2181 super.onEditorAction(actionCode);
2182 if (actionCode == EditorInfo.IME_ACTION_DONE) {
2183 clearFocus();
2184 }
2185 }
2186 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002187
2188 /**
2189 * Command for beginning soft input on long press.
2190 */
2191 class BeginSoftInputOnLongPressCommand implements Runnable {
2192
2193 @Override
2194 public void run() {
2195 showSoftInput();
2196 mIngonreMoveEvents = true;
2197 }
2198 }
2199
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002200 /**
2201 * Class for managing virtual view tree rooted at this picker.
2202 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002203 class AccessibilityNodeProviderImpl extends AccessibilityNodeProvider {
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002204 private static final int UNDEFINED = Integer.MIN_VALUE;
2205
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002206 private static final int VIRTUAL_VIEW_ID_INCREMENT = 1;
2207
2208 private static final int VIRTUAL_VIEW_ID_INPUT = 2;
2209
2210 private static final int VIRTUAL_VIEW_ID_DECREMENT = 3;
2211
2212 private final Rect mTempRect = new Rect();
2213
2214 private final int[] mTempArray = new int[2];
2215
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002216 private int mAccessibilityFocusedView = UNDEFINED;
2217
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002218 @Override
2219 public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
2220 switch (virtualViewId) {
2221 case View.NO_ID:
2222 return createAccessibilityNodeInfoForNumberPicker( mScrollX, mScrollY,
2223 mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop));
2224 case VIRTUAL_VIEW_ID_DECREMENT:
2225 return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_DECREMENT,
2226 getVirtualDecrementButtonText(), mScrollX, mScrollY,
2227 mScrollX + (mRight - mLeft),
2228 mTopSelectionDividerTop + mSelectionDividerHeight);
2229 case VIRTUAL_VIEW_ID_INPUT:
Alan Viverette0e2d2812013-05-21 17:15:30 -07002230 return createAccessibiltyNodeInfoForInputText(mScrollX,
2231 mTopSelectionDividerTop + mSelectionDividerHeight,
2232 mScrollX + (mRight - mLeft),
2233 mBottomSelectionDividerBottom - mSelectionDividerHeight);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002234 case VIRTUAL_VIEW_ID_INCREMENT:
2235 return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_INCREMENT,
2236 getVirtualIncrementButtonText(), mScrollX,
2237 mBottomSelectionDividerBottom - mSelectionDividerHeight,
2238 mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop));
2239 }
2240 return super.createAccessibilityNodeInfo(virtualViewId);
2241 }
2242
2243 @Override
2244 public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String searched,
2245 int virtualViewId) {
2246 if (TextUtils.isEmpty(searched)) {
2247 return Collections.emptyList();
2248 }
2249 String searchedLowerCase = searched.toLowerCase();
2250 List<AccessibilityNodeInfo> result = new ArrayList<AccessibilityNodeInfo>();
2251 switch (virtualViewId) {
2252 case View.NO_ID: {
2253 findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
2254 VIRTUAL_VIEW_ID_DECREMENT, result);
2255 findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
2256 VIRTUAL_VIEW_ID_INPUT, result);
2257 findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
2258 VIRTUAL_VIEW_ID_INCREMENT, result);
2259 return result;
2260 }
2261 case VIRTUAL_VIEW_ID_DECREMENT:
2262 case VIRTUAL_VIEW_ID_INCREMENT:
2263 case VIRTUAL_VIEW_ID_INPUT: {
2264 findAccessibilityNodeInfosByTextInChild(searchedLowerCase, virtualViewId,
2265 result);
2266 return result;
2267 }
2268 }
2269 return super.findAccessibilityNodeInfosByText(searched, virtualViewId);
2270 }
2271
2272 @Override
Svetoslav Ganovaa780c12012-04-19 23:01:39 -07002273 public boolean performAction(int virtualViewId, int action, Bundle arguments) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002274 switch (virtualViewId) {
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002275 case View.NO_ID: {
2276 switch (action) {
2277 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
2278 if (mAccessibilityFocusedView != virtualViewId) {
2279 mAccessibilityFocusedView = virtualViewId;
2280 requestAccessibilityFocus();
2281 return true;
2282 }
2283 } return false;
2284 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
2285 if (mAccessibilityFocusedView == virtualViewId) {
2286 mAccessibilityFocusedView = UNDEFINED;
2287 clearAccessibilityFocus();
2288 return true;
2289 }
2290 return false;
2291 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07002292 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002293 if (NumberPicker.this.isEnabled()
2294 && (getWrapSelectorWheel() || getValue() < getMaxValue())) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07002295 changeValueByOne(true);
2296 return true;
2297 }
2298 } return false;
2299 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002300 if (NumberPicker.this.isEnabled()
2301 && (getWrapSelectorWheel() || getValue() > getMinValue())) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07002302 changeValueByOne(false);
2303 return true;
2304 }
2305 } return false;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002306 }
2307 } break;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002308 case VIRTUAL_VIEW_ID_INPUT: {
2309 switch (action) {
2310 case AccessibilityNodeInfo.ACTION_FOCUS: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002311 if (NumberPicker.this.isEnabled() && !mInputText.isFocused()) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002312 return mInputText.requestFocus();
2313 }
2314 } break;
2315 case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002316 if (NumberPicker.this.isEnabled() && mInputText.isFocused()) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002317 mInputText.clearFocus();
2318 return true;
2319 }
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002320 return false;
2321 }
2322 case AccessibilityNodeInfo.ACTION_CLICK: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002323 if (NumberPicker.this.isEnabled()) {
2324 showSoftInput();
2325 return true;
2326 }
2327 return false;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002328 }
2329 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
2330 if (mAccessibilityFocusedView != virtualViewId) {
2331 mAccessibilityFocusedView = virtualViewId;
2332 sendAccessibilityEventForVirtualView(virtualViewId,
2333 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
2334 mInputText.invalidate();
2335 return true;
2336 }
2337 } return false;
2338 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
2339 if (mAccessibilityFocusedView == virtualViewId) {
2340 mAccessibilityFocusedView = UNDEFINED;
2341 sendAccessibilityEventForVirtualView(virtualViewId,
2342 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
2343 mInputText.invalidate();
2344 return true;
2345 }
2346 } return false;
2347 default: {
2348 return mInputText.performAccessibilityAction(action, arguments);
2349 }
2350 }
2351 } return false;
2352 case VIRTUAL_VIEW_ID_INCREMENT: {
2353 switch (action) {
2354 case AccessibilityNodeInfo.ACTION_CLICK: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002355 if (NumberPicker.this.isEnabled()) {
2356 NumberPicker.this.changeValueByOne(true);
2357 sendAccessibilityEventForVirtualView(virtualViewId,
2358 AccessibilityEvent.TYPE_VIEW_CLICKED);
2359 return true;
2360 }
2361 } return false;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002362 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
2363 if (mAccessibilityFocusedView != virtualViewId) {
2364 mAccessibilityFocusedView = virtualViewId;
2365 sendAccessibilityEventForVirtualView(virtualViewId,
2366 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
2367 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
2368 return true;
2369 }
2370 } return false;
2371 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
2372 if (mAccessibilityFocusedView == virtualViewId) {
2373 mAccessibilityFocusedView = UNDEFINED;
2374 sendAccessibilityEventForVirtualView(virtualViewId,
2375 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
2376 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
2377 return true;
2378 }
2379 } return false;
2380 }
2381 } return false;
2382 case VIRTUAL_VIEW_ID_DECREMENT: {
2383 switch (action) {
2384 case AccessibilityNodeInfo.ACTION_CLICK: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002385 if (NumberPicker.this.isEnabled()) {
2386 final boolean increment = (virtualViewId == VIRTUAL_VIEW_ID_INCREMENT);
2387 NumberPicker.this.changeValueByOne(increment);
2388 sendAccessibilityEventForVirtualView(virtualViewId,
2389 AccessibilityEvent.TYPE_VIEW_CLICKED);
2390 return true;
2391 }
2392 } return false;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002393 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
2394 if (mAccessibilityFocusedView != virtualViewId) {
2395 mAccessibilityFocusedView = virtualViewId;
2396 sendAccessibilityEventForVirtualView(virtualViewId,
2397 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
2398 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2399 return true;
2400 }
2401 } return false;
2402 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
2403 if (mAccessibilityFocusedView == virtualViewId) {
2404 mAccessibilityFocusedView = UNDEFINED;
2405 sendAccessibilityEventForVirtualView(virtualViewId,
2406 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
2407 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2408 return true;
2409 }
2410 } return false;
2411 }
2412 } return false;
2413 }
2414 return super.performAction(virtualViewId, action, arguments);
2415 }
2416
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002417 public void sendAccessibilityEventForVirtualView(int virtualViewId, int eventType) {
2418 switch (virtualViewId) {
2419 case VIRTUAL_VIEW_ID_DECREMENT: {
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002420 if (hasVirtualDecrementButton()) {
2421 sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
2422 getVirtualDecrementButtonText());
2423 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002424 } break;
2425 case VIRTUAL_VIEW_ID_INPUT: {
2426 sendAccessibilityEventForVirtualText(eventType);
2427 } break;
2428 case VIRTUAL_VIEW_ID_INCREMENT: {
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002429 if (hasVirtualIncrementButton()) {
2430 sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
2431 getVirtualIncrementButtonText());
2432 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002433 } break;
2434 }
2435 }
2436
2437 private void sendAccessibilityEventForVirtualText(int eventType) {
Svetoslav Ganova9092762012-09-06 19:57:00 -07002438 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2439 AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
2440 mInputText.onInitializeAccessibilityEvent(event);
2441 mInputText.onPopulateAccessibilityEvent(event);
2442 event.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
2443 requestSendAccessibilityEvent(NumberPicker.this, event);
2444 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002445 }
2446
2447 private void sendAccessibilityEventForVirtualButton(int virtualViewId, int eventType,
2448 String text) {
Svetoslav Ganova9092762012-09-06 19:57:00 -07002449 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2450 AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
2451 event.setClassName(Button.class.getName());
2452 event.setPackageName(mContext.getPackageName());
2453 event.getText().add(text);
2454 event.setEnabled(NumberPicker.this.isEnabled());
2455 event.setSource(NumberPicker.this, virtualViewId);
2456 requestSendAccessibilityEvent(NumberPicker.this, event);
2457 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002458 }
2459
2460 private void findAccessibilityNodeInfosByTextInChild(String searchedLowerCase,
2461 int virtualViewId, List<AccessibilityNodeInfo> outResult) {
2462 switch (virtualViewId) {
2463 case VIRTUAL_VIEW_ID_DECREMENT: {
2464 String text = getVirtualDecrementButtonText();
2465 if (!TextUtils.isEmpty(text)
2466 && text.toString().toLowerCase().contains(searchedLowerCase)) {
2467 outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT));
2468 }
2469 } return;
2470 case VIRTUAL_VIEW_ID_INPUT: {
2471 CharSequence text = mInputText.getText();
2472 if (!TextUtils.isEmpty(text) &&
2473 text.toString().toLowerCase().contains(searchedLowerCase)) {
2474 outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT));
2475 return;
2476 }
2477 CharSequence contentDesc = mInputText.getText();
2478 if (!TextUtils.isEmpty(contentDesc) &&
2479 contentDesc.toString().toLowerCase().contains(searchedLowerCase)) {
2480 outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT));
2481 return;
2482 }
2483 } break;
2484 case VIRTUAL_VIEW_ID_INCREMENT: {
2485 String text = getVirtualIncrementButtonText();
2486 if (!TextUtils.isEmpty(text)
2487 && text.toString().toLowerCase().contains(searchedLowerCase)) {
2488 outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT));
2489 }
2490 } return;
2491 }
2492 }
2493
Alan Viverette0e2d2812013-05-21 17:15:30 -07002494 private AccessibilityNodeInfo createAccessibiltyNodeInfoForInputText(
2495 int left, int top, int right, int bottom) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002496 AccessibilityNodeInfo info = mInputText.createAccessibilityNodeInfo();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002497 info.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002498 if (mAccessibilityFocusedView != VIRTUAL_VIEW_ID_INPUT) {
2499 info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
2500 }
2501 if (mAccessibilityFocusedView == VIRTUAL_VIEW_ID_INPUT) {
2502 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
2503 }
Alan Viverette0e2d2812013-05-21 17:15:30 -07002504 Rect boundsInParent = mTempRect;
2505 boundsInParent.set(left, top, right, bottom);
2506 info.setVisibleToUser(isVisibleToUser(boundsInParent));
2507 info.setBoundsInParent(boundsInParent);
2508 Rect boundsInScreen = boundsInParent;
2509 int[] locationOnScreen = mTempArray;
2510 getLocationOnScreen(locationOnScreen);
2511 boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
2512 info.setBoundsInScreen(boundsInScreen);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002513 return info;
2514 }
2515
2516 private AccessibilityNodeInfo createAccessibilityNodeInfoForVirtualButton(int virtualViewId,
2517 String text, int left, int top, int right, int bottom) {
2518 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
2519 info.setClassName(Button.class.getName());
2520 info.setPackageName(mContext.getPackageName());
2521 info.setSource(NumberPicker.this, virtualViewId);
2522 info.setParent(NumberPicker.this);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002523 info.setText(text);
2524 info.setClickable(true);
2525 info.setLongClickable(true);
2526 info.setEnabled(NumberPicker.this.isEnabled());
2527 Rect boundsInParent = mTempRect;
2528 boundsInParent.set(left, top, right, bottom);
Guang Zhu0d607fb2012-05-11 19:34:56 -07002529 info.setVisibleToUser(isVisibleToUser(boundsInParent));
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002530 info.setBoundsInParent(boundsInParent);
2531 Rect boundsInScreen = boundsInParent;
2532 int[] locationOnScreen = mTempArray;
2533 getLocationOnScreen(locationOnScreen);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002534 boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
2535 info.setBoundsInScreen(boundsInScreen);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002536
2537 if (mAccessibilityFocusedView != virtualViewId) {
2538 info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
2539 }
2540 if (mAccessibilityFocusedView == virtualViewId) {
2541 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
2542 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002543 if (NumberPicker.this.isEnabled()) {
2544 info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
2545 }
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002546
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002547 return info;
2548 }
2549
2550 private AccessibilityNodeInfo createAccessibilityNodeInfoForNumberPicker(int left, int top,
2551 int right, int bottom) {
2552 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
Guang Zhu0d607fb2012-05-11 19:34:56 -07002553 info.setClassName(NumberPicker.class.getName());
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002554 info.setPackageName(mContext.getPackageName());
2555 info.setSource(NumberPicker.this);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002556
2557 if (hasVirtualDecrementButton()) {
2558 info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_DECREMENT);
2559 }
Guang Zhu0d607fb2012-05-11 19:34:56 -07002560 info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002561 if (hasVirtualIncrementButton()) {
2562 info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INCREMENT);
2563 }
2564
Svetoslav Ganov4528b4e2012-05-15 18:24:10 -07002565 info.setParent((View) getParentForAccessibility());
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002566 info.setEnabled(NumberPicker.this.isEnabled());
2567 info.setScrollable(true);
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002568
2569 final float applicationScale =
2570 getContext().getResources().getCompatibilityInfo().applicationScale;
2571
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002572 Rect boundsInParent = mTempRect;
2573 boundsInParent.set(left, top, right, bottom);
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002574 boundsInParent.scale(applicationScale);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002575 info.setBoundsInParent(boundsInParent);
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002576
Guang Zhu0d607fb2012-05-11 19:34:56 -07002577 info.setVisibleToUser(isVisibleToUser());
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002578
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002579 Rect boundsInScreen = boundsInParent;
2580 int[] locationOnScreen = mTempArray;
2581 getLocationOnScreen(locationOnScreen);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002582 boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002583 boundsInScreen.scale(applicationScale);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002584 info.setBoundsInScreen(boundsInScreen);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002585
2586 if (mAccessibilityFocusedView != View.NO_ID) {
2587 info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
2588 }
2589 if (mAccessibilityFocusedView == View.NO_ID) {
2590 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
2591 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002592 if (NumberPicker.this.isEnabled()) {
2593 if (getWrapSelectorWheel() || getValue() < getMaxValue()) {
2594 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
2595 }
2596 if (getWrapSelectorWheel() || getValue() > getMinValue()) {
2597 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
2598 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07002599 }
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002600
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002601 return info;
2602 }
2603
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002604 private boolean hasVirtualDecrementButton() {
2605 return getWrapSelectorWheel() || getValue() > getMinValue();
2606 }
2607
2608 private boolean hasVirtualIncrementButton() {
2609 return getWrapSelectorWheel() || getValue() < getMaxValue();
2610 }
2611
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002612 private String getVirtualDecrementButtonText() {
2613 int value = mValue - 1;
2614 if (mWrapSelectorWheel) {
2615 value = getWrappedSelectorIndex(value);
2616 }
2617 if (value >= mMinValue) {
2618 return (mDisplayedValues == null) ? formatNumber(value)
2619 : mDisplayedValues[value - mMinValue];
2620 }
2621 return null;
2622 }
2623
2624 private String getVirtualIncrementButtonText() {
2625 int value = mValue + 1;
2626 if (mWrapSelectorWheel) {
2627 value = getWrappedSelectorIndex(value);
2628 }
2629 if (value <= mMaxValue) {
2630 return (mDisplayedValues == null) ? formatNumber(value)
2631 : mDisplayedValues[value - mMinValue];
2632 }
2633 return null;
2634 }
2635 }
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07002636
2637 static private String formatNumberWithLocale(int value) {
2638 return String.format(Locale.getDefault(), "%d", value);
2639 }
Paul Westbrook7762d932009-12-11 14:13:48 -08002640}