blob: ee17b782ad3f93a3ad17948dc39584ba7b0badde [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/**
Brian Duff0db4c3e2013-09-08 14:11:28 -070065 * A widget that enables the user to select a number from 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 Ganovfe41ce42012-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 */
Svetoslav46a27ef2014-03-03 15:37:14 -0800433 private boolean mIgnoreMoveEvents;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800434
435 /**
Svetoslav46a27ef2014-03-03 15:37:14 -0800436 * Flag whether to perform a click on tap.
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700437 */
Svetoslav46a27ef2014-03-03 15:37:14 -0800438 private boolean mPerformClickOnTap;
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 /**
Craig Stout9eef3f42014-06-23 14:13:43 -0700481 * If true then the selector wheel is hidden until the picker has focus.
482 */
483 private boolean mHideWheelUntilFocused;
484
485 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800486 * Interface to listen for changes of the current value.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800487 */
Svetoslav Ganovcedc4462011-01-19 19:25:46 -0800488 public interface OnValueChangeListener {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800489
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800490 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800491 * Called upon a change of the current value.
492 *
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800493 * @param picker The NumberPicker associated with this listener.
494 * @param oldVal The previous value.
495 * @param newVal The new value.
496 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800497 void onValueChange(NumberPicker picker, int oldVal, int newVal);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800498 }
499
500 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800501 * Interface to listen for the picker scroll state.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800502 */
503 public interface OnScrollListener {
Tor Norbyed9273d62013-05-30 15:59:53 -0700504 /** @hide */
505 @IntDef({SCROLL_STATE_IDLE, SCROLL_STATE_TOUCH_SCROLL, SCROLL_STATE_FLING})
506 @Retention(RetentionPolicy.SOURCE)
507 public @interface ScrollState {}
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800508
509 /**
510 * The view is not scrolling.
511 */
512 public static int SCROLL_STATE_IDLE = 0;
513
514 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700515 * The user is scrolling using touch, and his finger is still on the screen.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800516 */
517 public static int SCROLL_STATE_TOUCH_SCROLL = 1;
518
519 /**
520 * The user had previously been scrolling using touch and performed a fling.
521 */
522 public static int SCROLL_STATE_FLING = 2;
523
524 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800525 * Callback invoked while the number picker scroll state has changed.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800526 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800527 * @param view The view whose scroll state is being reported.
528 * @param scrollState The current scroll state. One of
529 * {@link #SCROLL_STATE_IDLE},
530 * {@link #SCROLL_STATE_TOUCH_SCROLL} or
531 * {@link #SCROLL_STATE_IDLE}.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800532 */
Tor Norbyed9273d62013-05-30 15:59:53 -0700533 public void onScrollStateChange(NumberPicker view, @ScrollState int scrollState);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800534 }
535
536 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800537 * Interface used to format current value into a string for presentation.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800538 */
539 public interface Formatter {
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800540
541 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800542 * Formats a string representation of the current value.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800543 *
544 * @param value The currently selected value.
545 * @return A formatted string representation.
546 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800547 public String format(int value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800548 }
Tom Taylorfdf6db62009-09-09 11:37:58 -0700549
Paul Westbrook68f2f542010-01-13 12:13:57 -0800550 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800551 * Create a new number picker.
552 *
553 * @param context The application environment.
554 */
555 public NumberPicker(Context context) {
556 this(context, null);
557 }
558
559 /**
560 * Create a new number picker.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800561 *
562 * @param context The application environment.
563 * @param attrs A collection of attributes.
Paul Westbrook68f2f542010-01-13 12:13:57 -0800564 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800565 public NumberPicker(Context context, AttributeSet attrs) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800566 this(context, attrs, R.attr.numberPickerStyle);
567 }
568
569 /**
570 * Create a new number picker
571 *
572 * @param context the application environment.
573 * @param attrs a collection of attributes.
Alan Viverette617feb92013-09-09 18:09:13 -0700574 * @param defStyleAttr An attribute in the current theme that contains a
575 * reference to a style resource that supplies default values for
576 * the view. Can be 0 to not look for defaults.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800577 */
Alan Viverette617feb92013-09-09 18:09:13 -0700578 public NumberPicker(Context context, AttributeSet attrs, int defStyleAttr) {
579 this(context, attrs, defStyleAttr, 0);
580 }
581
582 /**
583 * Create a new number picker
584 *
585 * @param context the application environment.
586 * @param attrs a collection of attributes.
587 * @param defStyleAttr An attribute in the current theme that contains a
588 * reference to a style resource that supplies default values for
589 * the view. Can be 0 to not look for defaults.
590 * @param defStyleRes A resource identifier of a style resource that
591 * supplies default values for the view, used only if
592 * defStyleAttr is 0 or can not be found in the theme. Can be 0
593 * to not look for defaults.
594 */
595 public NumberPicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
596 super(context, attrs, defStyleAttr, defStyleRes);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800597
598 // process style attributes
Alan Viverette617feb92013-09-09 18:09:13 -0700599 final TypedArray attributesArray = context.obtainStyledAttributes(
600 attrs, R.styleable.NumberPicker, defStyleAttr, defStyleRes);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700601 final int layoutResId = attributesArray.getResourceId(
602 R.styleable.NumberPicker_internalLayout, DEFAULT_LAYOUT_RESOURCE_ID);
603
604 mHasSelectorWheel = (layoutResId != DEFAULT_LAYOUT_RESOURCE_ID);
605
Craig Stout9eef3f42014-06-23 14:13:43 -0700606 mHideWheelUntilFocused = attributesArray.getBoolean(
607 R.styleable.NumberPicker_hideWheelUntilFocused, false);
608
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800609 mSolidColor = attributesArray.getColor(R.styleable.NumberPicker_solidColor, 0);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700610
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800611 mSelectionDivider = attributesArray.getDrawable(R.styleable.NumberPicker_selectionDivider);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700612
613 final int defSelectionDividerHeight = (int) TypedValue.applyDimension(
614 TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT,
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800615 getResources().getDisplayMetrics());
616 mSelectionDividerHeight = attributesArray.getDimensionPixelSize(
617 R.styleable.NumberPicker_selectionDividerHeight, defSelectionDividerHeight);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700618
619 final int defSelectionDividerDistance = (int) TypedValue.applyDimension(
620 TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE,
621 getResources().getDisplayMetrics());
622 mSelectionDividersDistance = attributesArray.getDimensionPixelSize(
623 R.styleable.NumberPicker_selectionDividersDistance, defSelectionDividerDistance);
624
Svetoslav Ganove8331bd2012-03-02 15:15:35 -0800625 mMinHeight = attributesArray.getDimensionPixelSize(
626 R.styleable.NumberPicker_internalMinHeight, SIZE_UNSPECIFIED);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700627
Svetoslav Ganove8331bd2012-03-02 15:15:35 -0800628 mMaxHeight = attributesArray.getDimensionPixelSize(
629 R.styleable.NumberPicker_internalMaxHeight, SIZE_UNSPECIFIED);
Svetoslav Ganov9f086d82011-11-29 18:27:23 -0800630 if (mMinHeight != SIZE_UNSPECIFIED && mMaxHeight != SIZE_UNSPECIFIED
631 && mMinHeight > mMaxHeight) {
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700632 throw new IllegalArgumentException("minHeight > maxHeight");
633 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700634
635 mMinWidth = attributesArray.getDimensionPixelSize(
636 R.styleable.NumberPicker_internalMinWidth, SIZE_UNSPECIFIED);
637
638 mMaxWidth = attributesArray.getDimensionPixelSize(
639 R.styleable.NumberPicker_internalMaxWidth, SIZE_UNSPECIFIED);
Svetoslav Ganov9f086d82011-11-29 18:27:23 -0800640 if (mMinWidth != SIZE_UNSPECIFIED && mMaxWidth != SIZE_UNSPECIFIED
641 && mMinWidth > mMaxWidth) {
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700642 throw new IllegalArgumentException("minWidth > maxWidth");
643 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800644
Svetoslav Ganovf7c83bc2012-04-12 16:00:13 -0700645 mComputeMaxWidth = (mMaxWidth == SIZE_UNSPECIFIED);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700646
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700647 mVirtualButtonPressedDrawable = attributesArray.getDrawable(
648 R.styleable.NumberPicker_virtualButtonPressedDrawable);
649
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700650 attributesArray.recycle();
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800651
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700652 mPressedStateHelper = new PressedStateHelper();
653
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800654 // By default Linearlayout that we extend is not drawn. This is
655 // its draw() method is not called but dispatchDraw() is called
656 // directly (see ViewGroup.drawChild()). However, this class uses
657 // the fading edge effect implemented by View and we need our
658 // draw() method to be called. Therefore, we declare we will draw.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700659 setWillNotDraw(!mHasSelectorWheel);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800660
661 LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
662 Context.LAYOUT_INFLATER_SERVICE);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700663 inflater.inflate(layoutResId, this, true);
Paul Westbrook68f2f542010-01-13 12:13:57 -0800664
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800665 OnClickListener onClickListener = new OnClickListener() {
Paul Westbrook68f2f542010-01-13 12:13:57 -0800666 public void onClick(View v) {
Svetoslav Ganovb52d9722011-11-07 14:53:34 -0800667 hideSoftInput();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800668 mInputText.clearFocus();
669 if (v.getId() == R.id.increment) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700670 changeValueByOne(true);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800671 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700672 changeValueByOne(false);
Paul Westbrook68f2f542010-01-13 12:13:57 -0800673 }
674 }
675 };
676
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800677 OnLongClickListener onLongClickListener = new OnLongClickListener() {
Paul Westbrook68f2f542010-01-13 12:13:57 -0800678 public boolean onLongClick(View v) {
Svetoslav Ganovb52d9722011-11-07 14:53:34 -0800679 hideSoftInput();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800680 mInputText.clearFocus();
681 if (v.getId() == R.id.increment) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700682 postChangeCurrentByOneFromLongPress(true, 0);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800683 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700684 postChangeCurrentByOneFromLongPress(false, 0);
Paul Westbrook68f2f542010-01-13 12:13:57 -0800685 }
686 return true;
687 }
688 };
689
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800690 // increment button
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700691 if (!mHasSelectorWheel) {
692 mIncrementButton = (ImageButton) findViewById(R.id.increment);
693 mIncrementButton.setOnClickListener(onClickListener);
694 mIncrementButton.setOnLongClickListener(onLongClickListener);
695 } else {
696 mIncrementButton = null;
697 }
Paul Westbrook68f2f542010-01-13 12:13:57 -0800698
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800699 // decrement button
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700700 if (!mHasSelectorWheel) {
701 mDecrementButton = (ImageButton) findViewById(R.id.decrement);
702 mDecrementButton.setOnClickListener(onClickListener);
703 mDecrementButton.setOnLongClickListener(onLongClickListener);
704 } else {
705 mDecrementButton = null;
706 }
Tom Taylorfdf6db62009-09-09 11:37:58 -0700707
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800708 // input text
Svetoslav Ganov012dd5a2011-01-11 16:54:01 -0800709 mInputText = (EditText) findViewById(R.id.numberpicker_input);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800710 mInputText.setOnFocusChangeListener(new OnFocusChangeListener() {
711 public void onFocusChange(View v, boolean hasFocus) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700712 if (hasFocus) {
713 mInputText.selectAll();
714 } else {
715 mInputText.setSelection(0, 0);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800716 validateInputTextView(v);
717 }
718 }
719 });
720 mInputText.setFilters(new InputFilter[] {
721 new InputTextFilter()
722 });
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800723
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800724 mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
Svetoslav Ganova2b41b42012-02-27 15:53:32 -0800725 mInputText.setImeOptions(EditorInfo.IME_ACTION_DONE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800726
727 // initialize constants
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800728 ViewConfiguration configuration = ViewConfiguration.get(context);
729 mTouchSlop = configuration.getScaledTouchSlop();
730 mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
731 mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity()
732 / SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT;
733 mTextSize = (int) mInputText.getTextSize();
734
735 // create the selector wheel paint
736 Paint paint = new Paint();
737 paint.setAntiAlias(true);
738 paint.setTextAlign(Align.CENTER);
739 paint.setTextSize(mTextSize);
740 paint.setTypeface(mInputText.getTypeface());
741 ColorStateList colors = mInputText.getTextColors();
742 int color = colors.getColorForState(ENABLED_STATE_SET, Color.WHITE);
743 paint.setColor(color);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700744 mSelectorWheelPaint = paint;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800745
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800746 // create the fling and adjust scrollers
Svetoslav Ganovbf805622010-12-17 16:00:18 -0800747 mFlingScroller = new Scroller(getContext(), null, true);
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800748 mAdjustScroller = new Scroller(getContext(), new DecelerateInterpolator(2.5f));
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800749
750 updateInputTextView();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700751
752 // If not explicitly specified this view is important for accessibility.
753 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
754 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
755 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800756 }
757
758 @Override
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800759 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700760 if (!mHasSelectorWheel) {
761 super.onLayout(changed, left, top, right, bottom);
762 return;
763 }
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700764 final int msrdWdth = getMeasuredWidth();
765 final int msrdHght = getMeasuredHeight();
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700766
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700767 // Input text centered horizontally.
768 final int inptTxtMsrdWdth = mInputText.getMeasuredWidth();
769 final int inptTxtMsrdHght = mInputText.getMeasuredHeight();
770 final int inptTxtLeft = (msrdWdth - inptTxtMsrdWdth) / 2;
771 final int inptTxtTop = (msrdHght - inptTxtMsrdHght) / 2;
772 final int inptTxtRight = inptTxtLeft + inptTxtMsrdWdth;
773 final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght;
774 mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700775
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700776 if (changed) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700777 // need to do all this when we know our size
778 initializeSelectorWheel();
779 initializeFadingEdges();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700780 mTopSelectionDividerTop = (getHeight() - mSelectionDividersDistance) / 2
781 - mSelectionDividerHeight;
782 mBottomSelectionDividerBottom = mTopSelectionDividerTop + 2 * mSelectionDividerHeight
783 + mSelectionDividersDistance;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700784 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800785 }
786
787 @Override
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700788 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700789 if (!mHasSelectorWheel) {
790 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
791 return;
792 }
Svetoslav Ganov698e1d52011-11-07 18:43:01 -0800793 // Try greedily to fit the max width and height.
794 final int newWidthMeasureSpec = makeMeasureSpec(widthMeasureSpec, mMaxWidth);
795 final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMaxHeight);
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700796 super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec);
Svetoslav Ganov698e1d52011-11-07 18:43:01 -0800797 // Flag if we are measured with width or height less than the respective min.
Svetoslav Ganov9f086d82011-11-29 18:27:23 -0800798 final int widthSize = resolveSizeAndStateRespectingMinSize(mMinWidth, getMeasuredWidth(),
799 widthMeasureSpec);
800 final int heightSize = resolveSizeAndStateRespectingMinSize(mMinHeight, getMeasuredHeight(),
801 heightMeasureSpec);
Svetoslav Ganov698e1d52011-11-07 18:43:01 -0800802 setMeasuredDimension(widthSize, heightSize);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700803 }
804
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700805 /**
806 * Move to the final position of a scroller. Ensures to force finish the scroller
807 * and if it is not at its final position a scroll of the selector wheel is
808 * performed to fast forward to the final position.
809 *
810 * @param scroller The scroller to whose final position to get.
811 * @return True of the a move was performed, i.e. the scroller was not in final position.
812 */
813 private boolean moveToFinalScrollerPosition(Scroller scroller) {
814 scroller.forceFinished(true);
815 int amountToScroll = scroller.getFinalY() - scroller.getCurrY();
816 int futureScrollOffset = (mCurrentScrollOffset + amountToScroll) % mSelectorElementHeight;
817 int overshootAdjustment = mInitialScrollOffset - futureScrollOffset;
818 if (overshootAdjustment != 0) {
819 if (Math.abs(overshootAdjustment) > mSelectorElementHeight / 2) {
820 if (overshootAdjustment > 0) {
821 overshootAdjustment -= mSelectorElementHeight;
822 } else {
823 overshootAdjustment += mSelectorElementHeight;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800824 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700825 }
826 amountToScroll += overshootAdjustment;
827 scrollBy(0, amountToScroll);
828 return true;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800829 }
830 return false;
831 }
832
833 @Override
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700834 public boolean onInterceptTouchEvent(MotionEvent event) {
835 if (!mHasSelectorWheel || !isEnabled()) {
836 return false;
837 }
838 final int action = event.getActionMasked();
839 switch (action) {
840 case MotionEvent.ACTION_DOWN: {
841 removeAllCallbacks();
842 mInputText.setVisibility(View.INVISIBLE);
843 mLastDownOrMoveEventY = mLastDownEventY = event.getY();
844 mLastDownEventTime = event.getEventTime();
Svetoslav46a27ef2014-03-03 15:37:14 -0800845 mIgnoreMoveEvents = false;
846 mPerformClickOnTap = false;
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700847 // Handle pressed state before any state change.
848 if (mLastDownEventY < mTopSelectionDividerTop) {
849 if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
850 mPressedStateHelper.buttonPressDelayed(
851 PressedStateHelper.BUTTON_DECREMENT);
852 }
853 } else if (mLastDownEventY > mBottomSelectionDividerBottom) {
854 if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
855 mPressedStateHelper.buttonPressDelayed(
856 PressedStateHelper.BUTTON_INCREMENT);
857 }
858 }
859 // Make sure we support flinging inside scrollables.
Svetoslav Ganov83dc45c2012-04-12 16:19:32 -0700860 getParent().requestDisallowInterceptTouchEvent(true);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700861 if (!mFlingScroller.isFinished()) {
862 mFlingScroller.forceFinished(true);
863 mAdjustScroller.forceFinished(true);
864 onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
865 } else if (!mAdjustScroller.isFinished()) {
866 mFlingScroller.forceFinished(true);
867 mAdjustScroller.forceFinished(true);
868 } else if (mLastDownEventY < mTopSelectionDividerTop) {
869 hideSoftInput();
870 postChangeCurrentByOneFromLongPress(
871 false, ViewConfiguration.getLongPressTimeout());
872 } else if (mLastDownEventY > mBottomSelectionDividerBottom) {
873 hideSoftInput();
874 postChangeCurrentByOneFromLongPress(
875 true, ViewConfiguration.getLongPressTimeout());
876 } else {
Svetoslav46a27ef2014-03-03 15:37:14 -0800877 mPerformClickOnTap = true;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700878 postBeginSoftInputOnLongPressCommand();
879 }
880 return true;
881 }
882 }
883 return false;
884 }
885
886 @Override
887 public boolean onTouchEvent(MotionEvent event) {
888 if (!isEnabled() || !mHasSelectorWheel) {
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800889 return false;
890 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800891 if (mVelocityTracker == null) {
892 mVelocityTracker = VelocityTracker.obtain();
893 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700894 mVelocityTracker.addMovement(event);
895 int action = event.getActionMasked();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800896 switch (action) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700897 case MotionEvent.ACTION_MOVE: {
Svetoslav46a27ef2014-03-03 15:37:14 -0800898 if (mIgnoreMoveEvents) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700899 break;
900 }
901 float currentMoveY = event.getY();
902 if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800903 int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY);
904 if (deltaDownY > mTouchSlop) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700905 removeAllCallbacks();
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800906 onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800907 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700908 } else {
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700909 int deltaMoveY = (int) ((currentMoveY - mLastDownOrMoveEventY));
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700910 scrollBy(0, deltaMoveY);
911 invalidate();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800912 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700913 mLastDownOrMoveEventY = currentMoveY;
914 } break;
915 case MotionEvent.ACTION_UP: {
916 removeBeginSoftInputCommand();
917 removeChangeCurrentByOneFromLongPress();
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700918 mPressedStateHelper.cancel();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800919 VelocityTracker velocityTracker = mVelocityTracker;
920 velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
921 int initialVelocity = (int) velocityTracker.getYVelocity();
922 if (Math.abs(initialVelocity) > mMinimumFlingVelocity) {
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700923 fling(initialVelocity);
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800924 onScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800925 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700926 int eventY = (int) event.getY();
927 int deltaMoveY = (int) Math.abs(eventY - mLastDownEventY);
928 long deltaTime = event.getEventTime() - mLastDownEventTime;
929 if (deltaMoveY <= mTouchSlop && deltaTime < ViewConfiguration.getTapTimeout()) {
Svetoslav46a27ef2014-03-03 15:37:14 -0800930 if (mPerformClickOnTap) {
931 mPerformClickOnTap = false;
932 performClick();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700933 } else {
934 int selectorIndexOffset = (eventY / mSelectorElementHeight)
935 - SELECTOR_MIDDLE_ITEM_INDEX;
936 if (selectorIndexOffset > 0) {
937 changeValueByOne(true);
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700938 mPressedStateHelper.buttonTapped(
939 PressedStateHelper.BUTTON_INCREMENT);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700940 } else if (selectorIndexOffset < 0) {
941 changeValueByOne(false);
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700942 mPressedStateHelper.buttonTapped(
943 PressedStateHelper.BUTTON_DECREMENT);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700944 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800945 }
946 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700947 ensureScrollWheelAdjusted();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800948 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700949 onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800950 }
951 mVelocityTracker.recycle();
952 mVelocityTracker = null;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700953 } break;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800954 }
955 return true;
956 }
957
958 @Override
959 public boolean dispatchTouchEvent(MotionEvent event) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700960 final int action = event.getActionMasked();
961 switch (action) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700962 case MotionEvent.ACTION_CANCEL:
963 case MotionEvent.ACTION_UP:
964 removeAllCallbacks();
965 break;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800966 }
967 return super.dispatchTouchEvent(event);
968 }
969
970 @Override
971 public boolean dispatchKeyEvent(KeyEvent event) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700972 final int keyCode = event.getKeyCode();
973 switch (keyCode) {
974 case KeyEvent.KEYCODE_DPAD_CENTER:
975 case KeyEvent.KEYCODE_ENTER:
976 removeAllCallbacks();
977 break;
Svetoslav Ganov5dc21d92012-10-07 18:54:42 -0700978 case KeyEvent.KEYCODE_DPAD_DOWN:
979 case KeyEvent.KEYCODE_DPAD_UP:
980 if (!mHasSelectorWheel) {
981 break;
982 }
983 switch (event.getAction()) {
984 case KeyEvent.ACTION_DOWN:
Craig Stout9eef3f42014-06-23 14:13:43 -0700985 if (mWrapSelectorWheel || ((keyCode == KeyEvent.KEYCODE_DPAD_DOWN)
986 ? getValue() < getMaxValue() : getValue() > getMinValue())) {
Svetoslav Ganov5dc21d92012-10-07 18:54:42 -0700987 requestFocus();
988 mLastHandledDownDpadKeyCode = keyCode;
989 removeAllCallbacks();
990 if (mFlingScroller.isFinished()) {
Justin Mattson86cf0cd2012-10-08 11:46:28 -0700991 changeValueByOne(keyCode == KeyEvent.KEYCODE_DPAD_DOWN);
Svetoslav Ganov5dc21d92012-10-07 18:54:42 -0700992 }
993 return true;
994 }
995 break;
996 case KeyEvent.ACTION_UP:
997 if (mLastHandledDownDpadKeyCode == keyCode) {
998 mLastHandledDownDpadKeyCode = -1;
999 return true;
1000 }
1001 break;
1002 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001003 }
1004 return super.dispatchKeyEvent(event);
1005 }
1006
1007 @Override
1008 public boolean dispatchTrackballEvent(MotionEvent event) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001009 final int action = event.getActionMasked();
1010 switch (action) {
1011 case MotionEvent.ACTION_CANCEL:
1012 case MotionEvent.ACTION_UP:
1013 removeAllCallbacks();
1014 break;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001015 }
1016 return super.dispatchTrackballEvent(event);
1017 }
1018
1019 @Override
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001020 protected boolean dispatchHoverEvent(MotionEvent event) {
1021 if (!mHasSelectorWheel) {
1022 return super.dispatchHoverEvent(event);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001023 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001024 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
1025 final int eventY = (int) event.getY();
1026 final int hoveredVirtualViewId;
1027 if (eventY < mTopSelectionDividerTop) {
1028 hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_DECREMENT;
1029 } else if (eventY > mBottomSelectionDividerBottom) {
1030 hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INCREMENT;
1031 } else {
1032 hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INPUT;
1033 }
1034 final int action = event.getActionMasked();
1035 AccessibilityNodeProviderImpl provider =
1036 (AccessibilityNodeProviderImpl) getAccessibilityNodeProvider();
1037 switch (action) {
1038 case MotionEvent.ACTION_HOVER_ENTER: {
1039 provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
1040 AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
1041 mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07001042 provider.performAction(hoveredVirtualViewId,
1043 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001044 } break;
1045 case MotionEvent.ACTION_HOVER_MOVE: {
1046 if (mLastHoveredChildVirtualViewId != hoveredVirtualViewId
1047 && mLastHoveredChildVirtualViewId != View.NO_ID) {
1048 provider.sendAccessibilityEventForVirtualView(
1049 mLastHoveredChildVirtualViewId,
1050 AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
1051 provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
1052 AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
1053 mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07001054 provider.performAction(hoveredVirtualViewId,
1055 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001056 }
1057 } break;
1058 case MotionEvent.ACTION_HOVER_EXIT: {
1059 provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
1060 AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
1061 mLastHoveredChildVirtualViewId = View.NO_ID;
1062 } break;
1063 }
1064 }
1065 return false;
1066 }
1067
1068 @Override
1069 public void computeScroll() {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001070 Scroller scroller = mFlingScroller;
1071 if (scroller.isFinished()) {
1072 scroller = mAdjustScroller;
1073 if (scroller.isFinished()) {
1074 return;
1075 }
1076 }
1077 scroller.computeScrollOffset();
1078 int currentScrollerY = scroller.getCurrY();
1079 if (mPreviousScrollerY == 0) {
1080 mPreviousScrollerY = scroller.getStartY();
1081 }
1082 scrollBy(0, currentScrollerY - mPreviousScrollerY);
1083 mPreviousScrollerY = currentScrollerY;
1084 if (scroller.isFinished()) {
1085 onScrollerFinished(scroller);
1086 } else {
1087 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001088 }
1089 }
Tom Taylorfdf6db62009-09-09 11:37:58 -07001090
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001091 @Override
1092 public void setEnabled(boolean enabled) {
1093 super.setEnabled(enabled);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001094 if (!mHasSelectorWheel) {
1095 mIncrementButton.setEnabled(enabled);
1096 }
1097 if (!mHasSelectorWheel) {
1098 mDecrementButton.setEnabled(enabled);
1099 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001100 mInputText.setEnabled(enabled);
1101 }
1102
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001103 @Override
1104 public void scrollBy(int x, int y) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001105 int[] selectorIndices = mSelectorIndices;
Svetoslav Ganov34c06882011-01-03 01:32:34 -08001106 if (!mWrapSelectorWheel && y > 0
1107 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001108 mCurrentScrollOffset = mInitialScrollOffset;
1109 return;
1110 }
Svetoslav Ganov34c06882011-01-03 01:32:34 -08001111 if (!mWrapSelectorWheel && y < 0
1112 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001113 mCurrentScrollOffset = mInitialScrollOffset;
1114 return;
1115 }
1116 mCurrentScrollOffset += y;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001117 while (mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapHeight) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001118 mCurrentScrollOffset -= mSelectorElementHeight;
1119 decrementSelectorIndices(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] <= mMinValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001122 mCurrentScrollOffset = mInitialScrollOffset;
1123 }
1124 }
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001125 while (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapHeight) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001126 mCurrentScrollOffset += mSelectorElementHeight;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001127 incrementSelectorIndices(selectorIndices);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001128 setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001129 if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001130 mCurrentScrollOffset = mInitialScrollOffset;
1131 }
1132 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001133 }
Tom Taylorfdf6db62009-09-09 11:37:58 -07001134
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001135 @Override
Alan Viverettefd639172013-09-18 11:20:38 -07001136 protected int computeVerticalScrollOffset() {
Alan Viverette5ba99f52013-09-13 12:23:20 -07001137 return mCurrentScrollOffset;
1138 }
1139
1140 @Override
Alan Viverettefd639172013-09-18 11:20:38 -07001141 protected int computeVerticalScrollRange() {
1142 return (mMaxValue - mMinValue + 1) * mSelectorElementHeight;
1143 }
1144
1145 @Override
1146 protected int computeVerticalScrollExtent() {
1147 return getHeight();
Alan Viverette5ba99f52013-09-13 12:23:20 -07001148 }
1149
1150 @Override
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001151 public int getSolidColor() {
1152 return mSolidColor;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001153 }
1154
1155 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001156 * Sets the listener to be notified on change of the current value.
1157 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001158 * @param onValueChangedListener The listener.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001159 */
Svetoslav Ganovcedc4462011-01-19 19:25:46 -08001160 public void setOnValueChangedListener(OnValueChangeListener onValueChangedListener) {
1161 mOnValueChangeListener = onValueChangedListener;
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001162 }
1163
1164 /**
1165 * Set listener to be notified for scroll state changes.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001166 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001167 * @param onScrollListener The listener.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001168 */
1169 public void setOnScrollListener(OnScrollListener onScrollListener) {
1170 mOnScrollListener = onScrollListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001171 }
Tom Taylorfdf6db62009-09-09 11:37:58 -07001172
Paul Westbrook68f2f542010-01-13 12:13:57 -08001173 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001174 * Set the formatter to be used for formatting the current value.
1175 * <p>
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001176 * Note: If you have provided alternative values for the values this
1177 * formatter is never invoked.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001178 * </p>
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001179 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001180 * @param formatter The formatter object. If formatter is <code>null</code>,
1181 * {@link String#valueOf(int)} will be used.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001182 *@see #setDisplayedValues(String[])
Paul Westbrook68f2f542010-01-13 12:13:57 -08001183 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001184 public void setFormatter(Formatter formatter) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001185 if (formatter == mFormatter) {
Svetoslav Ganova911d4a2010-12-08 16:11:30 -08001186 return;
1187 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001188 mFormatter = formatter;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001189 initializeSelectorWheelIndices();
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001190 updateInputTextView();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001191 }
Tom Taylorfdf6db62009-09-09 11:37:58 -07001192
Paul Westbrook68f2f542010-01-13 12:13:57 -08001193 /**
1194 * Set the current value for the number picker.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001195 * <p>
1196 * If the argument is less than the {@link NumberPicker#getMinValue()} and
1197 * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the
1198 * current value is set to the {@link NumberPicker#getMinValue()} value.
1199 * </p>
1200 * <p>
1201 * If the argument is less than the {@link NumberPicker#getMinValue()} and
1202 * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the
1203 * current value is set to the {@link NumberPicker#getMaxValue()} value.
1204 * </p>
1205 * <p>
1206 * If the argument is less than the {@link NumberPicker#getMaxValue()} and
1207 * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the
1208 * current value is set to the {@link NumberPicker#getMaxValue()} value.
1209 * </p>
1210 * <p>
1211 * If the argument is less than the {@link NumberPicker#getMaxValue()} and
1212 * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the
1213 * current value is set to the {@link NumberPicker#getMinValue()} value.
1214 * </p>
Paul Westbrook68f2f542010-01-13 12:13:57 -08001215 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001216 * @param value The current value.
1217 * @see #setWrapSelectorWheel(boolean)
1218 * @see #setMinValue(int)
1219 * @see #setMaxValue(int)
Paul Westbrook68f2f542010-01-13 12:13:57 -08001220 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001221 public void setValue(int value) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001222 setValueInternal(value, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001223 }
1224
Svetoslav46a27ef2014-03-03 15:37:14 -08001225 @Override
1226 public boolean performClick() {
1227 if (!mHasSelectorWheel) {
1228 return super.performClick();
1229 } else if (!super.performClick()) {
1230 showSoftInput();
1231 }
1232 return true;
1233 }
1234
1235 @Override
1236 public boolean performLongClick() {
1237 if (!mHasSelectorWheel) {
1238 return super.performLongClick();
1239 } else if (!super.performLongClick()) {
1240 showSoftInput();
1241 mIgnoreMoveEvents = true;
1242 }
1243 return true;
1244 }
1245
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001246 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001247 * Shows the soft input for its input text.
1248 */
1249 private void showSoftInput() {
1250 InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
1251 if (inputMethodManager != null) {
1252 if (mHasSelectorWheel) {
1253 mInputText.setVisibility(View.VISIBLE);
1254 }
1255 mInputText.requestFocus();
1256 inputMethodManager.showSoftInput(mInputText, 0);
1257 }
1258 }
1259
1260 /**
1261 * Hides the soft input if it is active for the input text.
Svetoslav Ganovb52d9722011-11-07 14:53:34 -08001262 */
1263 private void hideSoftInput() {
1264 InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
1265 if (inputMethodManager != null && inputMethodManager.isActive(mInputText)) {
1266 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001267 if (mHasSelectorWheel) {
1268 mInputText.setVisibility(View.INVISIBLE);
1269 }
Svetoslav Ganovb52d9722011-11-07 14:53:34 -08001270 }
1271 }
1272
1273 /**
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001274 * Computes the max width if no such specified as an attribute.
1275 */
1276 private void tryComputeMaxWidth() {
1277 if (!mComputeMaxWidth) {
1278 return;
1279 }
1280 int maxTextWidth = 0;
1281 if (mDisplayedValues == null) {
1282 float maxDigitWidth = 0;
1283 for (int i = 0; i <= 9; i++) {
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07001284 final float digitWidth = mSelectorWheelPaint.measureText(formatNumberWithLocale(i));
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001285 if (digitWidth > maxDigitWidth) {
1286 maxDigitWidth = digitWidth;
1287 }
1288 }
1289 int numberOfDigits = 0;
1290 int current = mMaxValue;
1291 while (current > 0) {
1292 numberOfDigits++;
1293 current = current / 10;
1294 }
1295 maxTextWidth = (int) (numberOfDigits * maxDigitWidth);
1296 } else {
1297 final int valueCount = mDisplayedValues.length;
1298 for (int i = 0; i < valueCount; i++) {
1299 final float textWidth = mSelectorWheelPaint.measureText(mDisplayedValues[i]);
1300 if (textWidth > maxTextWidth) {
1301 maxTextWidth = (int) textWidth;
1302 }
1303 }
1304 }
1305 maxTextWidth += mInputText.getPaddingLeft() + mInputText.getPaddingRight();
1306 if (mMaxWidth != maxTextWidth) {
1307 if (maxTextWidth > mMinWidth) {
1308 mMaxWidth = maxTextWidth;
1309 } else {
1310 mMaxWidth = mMinWidth;
1311 }
1312 invalidate();
1313 }
1314 }
1315
1316 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001317 * Gets whether the selector wheel wraps when reaching the min/max value.
1318 *
1319 * @return True if the selector wheel wraps.
1320 *
1321 * @see #getMinValue()
1322 * @see #getMaxValue()
1323 */
1324 public boolean getWrapSelectorWheel() {
1325 return mWrapSelectorWheel;
1326 }
1327
1328 /**
1329 * Sets whether the selector wheel shown during flinging/scrolling should
1330 * wrap around the {@link NumberPicker#getMinValue()} and
1331 * {@link NumberPicker#getMaxValue()} values.
1332 * <p>
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001333 * By default if the range (max - min) is more than the number of items shown
1334 * on the selector wheel the selector wheel wrapping is enabled.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001335 * </p>
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -08001336 * <p>
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001337 * <strong>Note:</strong> If the number of items, i.e. the range (
1338 * {@link #getMaxValue()} - {@link #getMinValue()}) is less than
1339 * the number of items shown on the selector wheel, the selector wheel will
1340 * not wrap. Hence, in such a case calling this method is a NOP.
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -08001341 * </p>
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001342 *
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001343 * @param wrapSelectorWheel Whether to wrap.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001344 */
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001345 public void setWrapSelectorWheel(boolean wrapSelectorWheel) {
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -08001346 final boolean wrappingAllowed = (mMaxValue - mMinValue) >= mSelectorIndices.length;
1347 if ((!wrapSelectorWheel || wrappingAllowed) && wrapSelectorWheel != mWrapSelectorWheel) {
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001348 mWrapSelectorWheel = wrapSelectorWheel;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001349 }
1350 }
1351
1352 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001353 * Sets the speed at which the numbers be incremented and decremented when
1354 * the up and down buttons are long pressed respectively.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001355 * <p>
1356 * The default value is 300 ms.
1357 * </p>
Paul Westbrook68f2f542010-01-13 12:13:57 -08001358 *
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001359 * @param intervalMillis The speed (in milliseconds) at which the numbers
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001360 * will be incremented and decremented.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001361 */
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001362 public void setOnLongPressUpdateInterval(long intervalMillis) {
1363 mLongPressUpdateInterval = intervalMillis;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001364 }
1365
1366 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001367 * Returns the value of the picker.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001368 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001369 * @return The value.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001370 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001371 public int getValue() {
1372 return mValue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001373 }
Paul Westbrook68f2f542010-01-13 12:13:57 -08001374
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001375 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001376 * Returns the min value of the picker.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001377 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001378 * @return The min value
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001379 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001380 public int getMinValue() {
1381 return mMinValue;
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001382 }
1383
1384 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001385 * Sets the min value of the picker.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001386 *
Svetoslav Ganov7018cfd2012-11-19 12:33:41 -08001387 * @param minValue The min value inclusive.
1388 *
1389 * <strong>Note:</strong> The length of the displayed values array
1390 * set via {@link #setDisplayedValues(String[])} must be equal to the
1391 * range of selectable numbers which is equal to
1392 * {@link #getMaxValue()} - {@link #getMinValue()} + 1.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001393 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001394 public void setMinValue(int minValue) {
1395 if (mMinValue == minValue) {
1396 return;
1397 }
1398 if (minValue < 0) {
1399 throw new IllegalArgumentException("minValue must be >= 0");
1400 }
1401 mMinValue = minValue;
1402 if (mMinValue > mValue) {
1403 mValue = mMinValue;
1404 }
1405 boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
1406 setWrapSelectorWheel(wrapSelectorWheel);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001407 initializeSelectorWheelIndices();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001408 updateInputTextView();
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001409 tryComputeMaxWidth();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001410 invalidate();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001411 }
1412
1413 /**
1414 * Returns the max value of the picker.
1415 *
1416 * @return The max value.
1417 */
1418 public int getMaxValue() {
1419 return mMaxValue;
1420 }
1421
1422 /**
1423 * Sets the max value of the picker.
1424 *
Svetoslav Ganov7018cfd2012-11-19 12:33:41 -08001425 * @param maxValue The max value inclusive.
1426 *
1427 * <strong>Note:</strong> The length of the displayed values array
1428 * set via {@link #setDisplayedValues(String[])} must be equal to the
1429 * range of selectable numbers which is equal to
1430 * {@link #getMaxValue()} - {@link #getMinValue()} + 1.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001431 */
1432 public void setMaxValue(int maxValue) {
1433 if (mMaxValue == maxValue) {
1434 return;
1435 }
1436 if (maxValue < 0) {
1437 throw new IllegalArgumentException("maxValue must be >= 0");
1438 }
1439 mMaxValue = maxValue;
1440 if (mMaxValue < mValue) {
1441 mValue = mMaxValue;
1442 }
1443 boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
1444 setWrapSelectorWheel(wrapSelectorWheel);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001445 initializeSelectorWheelIndices();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001446 updateInputTextView();
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001447 tryComputeMaxWidth();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001448 invalidate();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001449 }
1450
1451 /**
1452 * Gets the values to be displayed instead of string values.
1453 *
1454 * @return The displayed values.
1455 */
1456 public String[] getDisplayedValues() {
1457 return mDisplayedValues;
1458 }
1459
1460 /**
1461 * Sets the values to be displayed.
1462 *
1463 * @param displayedValues The displayed values.
Svetoslav Ganov7018cfd2012-11-19 12:33:41 -08001464 *
1465 * <strong>Note:</strong> The length of the displayed values array
1466 * must be equal to the range of selectable numbers which is equal to
1467 * {@link #getMaxValue()} - {@link #getMinValue()} + 1.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001468 */
1469 public void setDisplayedValues(String[] displayedValues) {
1470 if (mDisplayedValues == displayedValues) {
1471 return;
1472 }
1473 mDisplayedValues = displayedValues;
1474 if (mDisplayedValues != null) {
1475 // Allow text entry rather than strictly numeric entry.
1476 mInputText.setRawInputType(InputType.TYPE_CLASS_TEXT
1477 | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
1478 } else {
1479 mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
1480 }
1481 updateInputTextView();
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001482 initializeSelectorWheelIndices();
Svetoslav Ganov9f086d82011-11-29 18:27:23 -08001483 tryComputeMaxWidth();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001484 }
1485
1486 @Override
1487 protected float getTopFadingEdgeStrength() {
1488 return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
1489 }
1490
1491 @Override
1492 protected float getBottomFadingEdgeStrength() {
1493 return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
1494 }
1495
1496 @Override
1497 protected void onDetachedFromWindow() {
Romain Guy46bfc482013-08-16 18:38:29 -07001498 super.onDetachedFromWindow();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001499 removeAllCallbacks();
1500 }
1501
1502 @Override
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001503 protected void onDraw(Canvas canvas) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001504 if (!mHasSelectorWheel) {
1505 super.onDraw(canvas);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001506 return;
1507 }
Craig Stout9eef3f42014-06-23 14:13:43 -07001508 final boolean showSelectorWheel = mHideWheelUntilFocused ? hasFocus() : true;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001509 float x = (mRight - mLeft) / 2;
1510 float y = mCurrentScrollOffset;
1511
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001512 // draw the virtual buttons pressed state if needed
Craig Stout9eef3f42014-06-23 14:13:43 -07001513 if (showSelectorWheel && mVirtualButtonPressedDrawable != null
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001514 && mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
1515 if (mDecrementVirtualButtonPressed) {
1516 mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET);
1517 mVirtualButtonPressedDrawable.setBounds(0, 0, mRight, mTopSelectionDividerTop);
1518 mVirtualButtonPressedDrawable.draw(canvas);
1519 }
1520 if (mIncrementVirtualButtonPressed) {
1521 mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET);
1522 mVirtualButtonPressedDrawable.setBounds(0, mBottomSelectionDividerBottom, mRight,
1523 mBottom);
1524 mVirtualButtonPressedDrawable.draw(canvas);
1525 }
1526 }
1527
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001528 // draw the selector wheel
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001529 int[] selectorIndices = mSelectorIndices;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001530 for (int i = 0; i < selectorIndices.length; i++) {
1531 int selectorIndex = selectorIndices[i];
1532 String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001533 // Do not draw the middle item if input is visible since the input
1534 // is shown only if the wheel is static and it covers the middle
1535 // item. Otherwise, if the user starts editing the text via the
1536 // IME he may see a dimmed version of the old value intermixed
1537 // with the new one.
Craig Stout9eef3f42014-06-23 14:13:43 -07001538 if ((showSelectorWheel && i != SELECTOR_MIDDLE_ITEM_INDEX) ||
1539 (i == SELECTOR_MIDDLE_ITEM_INDEX && mInputText.getVisibility() != VISIBLE)) {
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001540 canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint);
1541 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001542 y += mSelectorElementHeight;
1543 }
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001544
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001545 // draw the selection dividers
Craig Stout9eef3f42014-06-23 14:13:43 -07001546 if (showSelectorWheel && mSelectionDivider != null) {
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001547 // draw the top divider
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001548 int topOfTopDivider = mTopSelectionDividerTop;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001549 int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight;
1550 mSelectionDivider.setBounds(0, topOfTopDivider, mRight, bottomOfTopDivider);
1551 mSelectionDivider.draw(canvas);
1552
1553 // draw the bottom divider
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001554 int bottomOfBottomDivider = mBottomSelectionDividerBottom;
1555 int topOfBottomDivider = bottomOfBottomDivider - mSelectionDividerHeight;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001556 mSelectionDivider.setBounds(0, topOfBottomDivider, mRight, bottomOfBottomDivider);
1557 mSelectionDivider.draw(canvas);
1558 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001559 }
1560
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -07001561 @Override
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001562 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1563 super.onInitializeAccessibilityEvent(event);
1564 event.setClassName(NumberPicker.class.getName());
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001565 event.setScrollable(true);
1566 event.setScrollY((mMinValue + mValue) * mSelectorElementHeight);
1567 event.setMaxScrollY((mMaxValue - mMinValue) * mSelectorElementHeight);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001568 }
1569
1570 @Override
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001571 public AccessibilityNodeProvider getAccessibilityNodeProvider() {
1572 if (!mHasSelectorWheel) {
1573 return super.getAccessibilityNodeProvider();
1574 }
1575 if (mAccessibilityNodeProvider == null) {
1576 mAccessibilityNodeProvider = new AccessibilityNodeProviderImpl();
1577 }
1578 return mAccessibilityNodeProvider;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001579 }
1580
Paul Westbrook68f2f542010-01-13 12:13:57 -08001581 /**
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001582 * Makes a measure spec that tries greedily to use the max value.
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001583 *
1584 * @param measureSpec The measure spec.
Svetoslav Ganov698e1d52011-11-07 18:43:01 -08001585 * @param maxSize The max value for the size.
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001586 * @return A measure spec greedily imposing the max size.
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001587 */
Svetoslav Ganov698e1d52011-11-07 18:43:01 -08001588 private int makeMeasureSpec(int measureSpec, int maxSize) {
Svetoslav Ganov9f086d82011-11-29 18:27:23 -08001589 if (maxSize == SIZE_UNSPECIFIED) {
1590 return measureSpec;
1591 }
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001592 final int size = MeasureSpec.getSize(measureSpec);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001593 final int mode = MeasureSpec.getMode(measureSpec);
1594 switch (mode) {
1595 case MeasureSpec.EXACTLY:
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001596 return measureSpec;
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001597 case MeasureSpec.AT_MOST:
Svetoslav Ganov698e1d52011-11-07 18:43:01 -08001598 return MeasureSpec.makeMeasureSpec(Math.min(size, maxSize), MeasureSpec.EXACTLY);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001599 case MeasureSpec.UNSPECIFIED:
Svetoslav Ganov698e1d52011-11-07 18:43:01 -08001600 return MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.EXACTLY);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001601 default:
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001602 throw new IllegalArgumentException("Unknown measure mode: " + mode);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001603 }
1604 }
1605
1606 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001607 * Utility to reconcile a desired size and state, with constraints imposed
1608 * by a MeasureSpec. Tries to respect the min size, unless a different size
1609 * is imposed by the constraints.
Svetoslav Ganov9f086d82011-11-29 18:27:23 -08001610 *
1611 * @param minSize The minimal desired size.
1612 * @param measuredSize The currently measured size.
1613 * @param measureSpec The current measure spec.
1614 * @return The resolved size and state.
1615 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001616 private int resolveSizeAndStateRespectingMinSize(
1617 int minSize, int measuredSize, int measureSpec) {
Svetoslav Ganov9f086d82011-11-29 18:27:23 -08001618 if (minSize != SIZE_UNSPECIFIED) {
1619 final int desiredWidth = Math.max(minSize, measuredSize);
1620 return resolveSizeAndState(desiredWidth, measureSpec, 0);
1621 } else {
1622 return measuredSize;
1623 }
1624 }
1625
1626 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001627 * Resets the selector indices and clear the cached string representation of
1628 * these indices.
Svetoslav Ganova911d4a2010-12-08 16:11:30 -08001629 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001630 private void initializeSelectorWheelIndices() {
Svetoslav Ganova911d4a2010-12-08 16:11:30 -08001631 mSelectorIndexToStringCache.clear();
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001632 int[] selectorIndices = mSelectorIndices;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001633 int current = getValue();
1634 for (int i = 0; i < mSelectorIndices.length; i++) {
1635 int selectorIndex = current + (i - SELECTOR_MIDDLE_ITEM_INDEX);
1636 if (mWrapSelectorWheel) {
1637 selectorIndex = getWrappedSelectorIndex(selectorIndex);
1638 }
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001639 selectorIndices[i] = selectorIndex;
1640 ensureCachedScrollSelectorValue(selectorIndices[i]);
Svetoslav Ganova911d4a2010-12-08 16:11:30 -08001641 }
1642 }
1643
1644 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001645 * Sets the current value of this NumberPicker.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001646 *
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001647 * @param current The new value of the NumberPicker.
1648 * @param notifyChange Whether to notify if the current value changed.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001649 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001650 private void setValueInternal(int current, boolean notifyChange) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001651 if (mValue == current) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001652 return;
1653 }
1654 // Wrap around the values if we go past the start or end
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001655 if (mWrapSelectorWheel) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001656 current = getWrappedSelectorIndex(current);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001657 } else {
1658 current = Math.max(current, mMinValue);
1659 current = Math.min(current, mMaxValue);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001660 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001661 int previous = mValue;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001662 mValue = current;
1663 updateInputTextView();
1664 if (notifyChange) {
1665 notifyChange(previous, current);
1666 }
Svetoslav Ganovfac14f92012-04-12 16:51:04 -07001667 initializeSelectorWheelIndices();
1668 invalidate();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001669 }
1670
1671 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001672 * Changes the current value by one which is increment or
1673 * decrement based on the passes argument.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001674 * decrement the current value.
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001675 *
1676 * @param increment True to increment, false to decrement.
1677 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001678 private void changeValueByOne(boolean increment) {
1679 if (mHasSelectorWheel) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001680 mInputText.setVisibility(View.INVISIBLE);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001681 if (!moveToFinalScrollerPosition(mFlingScroller)) {
1682 moveToFinalScrollerPosition(mAdjustScroller);
1683 }
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001684 mPreviousScrollerY = 0;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001685 if (increment) {
Svetoslav Ganovfe41ce42012-04-02 20:31:05 -07001686 mFlingScroller.startScroll(0, 0, 0, -mSelectorElementHeight, SNAP_SCROLL_DURATION);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001687 } else {
Svetoslav Ganovfe41ce42012-04-02 20:31:05 -07001688 mFlingScroller.startScroll(0, 0, 0, mSelectorElementHeight, SNAP_SCROLL_DURATION);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001689 }
1690 invalidate();
1691 } else {
1692 if (increment) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001693 setValueInternal(mValue + 1, true);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001694 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001695 setValueInternal(mValue - 1, true);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001696 }
1697 }
1698 }
1699
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001700 private void initializeSelectorWheel() {
1701 initializeSelectorWheelIndices();
1702 int[] selectorIndices = mSelectorIndices;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001703 int totalTextHeight = selectorIndices.length * mTextSize;
Svetoslav Ganov01fa0d72011-06-28 22:08:23 -07001704 float totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001705 float textGapCount = selectorIndices.length;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001706 mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f);
1707 mSelectorElementHeight = mTextSize + mSelectorTextGapHeight;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001708 // Ensure that the middle item is positioned the same as the text in
1709 // mInputText
Chet Haaseeeafd422011-08-17 18:26:56 -07001710 int editTextTextPosition = mInputText.getBaseline() + mInputText.getTop();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001711 mInitialScrollOffset = editTextTextPosition
1712 - (mSelectorElementHeight * SELECTOR_MIDDLE_ITEM_INDEX);
Svetoslav Ganov6a19fcd2011-06-29 12:26:11 -07001713 mCurrentScrollOffset = mInitialScrollOffset;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001714 updateInputTextView();
1715 }
1716
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001717 private void initializeFadingEdges() {
1718 setVerticalFadingEdgeEnabled(true);
1719 setFadingEdgeLength((mBottom - mTop - mTextSize) / 2);
1720 }
1721
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001722 /**
1723 * Callback invoked upon completion of a given <code>scroller</code>.
1724 */
1725 private void onScrollerFinished(Scroller scroller) {
1726 if (scroller == mFlingScroller) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001727 if (!ensureScrollWheelAdjusted()) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001728 updateInputTextView();
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001729 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001730 onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001731 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001732 if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
1733 updateInputTextView();
1734 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001735 }
1736 }
1737
1738 /**
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001739 * Handles transition to a given <code>scrollState</code>
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001740 */
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001741 private void onScrollStateChange(int scrollState) {
1742 if (mScrollState == scrollState) {
1743 return;
1744 }
1745 mScrollState = scrollState;
1746 if (mOnScrollListener != null) {
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001747 mOnScrollListener.onScrollStateChange(this, scrollState);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001748 }
1749 }
1750
1751 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001752 * Flings the selector with the given <code>velocityY</code>.
1753 */
1754 private void fling(int velocityY) {
1755 mPreviousScrollerY = 0;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001756
Svetoslav Ganov234484a2011-12-07 19:06:35 -08001757 if (velocityY > 0) {
1758 mFlingScroller.fling(0, 0, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001759 } else {
Svetoslav Ganov234484a2011-12-07 19:06:35 -08001760 mFlingScroller.fling(0, Integer.MAX_VALUE, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001761 }
1762
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001763 invalidate();
1764 }
1765
1766 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001767 * @return The wrapped index <code>selectorIndex</code> value.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001768 */
1769 private int getWrappedSelectorIndex(int selectorIndex) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001770 if (selectorIndex > mMaxValue) {
1771 return mMinValue + (selectorIndex - mMaxValue) % (mMaxValue - mMinValue) - 1;
1772 } else if (selectorIndex < mMinValue) {
1773 return mMaxValue - (mMinValue - selectorIndex) % (mMaxValue - mMinValue) + 1;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001774 }
1775 return selectorIndex;
1776 }
1777
1778 /**
1779 * Increments the <code>selectorIndices</code> whose string representations
1780 * will be displayed in the selector.
1781 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001782 private void incrementSelectorIndices(int[] selectorIndices) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001783 for (int i = 0; i < selectorIndices.length - 1; i++) {
1784 selectorIndices[i] = selectorIndices[i + 1];
1785 }
1786 int nextScrollSelectorIndex = selectorIndices[selectorIndices.length - 2] + 1;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001787 if (mWrapSelectorWheel && nextScrollSelectorIndex > mMaxValue) {
1788 nextScrollSelectorIndex = mMinValue;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001789 }
1790 selectorIndices[selectorIndices.length - 1] = nextScrollSelectorIndex;
1791 ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
1792 }
1793
1794 /**
1795 * Decrements the <code>selectorIndices</code> whose string representations
1796 * will be displayed in the selector.
1797 */
1798 private void decrementSelectorIndices(int[] selectorIndices) {
1799 for (int i = selectorIndices.length - 1; i > 0; i--) {
1800 selectorIndices[i] = selectorIndices[i - 1];
1801 }
1802 int nextScrollSelectorIndex = selectorIndices[1] - 1;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001803 if (mWrapSelectorWheel && nextScrollSelectorIndex < mMinValue) {
1804 nextScrollSelectorIndex = mMaxValue;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001805 }
1806 selectorIndices[0] = nextScrollSelectorIndex;
1807 ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
1808 }
1809
1810 /**
1811 * Ensures we have a cached string representation of the given <code>
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001812 * selectorIndex</code> to avoid multiple instantiations of the same string.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001813 */
1814 private void ensureCachedScrollSelectorValue(int selectorIndex) {
1815 SparseArray<String> cache = mSelectorIndexToStringCache;
1816 String scrollSelectorValue = cache.get(selectorIndex);
1817 if (scrollSelectorValue != null) {
1818 return;
1819 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001820 if (selectorIndex < mMinValue || selectorIndex > mMaxValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001821 scrollSelectorValue = "";
1822 } else {
1823 if (mDisplayedValues != null) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001824 int displayedValueIndex = selectorIndex - mMinValue;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001825 scrollSelectorValue = mDisplayedValues[displayedValueIndex];
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001826 } else {
1827 scrollSelectorValue = formatNumber(selectorIndex);
1828 }
1829 }
1830 cache.put(selectorIndex, scrollSelectorValue);
1831 }
1832
1833 private String formatNumber(int value) {
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07001834 return (mFormatter != null) ? mFormatter.format(value) : formatNumberWithLocale(value);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001835 }
1836
1837 private void validateInputTextView(View v) {
1838 String str = String.valueOf(((TextView) v).getText());
1839 if (TextUtils.isEmpty(str)) {
1840 // Restore to the old value as we don't allow empty values
1841 updateInputTextView();
1842 } else {
1843 // Check the new value and ensure it's in range
1844 int current = getSelectedPos(str.toString());
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001845 setValueInternal(current, true);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001846 }
1847 }
1848
1849 /**
1850 * Updates the view of this NumberPicker. If displayValues were specified in
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001851 * the string corresponding to the index specified by the current value will
1852 * be returned. Otherwise, the formatter specified in {@link #setFormatter}
1853 * will be used to format the number.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001854 *
1855 * @return Whether the text was updated.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001856 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001857 private boolean updateInputTextView() {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001858 /*
1859 * If we don't have displayed values then use the current number else
1860 * find the correct value in the displayed values for the current
1861 * number.
1862 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001863 String text = (mDisplayedValues == null) ? formatNumber(mValue)
1864 : mDisplayedValues[mValue - mMinValue];
1865 if (!TextUtils.isEmpty(text) && !text.equals(mInputText.getText().toString())) {
1866 mInputText.setText(text);
1867 return true;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001868 }
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -07001869
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001870 return false;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001871 }
1872
1873 /**
1874 * Notifies the listener, if registered, of a change of the value of this
1875 * NumberPicker.
1876 */
1877 private void notifyChange(int previous, int current) {
Svetoslav Ganovcedc4462011-01-19 19:25:46 -08001878 if (mOnValueChangeListener != null) {
1879 mOnValueChangeListener.onValueChange(this, previous, mValue);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001880 }
1881 }
1882
1883 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001884 * Posts a command for changing the current value by one.
1885 *
1886 * @param increment Whether to increment or decrement the value.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001887 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001888 private void postChangeCurrentByOneFromLongPress(boolean increment, long delayMillis) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001889 if (mChangeCurrentByOneFromLongPressCommand == null) {
1890 mChangeCurrentByOneFromLongPressCommand = new ChangeCurrentByOneFromLongPressCommand();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001891 } else {
1892 removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001893 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001894 mChangeCurrentByOneFromLongPressCommand.setStep(increment);
1895 postDelayed(mChangeCurrentByOneFromLongPressCommand, delayMillis);
1896 }
1897
1898 /**
1899 * Removes the command for changing the current value by one.
1900 */
1901 private void removeChangeCurrentByOneFromLongPress() {
1902 if (mChangeCurrentByOneFromLongPressCommand != null) {
1903 removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
1904 }
1905 }
1906
1907 /**
1908 * Posts a command for beginning an edit of the current value via IME on
1909 * long press.
1910 */
1911 private void postBeginSoftInputOnLongPressCommand() {
1912 if (mBeginSoftInputOnLongPressCommand == null) {
1913 mBeginSoftInputOnLongPressCommand = new BeginSoftInputOnLongPressCommand();
1914 } else {
1915 removeCallbacks(mBeginSoftInputOnLongPressCommand);
1916 }
1917 postDelayed(mBeginSoftInputOnLongPressCommand, ViewConfiguration.getLongPressTimeout());
1918 }
1919
1920 /**
1921 * Removes the command for beginning an edit of the current value via IME.
1922 */
1923 private void removeBeginSoftInputCommand() {
1924 if (mBeginSoftInputOnLongPressCommand != null) {
1925 removeCallbacks(mBeginSoftInputOnLongPressCommand);
1926 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001927 }
1928
1929 /**
1930 * Removes all pending callback from the message queue.
1931 */
1932 private void removeAllCallbacks() {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001933 if (mChangeCurrentByOneFromLongPressCommand != null) {
1934 removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001935 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001936 if (mSetSelectionCommand != null) {
1937 removeCallbacks(mSetSelectionCommand);
1938 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001939 if (mBeginSoftInputOnLongPressCommand != null) {
1940 removeCallbacks(mBeginSoftInputOnLongPressCommand);
1941 }
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001942 mPressedStateHelper.cancel();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001943 }
1944
1945 /**
1946 * @return The selected index given its displayed <code>value</code>.
1947 */
1948 private int getSelectedPos(String value) {
1949 if (mDisplayedValues == null) {
1950 try {
1951 return Integer.parseInt(value);
1952 } catch (NumberFormatException e) {
1953 // Ignore as if it's not a number we don't care
1954 }
1955 } else {
1956 for (int i = 0; i < mDisplayedValues.length; i++) {
1957 // Don't force the user to type in jan when ja will do
1958 value = value.toLowerCase();
1959 if (mDisplayedValues[i].toLowerCase().startsWith(value)) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001960 return mMinValue + i;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001961 }
1962 }
1963
1964 /*
1965 * The user might have typed in a number into the month field i.e.
1966 * 10 instead of OCT so support that too.
1967 */
1968 try {
1969 return Integer.parseInt(value);
1970 } catch (NumberFormatException e) {
1971
1972 // Ignore as if it's not a number we don't care
1973 }
1974 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001975 return mMinValue;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001976 }
1977
1978 /**
1979 * Posts an {@link SetSelectionCommand} from the given <code>selectionStart
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001980 * </code> to <code>selectionEnd</code>.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001981 */
1982 private void postSetSelectionCommand(int selectionStart, int selectionEnd) {
1983 if (mSetSelectionCommand == null) {
1984 mSetSelectionCommand = new SetSelectionCommand();
1985 } else {
1986 removeCallbacks(mSetSelectionCommand);
1987 }
1988 mSetSelectionCommand.mSelectionStart = selectionStart;
1989 mSetSelectionCommand.mSelectionEnd = selectionEnd;
1990 post(mSetSelectionCommand);
1991 }
1992
1993 /**
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07001994 * The numbers accepted by the input text's {@link Filter}
1995 */
1996 private static final char[] DIGIT_CHARACTERS = new char[] {
1997 // Latin digits are the common case
1998 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1999 // Arabic-Indic
2000 '\u0660', '\u0661', '\u0662', '\u0663', '\u0664', '\u0665', '\u0666', '\u0667', '\u0668'
2001 , '\u0669',
2002 // Extended Arabic-Indic
2003 '\u06f0', '\u06f1', '\u06f2', '\u06f3', '\u06f4', '\u06f5', '\u06f6', '\u06f7', '\u06f8'
Martin Olsson 4962dde72013-07-05 07:07:33 +02002004 , '\u06f9',
2005 // Hindi and Marathi (Devanagari script)
2006 '\u0966', '\u0967', '\u0968', '\u0969', '\u096a', '\u096b', '\u096c', '\u096d', '\u096e'
2007 , '\u096f',
2008 // Bengali
2009 '\u09e6', '\u09e7', '\u09e8', '\u09e9', '\u09ea', '\u09eb', '\u09ec', '\u09ed', '\u09ee'
2010 , '\u09ef',
2011 // Kannada
2012 '\u0ce6', '\u0ce7', '\u0ce8', '\u0ce9', '\u0cea', '\u0ceb', '\u0cec', '\u0ced', '\u0cee'
2013 , '\u0cef'
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07002014 };
2015
2016 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002017 * Filter for accepting only valid indices or prefixes of the string
2018 * representation of valid indices.
2019 */
2020 class InputTextFilter extends NumberKeyListener {
2021
2022 // XXX This doesn't allow for range limits when controlled by a
2023 // soft input method!
2024 public int getInputType() {
2025 return InputType.TYPE_CLASS_TEXT;
2026 }
2027
2028 @Override
2029 protected char[] getAcceptedChars() {
2030 return DIGIT_CHARACTERS;
2031 }
2032
2033 @Override
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002034 public CharSequence filter(
2035 CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002036 if (mDisplayedValues == null) {
2037 CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
2038 if (filtered == null) {
2039 filtered = source.subSequence(start, end);
2040 }
2041
2042 String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
2043 + dest.subSequence(dend, dest.length());
2044
2045 if ("".equals(result)) {
2046 return result;
2047 }
2048 int val = getSelectedPos(result);
2049
2050 /*
2051 * Ensure the user can't type in a value greater than the max
2052 * allowed. We have to allow less than min as the user might
2053 * want to delete some numbers and then type a new number.
Sungmin Choi6d8a99f2013-01-25 18:26:46 +09002054 * And prevent multiple-"0" that exceeds the length of upper
2055 * bound number.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002056 */
Sungmin Choi6d8a99f2013-01-25 18:26:46 +09002057 if (val > mMaxValue || result.length() > String.valueOf(mMaxValue).length()) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002058 return "";
2059 } else {
2060 return filtered;
2061 }
2062 } else {
2063 CharSequence filtered = String.valueOf(source.subSequence(start, end));
2064 if (TextUtils.isEmpty(filtered)) {
2065 return "";
2066 }
2067 String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
2068 + dest.subSequence(dend, dest.length());
2069 String str = String.valueOf(result).toLowerCase();
2070 for (String val : mDisplayedValues) {
2071 String valLowerCase = val.toLowerCase();
2072 if (valLowerCase.startsWith(str)) {
2073 postSetSelectionCommand(result.length(), val.length());
2074 return val.subSequence(dstart, val.length());
2075 }
2076 }
2077 return "";
2078 }
2079 }
2080 }
2081
2082 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002083 * Ensures that the scroll wheel is adjusted i.e. there is no offset and the
2084 * middle element is in the middle of the widget.
2085 *
2086 * @return Whether an adjustment has been made.
2087 */
2088 private boolean ensureScrollWheelAdjusted() {
2089 // adjust to the closest value
2090 int deltaY = mInitialScrollOffset - mCurrentScrollOffset;
2091 if (deltaY != 0) {
2092 mPreviousScrollerY = 0;
2093 if (Math.abs(deltaY) > mSelectorElementHeight / 2) {
2094 deltaY += (deltaY > 0) ? -mSelectorElementHeight : mSelectorElementHeight;
2095 }
2096 mAdjustScroller.startScroll(0, 0, 0, deltaY, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
2097 invalidate();
2098 return true;
2099 }
2100 return false;
2101 }
2102
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07002103 class PressedStateHelper implements Runnable {
2104 public static final int BUTTON_INCREMENT = 1;
2105 public static final int BUTTON_DECREMENT = 2;
Svetoslav Ganovfe41ce42012-04-02 20:31:05 -07002106
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07002107 private final int MODE_PRESS = 1;
2108 private final int MODE_TAPPED = 2;
2109
2110 private int mManagedButton;
2111 private int mMode;
2112
2113 public void cancel() {
2114 mMode = 0;
2115 mManagedButton = 0;
2116 NumberPicker.this.removeCallbacks(this);
2117 if (mIncrementVirtualButtonPressed) {
2118 mIncrementVirtualButtonPressed = false;
2119 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
Svetoslav Ganovfe41ce42012-04-02 20:31:05 -07002120 }
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07002121 mDecrementVirtualButtonPressed = false;
2122 if (mDecrementVirtualButtonPressed) {
2123 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2124 }
2125 }
2126
2127 public void buttonPressDelayed(int button) {
2128 cancel();
2129 mMode = MODE_PRESS;
2130 mManagedButton = button;
2131 NumberPicker.this.postDelayed(this, ViewConfiguration.getTapTimeout());
2132 }
2133
2134 public void buttonTapped(int button) {
2135 cancel();
2136 mMode = MODE_TAPPED;
2137 mManagedButton = button;
2138 NumberPicker.this.post(this);
2139 }
2140
2141 @Override
2142 public void run() {
2143 switch (mMode) {
2144 case MODE_PRESS: {
2145 switch (mManagedButton) {
2146 case BUTTON_INCREMENT: {
2147 mIncrementVirtualButtonPressed = true;
2148 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
2149 } break;
2150 case BUTTON_DECREMENT: {
2151 mDecrementVirtualButtonPressed = true;
2152 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2153 }
2154 }
2155 } break;
2156 case MODE_TAPPED: {
2157 switch (mManagedButton) {
2158 case BUTTON_INCREMENT: {
2159 if (!mIncrementVirtualButtonPressed) {
2160 NumberPicker.this.postDelayed(this,
2161 ViewConfiguration.getPressedStateDuration());
2162 }
2163 mIncrementVirtualButtonPressed ^= true;
2164 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
2165 } break;
2166 case BUTTON_DECREMENT: {
2167 if (!mDecrementVirtualButtonPressed) {
2168 NumberPicker.this.postDelayed(this,
2169 ViewConfiguration.getPressedStateDuration());
2170 }
2171 mDecrementVirtualButtonPressed ^= true;
2172 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2173 }
2174 }
2175 } break;
2176 }
Svetoslav Ganovfe41ce42012-04-02 20:31:05 -07002177 }
2178 }
2179
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002180 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002181 * Command for setting the input text selection.
2182 */
2183 class SetSelectionCommand implements Runnable {
2184 private int mSelectionStart;
2185
2186 private int mSelectionEnd;
2187
2188 public void run() {
2189 mInputText.setSelection(mSelectionStart, mSelectionEnd);
2190 }
2191 }
2192
2193 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07002194 * Command for changing the current value from a long press by one.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002195 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07002196 class ChangeCurrentByOneFromLongPressCommand implements Runnable {
2197 private boolean mIncrement;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002198
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002199 private void setStep(boolean increment) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07002200 mIncrement = increment;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002201 }
2202
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002203 @Override
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002204 public void run() {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002205 changeValueByOne(mIncrement);
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08002206 postDelayed(this, mLongPressUpdateInterval);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002207 }
2208 }
Svetoslav Ganova2b41b42012-02-27 15:53:32 -08002209
2210 /**
2211 * @hide
2212 */
2213 public static class CustomEditText extends EditText {
2214
2215 public CustomEditText(Context context, AttributeSet attrs) {
2216 super(context, attrs);
2217 }
2218
2219 @Override
2220 public void onEditorAction(int actionCode) {
2221 super.onEditorAction(actionCode);
2222 if (actionCode == EditorInfo.IME_ACTION_DONE) {
2223 clearFocus();
2224 }
2225 }
2226 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002227
2228 /**
2229 * Command for beginning soft input on long press.
2230 */
2231 class BeginSoftInputOnLongPressCommand implements Runnable {
2232
2233 @Override
2234 public void run() {
Svetoslav46a27ef2014-03-03 15:37:14 -08002235 performLongClick();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002236 }
2237 }
2238
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002239 /**
2240 * Class for managing virtual view tree rooted at this picker.
2241 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002242 class AccessibilityNodeProviderImpl extends AccessibilityNodeProvider {
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002243 private static final int UNDEFINED = Integer.MIN_VALUE;
2244
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002245 private static final int VIRTUAL_VIEW_ID_INCREMENT = 1;
2246
2247 private static final int VIRTUAL_VIEW_ID_INPUT = 2;
2248
2249 private static final int VIRTUAL_VIEW_ID_DECREMENT = 3;
2250
2251 private final Rect mTempRect = new Rect();
2252
2253 private final int[] mTempArray = new int[2];
2254
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002255 private int mAccessibilityFocusedView = UNDEFINED;
2256
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002257 @Override
2258 public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
2259 switch (virtualViewId) {
2260 case View.NO_ID:
2261 return createAccessibilityNodeInfoForNumberPicker( mScrollX, mScrollY,
2262 mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop));
2263 case VIRTUAL_VIEW_ID_DECREMENT:
2264 return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_DECREMENT,
2265 getVirtualDecrementButtonText(), mScrollX, mScrollY,
2266 mScrollX + (mRight - mLeft),
2267 mTopSelectionDividerTop + mSelectionDividerHeight);
2268 case VIRTUAL_VIEW_ID_INPUT:
Alan Viverette0e2d2812013-05-21 17:15:30 -07002269 return createAccessibiltyNodeInfoForInputText(mScrollX,
2270 mTopSelectionDividerTop + mSelectionDividerHeight,
2271 mScrollX + (mRight - mLeft),
2272 mBottomSelectionDividerBottom - mSelectionDividerHeight);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002273 case VIRTUAL_VIEW_ID_INCREMENT:
2274 return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_INCREMENT,
2275 getVirtualIncrementButtonText(), mScrollX,
2276 mBottomSelectionDividerBottom - mSelectionDividerHeight,
2277 mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop));
2278 }
2279 return super.createAccessibilityNodeInfo(virtualViewId);
2280 }
2281
2282 @Override
2283 public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String searched,
2284 int virtualViewId) {
2285 if (TextUtils.isEmpty(searched)) {
2286 return Collections.emptyList();
2287 }
2288 String searchedLowerCase = searched.toLowerCase();
2289 List<AccessibilityNodeInfo> result = new ArrayList<AccessibilityNodeInfo>();
2290 switch (virtualViewId) {
2291 case View.NO_ID: {
2292 findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
2293 VIRTUAL_VIEW_ID_DECREMENT, result);
2294 findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
2295 VIRTUAL_VIEW_ID_INPUT, result);
2296 findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
2297 VIRTUAL_VIEW_ID_INCREMENT, result);
2298 return result;
2299 }
2300 case VIRTUAL_VIEW_ID_DECREMENT:
2301 case VIRTUAL_VIEW_ID_INCREMENT:
2302 case VIRTUAL_VIEW_ID_INPUT: {
2303 findAccessibilityNodeInfosByTextInChild(searchedLowerCase, virtualViewId,
2304 result);
2305 return result;
2306 }
2307 }
2308 return super.findAccessibilityNodeInfosByText(searched, virtualViewId);
2309 }
2310
2311 @Override
Svetoslav Ganovaa780c12012-04-19 23:01:39 -07002312 public boolean performAction(int virtualViewId, int action, Bundle arguments) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002313 switch (virtualViewId) {
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002314 case View.NO_ID: {
2315 switch (action) {
2316 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
2317 if (mAccessibilityFocusedView != virtualViewId) {
2318 mAccessibilityFocusedView = virtualViewId;
2319 requestAccessibilityFocus();
2320 return true;
2321 }
2322 } return false;
2323 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
2324 if (mAccessibilityFocusedView == virtualViewId) {
2325 mAccessibilityFocusedView = UNDEFINED;
2326 clearAccessibilityFocus();
2327 return true;
2328 }
2329 return false;
2330 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07002331 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002332 if (NumberPicker.this.isEnabled()
2333 && (getWrapSelectorWheel() || getValue() < getMaxValue())) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07002334 changeValueByOne(true);
2335 return true;
2336 }
2337 } return false;
2338 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002339 if (NumberPicker.this.isEnabled()
2340 && (getWrapSelectorWheel() || getValue() > getMinValue())) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07002341 changeValueByOne(false);
2342 return true;
2343 }
2344 } return false;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002345 }
2346 } break;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002347 case VIRTUAL_VIEW_ID_INPUT: {
2348 switch (action) {
2349 case AccessibilityNodeInfo.ACTION_FOCUS: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002350 if (NumberPicker.this.isEnabled() && !mInputText.isFocused()) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002351 return mInputText.requestFocus();
2352 }
2353 } break;
2354 case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002355 if (NumberPicker.this.isEnabled() && mInputText.isFocused()) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002356 mInputText.clearFocus();
2357 return true;
2358 }
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002359 return false;
2360 }
2361 case AccessibilityNodeInfo.ACTION_CLICK: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002362 if (NumberPicker.this.isEnabled()) {
Svetoslav46a27ef2014-03-03 15:37:14 -08002363 performClick();
2364 return true;
2365 }
2366 return false;
2367 }
2368 case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
2369 if (NumberPicker.this.isEnabled()) {
2370 performLongClick();
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002371 return true;
2372 }
2373 return false;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002374 }
2375 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
2376 if (mAccessibilityFocusedView != virtualViewId) {
2377 mAccessibilityFocusedView = virtualViewId;
2378 sendAccessibilityEventForVirtualView(virtualViewId,
2379 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
2380 mInputText.invalidate();
2381 return true;
2382 }
2383 } return false;
2384 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
2385 if (mAccessibilityFocusedView == virtualViewId) {
2386 mAccessibilityFocusedView = UNDEFINED;
2387 sendAccessibilityEventForVirtualView(virtualViewId,
2388 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
2389 mInputText.invalidate();
2390 return true;
2391 }
2392 } return false;
2393 default: {
2394 return mInputText.performAccessibilityAction(action, arguments);
2395 }
2396 }
2397 } return false;
2398 case VIRTUAL_VIEW_ID_INCREMENT: {
2399 switch (action) {
2400 case AccessibilityNodeInfo.ACTION_CLICK: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002401 if (NumberPicker.this.isEnabled()) {
2402 NumberPicker.this.changeValueByOne(true);
2403 sendAccessibilityEventForVirtualView(virtualViewId,
2404 AccessibilityEvent.TYPE_VIEW_CLICKED);
2405 return true;
2406 }
2407 } return false;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002408 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
2409 if (mAccessibilityFocusedView != virtualViewId) {
2410 mAccessibilityFocusedView = virtualViewId;
2411 sendAccessibilityEventForVirtualView(virtualViewId,
2412 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
2413 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
2414 return true;
2415 }
2416 } return false;
2417 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
2418 if (mAccessibilityFocusedView == virtualViewId) {
2419 mAccessibilityFocusedView = UNDEFINED;
2420 sendAccessibilityEventForVirtualView(virtualViewId,
2421 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
2422 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
2423 return true;
2424 }
2425 } return false;
2426 }
2427 } return false;
2428 case VIRTUAL_VIEW_ID_DECREMENT: {
2429 switch (action) {
2430 case AccessibilityNodeInfo.ACTION_CLICK: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002431 if (NumberPicker.this.isEnabled()) {
2432 final boolean increment = (virtualViewId == VIRTUAL_VIEW_ID_INCREMENT);
2433 NumberPicker.this.changeValueByOne(increment);
2434 sendAccessibilityEventForVirtualView(virtualViewId,
2435 AccessibilityEvent.TYPE_VIEW_CLICKED);
2436 return true;
2437 }
2438 } return false;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002439 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
2440 if (mAccessibilityFocusedView != virtualViewId) {
2441 mAccessibilityFocusedView = virtualViewId;
2442 sendAccessibilityEventForVirtualView(virtualViewId,
2443 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
2444 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2445 return true;
2446 }
2447 } return false;
2448 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
2449 if (mAccessibilityFocusedView == virtualViewId) {
2450 mAccessibilityFocusedView = UNDEFINED;
2451 sendAccessibilityEventForVirtualView(virtualViewId,
2452 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
2453 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2454 return true;
2455 }
2456 } return false;
2457 }
2458 } return false;
2459 }
2460 return super.performAction(virtualViewId, action, arguments);
2461 }
2462
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002463 public void sendAccessibilityEventForVirtualView(int virtualViewId, int eventType) {
2464 switch (virtualViewId) {
2465 case VIRTUAL_VIEW_ID_DECREMENT: {
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002466 if (hasVirtualDecrementButton()) {
2467 sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
2468 getVirtualDecrementButtonText());
2469 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002470 } break;
2471 case VIRTUAL_VIEW_ID_INPUT: {
2472 sendAccessibilityEventForVirtualText(eventType);
2473 } break;
2474 case VIRTUAL_VIEW_ID_INCREMENT: {
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002475 if (hasVirtualIncrementButton()) {
2476 sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
2477 getVirtualIncrementButtonText());
2478 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002479 } break;
2480 }
2481 }
2482
2483 private void sendAccessibilityEventForVirtualText(int eventType) {
Svetoslav Ganova9092762012-09-06 19:57:00 -07002484 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2485 AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
2486 mInputText.onInitializeAccessibilityEvent(event);
2487 mInputText.onPopulateAccessibilityEvent(event);
2488 event.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
2489 requestSendAccessibilityEvent(NumberPicker.this, event);
2490 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002491 }
2492
2493 private void sendAccessibilityEventForVirtualButton(int virtualViewId, int eventType,
2494 String text) {
Svetoslav Ganova9092762012-09-06 19:57:00 -07002495 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2496 AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
2497 event.setClassName(Button.class.getName());
2498 event.setPackageName(mContext.getPackageName());
2499 event.getText().add(text);
2500 event.setEnabled(NumberPicker.this.isEnabled());
2501 event.setSource(NumberPicker.this, virtualViewId);
2502 requestSendAccessibilityEvent(NumberPicker.this, event);
2503 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002504 }
2505
2506 private void findAccessibilityNodeInfosByTextInChild(String searchedLowerCase,
2507 int virtualViewId, List<AccessibilityNodeInfo> outResult) {
2508 switch (virtualViewId) {
2509 case VIRTUAL_VIEW_ID_DECREMENT: {
2510 String text = getVirtualDecrementButtonText();
2511 if (!TextUtils.isEmpty(text)
2512 && text.toString().toLowerCase().contains(searchedLowerCase)) {
2513 outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT));
2514 }
2515 } return;
2516 case VIRTUAL_VIEW_ID_INPUT: {
2517 CharSequence text = mInputText.getText();
2518 if (!TextUtils.isEmpty(text) &&
2519 text.toString().toLowerCase().contains(searchedLowerCase)) {
2520 outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT));
2521 return;
2522 }
2523 CharSequence contentDesc = mInputText.getText();
2524 if (!TextUtils.isEmpty(contentDesc) &&
2525 contentDesc.toString().toLowerCase().contains(searchedLowerCase)) {
2526 outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT));
2527 return;
2528 }
2529 } break;
2530 case VIRTUAL_VIEW_ID_INCREMENT: {
2531 String text = getVirtualIncrementButtonText();
2532 if (!TextUtils.isEmpty(text)
2533 && text.toString().toLowerCase().contains(searchedLowerCase)) {
2534 outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT));
2535 }
2536 } return;
2537 }
2538 }
2539
Alan Viverette0e2d2812013-05-21 17:15:30 -07002540 private AccessibilityNodeInfo createAccessibiltyNodeInfoForInputText(
2541 int left, int top, int right, int bottom) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002542 AccessibilityNodeInfo info = mInputText.createAccessibilityNodeInfo();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002543 info.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002544 if (mAccessibilityFocusedView != VIRTUAL_VIEW_ID_INPUT) {
2545 info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
2546 }
2547 if (mAccessibilityFocusedView == VIRTUAL_VIEW_ID_INPUT) {
2548 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
2549 }
Alan Viverette0e2d2812013-05-21 17:15:30 -07002550 Rect boundsInParent = mTempRect;
2551 boundsInParent.set(left, top, right, bottom);
2552 info.setVisibleToUser(isVisibleToUser(boundsInParent));
2553 info.setBoundsInParent(boundsInParent);
2554 Rect boundsInScreen = boundsInParent;
2555 int[] locationOnScreen = mTempArray;
2556 getLocationOnScreen(locationOnScreen);
2557 boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
2558 info.setBoundsInScreen(boundsInScreen);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002559 return info;
2560 }
2561
2562 private AccessibilityNodeInfo createAccessibilityNodeInfoForVirtualButton(int virtualViewId,
2563 String text, int left, int top, int right, int bottom) {
2564 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
2565 info.setClassName(Button.class.getName());
2566 info.setPackageName(mContext.getPackageName());
2567 info.setSource(NumberPicker.this, virtualViewId);
2568 info.setParent(NumberPicker.this);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002569 info.setText(text);
2570 info.setClickable(true);
2571 info.setLongClickable(true);
2572 info.setEnabled(NumberPicker.this.isEnabled());
2573 Rect boundsInParent = mTempRect;
2574 boundsInParent.set(left, top, right, bottom);
Guang Zhu0d607fb2012-05-11 19:34:56 -07002575 info.setVisibleToUser(isVisibleToUser(boundsInParent));
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002576 info.setBoundsInParent(boundsInParent);
2577 Rect boundsInScreen = boundsInParent;
2578 int[] locationOnScreen = mTempArray;
2579 getLocationOnScreen(locationOnScreen);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002580 boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
2581 info.setBoundsInScreen(boundsInScreen);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002582
2583 if (mAccessibilityFocusedView != virtualViewId) {
2584 info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
2585 }
2586 if (mAccessibilityFocusedView == virtualViewId) {
2587 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
2588 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002589 if (NumberPicker.this.isEnabled()) {
2590 info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
2591 }
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002592
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002593 return info;
2594 }
2595
2596 private AccessibilityNodeInfo createAccessibilityNodeInfoForNumberPicker(int left, int top,
2597 int right, int bottom) {
2598 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
Guang Zhu0d607fb2012-05-11 19:34:56 -07002599 info.setClassName(NumberPicker.class.getName());
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002600 info.setPackageName(mContext.getPackageName());
2601 info.setSource(NumberPicker.this);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002602
2603 if (hasVirtualDecrementButton()) {
2604 info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_DECREMENT);
2605 }
Guang Zhu0d607fb2012-05-11 19:34:56 -07002606 info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002607 if (hasVirtualIncrementButton()) {
2608 info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INCREMENT);
2609 }
2610
Svetoslav Ganov4528b4e2012-05-15 18:24:10 -07002611 info.setParent((View) getParentForAccessibility());
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002612 info.setEnabled(NumberPicker.this.isEnabled());
2613 info.setScrollable(true);
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002614
2615 final float applicationScale =
2616 getContext().getResources().getCompatibilityInfo().applicationScale;
2617
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002618 Rect boundsInParent = mTempRect;
2619 boundsInParent.set(left, top, right, bottom);
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002620 boundsInParent.scale(applicationScale);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002621 info.setBoundsInParent(boundsInParent);
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002622
Guang Zhu0d607fb2012-05-11 19:34:56 -07002623 info.setVisibleToUser(isVisibleToUser());
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002624
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002625 Rect boundsInScreen = boundsInParent;
2626 int[] locationOnScreen = mTempArray;
2627 getLocationOnScreen(locationOnScreen);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002628 boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002629 boundsInScreen.scale(applicationScale);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002630 info.setBoundsInScreen(boundsInScreen);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002631
2632 if (mAccessibilityFocusedView != View.NO_ID) {
2633 info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
2634 }
2635 if (mAccessibilityFocusedView == View.NO_ID) {
2636 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
2637 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002638 if (NumberPicker.this.isEnabled()) {
2639 if (getWrapSelectorWheel() || getValue() < getMaxValue()) {
2640 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
2641 }
2642 if (getWrapSelectorWheel() || getValue() > getMinValue()) {
2643 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
2644 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07002645 }
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002646
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002647 return info;
2648 }
2649
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002650 private boolean hasVirtualDecrementButton() {
2651 return getWrapSelectorWheel() || getValue() > getMinValue();
2652 }
2653
2654 private boolean hasVirtualIncrementButton() {
2655 return getWrapSelectorWheel() || getValue() < getMaxValue();
2656 }
2657
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002658 private String getVirtualDecrementButtonText() {
2659 int value = mValue - 1;
2660 if (mWrapSelectorWheel) {
2661 value = getWrappedSelectorIndex(value);
2662 }
2663 if (value >= mMinValue) {
2664 return (mDisplayedValues == null) ? formatNumber(value)
2665 : mDisplayedValues[value - mMinValue];
2666 }
2667 return null;
2668 }
2669
2670 private String getVirtualIncrementButtonText() {
2671 int value = mValue + 1;
2672 if (mWrapSelectorWheel) {
2673 value = getWrappedSelectorIndex(value);
2674 }
2675 if (value <= mMaxValue) {
2676 return (mDisplayedValues == null) ? formatNumber(value)
2677 : mDisplayedValues[value - mMinValue];
2678 }
2679 return null;
2680 }
2681 }
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07002682
2683 static private String formatNumberWithLocale(int value) {
2684 return String.format(Locale.getDefault(), "%d", value);
2685 }
Paul Westbrook7762d932009-12-11 14:13:48 -08002686}