blob: c0fde2e51fb79eb02df8fc1d2bebd7dbb251a05d [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
Paul Westbrook68f2f542010-01-13 12:13:57 -080019import android.annotation.Widget;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.content.Context;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080021import android.content.res.ColorStateList;
22import android.content.res.TypedArray;
23import android.graphics.Canvas;
24import android.graphics.Color;
25import android.graphics.Paint;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080026import android.graphics.Paint.Align;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -070027import android.graphics.Rect;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -080028import android.graphics.drawable.Drawable;
Svetoslav Ganovaa780c12012-04-19 23:01:39 -070029import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.text.InputFilter;
31import android.text.InputType;
32import android.text.Spanned;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080033import android.text.TextUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.text.method.NumberKeyListener;
35import android.util.AttributeSet;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080036import android.util.SparseArray;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -080037import android.util.TypedValue;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080038import android.view.KeyEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.view.LayoutInflater;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -070040import android.view.LayoutInflater.Filter;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080041import android.view.MotionEvent;
42import android.view.VelocityTracker;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043import android.view.View;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080044import android.view.ViewConfiguration;
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -070045import android.view.accessibility.AccessibilityEvent;
46import android.view.accessibility.AccessibilityManager;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080047import android.view.accessibility.AccessibilityNodeInfo;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -070048import android.view.accessibility.AccessibilityNodeProvider;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -080049import android.view.animation.DecelerateInterpolator;
Svetoslav Ganova2b41b42012-02-27 15:53:32 -080050import android.view.inputmethod.EditorInfo;
Svetoslav Ganov206316a2010-11-19 00:04:05 -080051import android.view.inputmethod.InputMethodManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -070053import com.android.internal.R;
Fabrice Di Megliod88e3052012-09-21 12:15:23 -070054import libcore.icu.LocaleData;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -070055
Svetoslav Ganovd11e6152012-03-20 12:13:02 -070056import java.util.ArrayList;
57import java.util.Collections;
58import java.util.List;
Fabrice Di Megliod88e3052012-09-21 12:15:23 -070059import java.util.Locale;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -070060
Paul Westbrook68f2f542010-01-13 12:13:57 -080061/**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -080062 * A widget that enables the user to select a number form a predefined range.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -070063 * There are two flavors of this widget and which one is presented to the user
64 * depends on the current theme.
65 * <ul>
66 * <li>
67 * If the current theme is derived from {@link android.R.style#Theme} the widget
68 * presents the current value as an editable input field with an increment button
69 * above and a decrement button below. Long pressing the buttons allows for a quick
70 * change of the current value. Tapping on the input field allows to type in
71 * a desired value.
72 * </li>
73 * <li>
74 * If the current theme is derived from {@link android.R.style#Theme_Holo} or
75 * {@link android.R.style#Theme_Holo_Light} the widget presents the current
76 * value as an editable input field with a lesser value above and a greater
77 * value below. Tapping on the lesser or greater value selects it by animating
78 * the number axis up or down to make the chosen value current. Flinging up
79 * or down allows for multiple increments or decrements of the current value.
80 * Long pressing on the lesser and greater values also allows for a quick change
81 * of the current value. Tapping on the current value allows to type in a
82 * desired value.
83 * </li>
84 * </ul>
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -080085 * <p>
86 * For an example of using this widget, see {@link android.widget.TimePicker}.
87 * </p>
Paul Westbrook68f2f542010-01-13 12:13:57 -080088 */
89@Widget
90public class NumberPicker extends LinearLayout {
Tom Taylorfdf6db62009-09-09 11:37:58 -070091
Paul Westbrook68f2f542010-01-13 12:13:57 -080092 /**
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -080093 * The number of items show in the selector wheel.
94 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -070095 private static final int SELECTOR_WHEEL_ITEM_COUNT = 3;
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -080096
97 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080098 * The default update interval during long press.
99 */
100 private static final long DEFAULT_LONG_PRESS_UPDATE_INTERVAL = 300;
101
102 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800103 * The index of the middle selector item.
Paul Westbrook68f2f542010-01-13 12:13:57 -0800104 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700105 private static final int SELECTOR_MIDDLE_ITEM_INDEX = SELECTOR_WHEEL_ITEM_COUNT / 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106
Paul Westbrook68f2f542010-01-13 12:13:57 -0800107 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800108 * The coefficient by which to adjust (divide) the max fling velocity.
Paul Westbrook68f2f542010-01-13 12:13:57 -0800109 */
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800110 private static final int SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT = 8;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800112 /**
113 * The the duration for adjusting the selector wheel.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 */
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800115 private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800;
Tom Taylorfdf6db62009-09-09 11:37:58 -0700116
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800117 /**
Svetoslav Ganov42138042012-03-20 11:51:39 -0700118 * The duration of scrolling while snapping to a given position.
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700119 */
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -0700120 private static final int SNAP_SCROLL_DURATION = 300;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700121
122 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800123 * The strength of fading in the top and bottom while drawing the selector.
124 */
125 private static final float TOP_AND_BOTTOM_FADING_EDGE_STRENGTH = 0.9f;
126
127 /**
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800128 * The default unscaled height of the selection divider.
129 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700130 private static final int UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT = 2;
131
132 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700133 * The default unscaled distance between the selection dividers.
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700134 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700135 private static final int UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE = 48;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700136
137 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700138 * The resource id for the default layout.
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700139 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700140 private static final int DEFAULT_LAYOUT_RESOURCE_ID = R.layout.number_picker;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800141
142 /**
Svetoslav Ganov9f086d82011-11-29 18:27:23 -0800143 * Constant for unspecified size.
144 */
145 private static final int SIZE_UNSPECIFIED = -1;
146
147 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800148 * Use a custom NumberPicker formatting callback to use two-digit minutes
149 * strings like "01". Keeping a static formatter etc. is the most efficient
150 * way to do this; it avoids creating temporary objects on every call to
151 * format().
152 */
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700153 private static class TwoDigitFormatter implements NumberPicker.Formatter {
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800154 final StringBuilder mBuilder = new StringBuilder();
155
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700156 char mZeroDigit;
157 java.util.Formatter mFmt;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800158
159 final Object[] mArgs = new Object[1];
160
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700161 TwoDigitFormatter() {
162 final Locale locale = Locale.getDefault();
163 init(locale);
164 }
165
166 private void init(Locale locale) {
167 mFmt = createFormatter(locale);
168 mZeroDigit = getZeroDigit(locale);
169 }
170
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800171 public String format(int value) {
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700172 final Locale currentLocale = Locale.getDefault();
173 if (mZeroDigit != getZeroDigit(currentLocale)) {
174 init(currentLocale);
175 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800176 mArgs[0] = value;
177 mBuilder.delete(0, mBuilder.length());
178 mFmt.format("%02d", mArgs);
179 return mFmt.toString();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180 }
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700181
182 private static char getZeroDigit(Locale locale) {
183 return LocaleData.get(locale).zeroDigit;
184 }
185
186 private java.util.Formatter createFormatter(Locale locale) {
187 return new java.util.Formatter(mBuilder, locale);
188 }
189 }
190
191 private static final TwoDigitFormatter sTwoDigitFormatter = new TwoDigitFormatter();
192
Fabrice Di Meglioa65fe882012-09-23 11:54:51 -0700193 /**
194 * @hide
195 */
196 public static final Formatter getTwoDigitFormatter() {
Fabrice Di Megliod88e3052012-09-21 12:15:23 -0700197 return sTwoDigitFormatter;
198 }
199
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800200 /**
201 * The increment button.
202 */
203 private final ImageButton mIncrementButton;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800204
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800205 /**
206 * The decrement button.
207 */
208 private final ImageButton mDecrementButton;
209
210 /**
211 * The text for showing the current value.
212 */
213 private final EditText mInputText;
214
215 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700216 * The distance between the two selection dividers.
217 */
218 private final int mSelectionDividersDistance;
219
220 /**
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700221 * The min height of this widget.
222 */
223 private final int mMinHeight;
224
225 /**
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700226 * The max height of this widget.
227 */
228 private final int mMaxHeight;
229
230 /**
231 * The max width of this widget.
232 */
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700233 private final int mMinWidth;
234
235 /**
236 * The max width of this widget.
237 */
238 private int mMaxWidth;
239
240 /**
241 * Flag whether to compute the max width.
242 */
243 private final boolean mComputeMaxWidth;
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700244
245 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800246 * The height of the text.
247 */
248 private final int mTextSize;
249
250 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700251 * The height of the gap between text elements if the selector wheel.
252 */
253 private int mSelectorTextGapHeight;
254
255 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800256 * The values to be displayed instead the indices.
257 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800258 private String[] mDisplayedValues;
Paul Westbrook68f2f542010-01-13 12:13:57 -0800259
260 /**
261 * Lower value of the range of numbers allowed for the NumberPicker
262 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800263 private int mMinValue;
Paul Westbrook68f2f542010-01-13 12:13:57 -0800264
265 /**
266 * Upper value of the range of numbers allowed for the NumberPicker
267 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800268 private int mMaxValue;
Paul Westbrook68f2f542010-01-13 12:13:57 -0800269
270 /**
271 * Current value of this NumberPicker
272 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800273 private int mValue;
Paul Westbrook68f2f542010-01-13 12:13:57 -0800274
275 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800276 * Listener to be notified upon current value change.
Paul Westbrook68f2f542010-01-13 12:13:57 -0800277 */
Svetoslav Ganovcedc4462011-01-19 19:25:46 -0800278 private OnValueChangeListener mOnValueChangeListener;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800279
280 /**
281 * Listener to be notified upon scroll state change.
282 */
283 private OnScrollListener mOnScrollListener;
Tom Taylorfdf6db62009-09-09 11:37:58 -0700284
Paul Westbrook68f2f542010-01-13 12:13:57 -0800285 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800286 * Formatter for for displaying the current value.
Paul Westbrook68f2f542010-01-13 12:13:57 -0800287 */
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800288 private Formatter mFormatter;
289
290 /**
291 * The speed for updating the value form long press.
292 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800293 private long mLongPressUpdateInterval = DEFAULT_LONG_PRESS_UPDATE_INTERVAL;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800294
295 /**
296 * Cache for the string representation of selector indices.
297 */
298 private final SparseArray<String> mSelectorIndexToStringCache = new SparseArray<String>();
299
300 /**
301 * The selector indices whose value are show by the selector.
302 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700303 private final int[] mSelectorIndices = new int[SELECTOR_WHEEL_ITEM_COUNT];
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800304
305 /**
306 * The {@link Paint} for drawing the selector.
307 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700308 private final Paint mSelectorWheelPaint;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800309
310 /**
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700311 * The {@link Drawable} for pressed virtual (increment/decrement) buttons.
312 */
313 private final Drawable mVirtualButtonPressedDrawable;
314
315 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800316 * The height of a selector element (text + gap).
317 */
318 private int mSelectorElementHeight;
319
320 /**
321 * The initial offset of the scroll selector.
322 */
323 private int mInitialScrollOffset = Integer.MIN_VALUE;
324
325 /**
326 * The current offset of the scroll selector.
327 */
328 private int mCurrentScrollOffset;
329
330 /**
331 * The {@link Scroller} responsible for flinging the selector.
332 */
333 private final Scroller mFlingScroller;
334
335 /**
336 * The {@link Scroller} responsible for adjusting the selector.
337 */
338 private final Scroller mAdjustScroller;
339
340 /**
341 * The previous Y coordinate while scrolling the selector.
342 */
343 private int mPreviousScrollerY;
344
345 /**
346 * Handle to the reusable command for setting the input text selection.
347 */
348 private SetSelectionCommand mSetSelectionCommand;
349
350 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700351 * Handle to the reusable command for changing the current value from long
352 * press by one.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800353 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700354 private ChangeCurrentByOneFromLongPressCommand mChangeCurrentByOneFromLongPressCommand;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800355
356 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700357 * Command for beginning an edit of the current value via IME on long press.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800358 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700359 private BeginSoftInputOnLongPressCommand mBeginSoftInputOnLongPressCommand;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700360
361 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800362 * The Y position of the last down event.
363 */
364 private float mLastDownEventY;
365
366 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700367 * The time of the last down event.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800368 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700369 private long mLastDownEventTime;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800370
371 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700372 * The Y position of the last down or move event.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800373 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700374 private float mLastDownOrMoveEventY;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800375
376 /**
377 * Determines speed during touch scrolling.
378 */
379 private VelocityTracker mVelocityTracker;
380
381 /**
382 * @see ViewConfiguration#getScaledTouchSlop()
383 */
384 private int mTouchSlop;
385
386 /**
387 * @see ViewConfiguration#getScaledMinimumFlingVelocity()
388 */
389 private int mMinimumFlingVelocity;
390
391 /**
392 * @see ViewConfiguration#getScaledMaximumFlingVelocity()
393 */
394 private int mMaximumFlingVelocity;
395
396 /**
397 * Flag whether the selector should wrap around.
398 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800399 private boolean mWrapSelectorWheel;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800400
401 /**
402 * The back ground color used to optimize scroller fading.
403 */
404 private final int mSolidColor;
405
406 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700407 * Flag whether this widget has a selector wheel.
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800408 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700409 private final boolean mHasSelectorWheel;
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800410
411 /**
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800412 * Divider for showing item to be selected while scrolling
413 */
414 private final Drawable mSelectionDivider;
415
416 /**
417 * The height of the selection divider.
418 */
419 private final int mSelectionDividerHeight;
420
421 /**
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800422 * The current scroll state of the number picker.
423 */
424 private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
425
426 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700427 * Flag whether to ignore move events - we ignore such when we show in IME
428 * to prevent the content from scrolling.
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800429 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700430 private boolean mIngonreMoveEvents;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800431
432 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700433 * Flag whether to show soft input on tap.
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700434 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700435 private boolean mShowSoftInputOnTap;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700436
437 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700438 * The top of the top selection divider.
Svetoslav Ganova2b41b42012-02-27 15:53:32 -0800439 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700440 private int mTopSelectionDividerTop;
441
442 /**
443 * The bottom of the bottom selection divider.
444 */
445 private int mBottomSelectionDividerBottom;
446
447 /**
448 * The virtual id of the last hovered child.
449 */
450 private int mLastHoveredChildVirtualViewId;
451
452 /**
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700453 * Whether the increment virtual button is pressed.
454 */
455 private boolean mIncrementVirtualButtonPressed;
456
457 /**
458 * Whether the decrement virtual button is pressed.
459 */
460 private boolean mDecrementVirtualButtonPressed;
461
462 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700463 * Provider to report to clients the semantic structure of this widget.
464 */
465 private AccessibilityNodeProviderImpl mAccessibilityNodeProvider;
Svetoslav Ganova2b41b42012-02-27 15:53:32 -0800466
467 /**
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700468 * Helper class for managing pressed state of the virtual buttons.
469 */
470 private final PressedStateHelper mPressedStateHelper;
471
472 /**
Svetoslav Ganov5dc21d92012-10-07 18:54:42 -0700473 * The keycode of the last handled DPAD down event.
474 */
475 private int mLastHandledDownDpadKeyCode = -1;
476
477 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800478 * Interface to listen for changes of the current value.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800479 */
Svetoslav Ganovcedc4462011-01-19 19:25:46 -0800480 public interface OnValueChangeListener {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800481
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800482 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800483 * Called upon a change of the current value.
484 *
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800485 * @param picker The NumberPicker associated with this listener.
486 * @param oldVal The previous value.
487 * @param newVal The new value.
488 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800489 void onValueChange(NumberPicker picker, int oldVal, int newVal);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800490 }
491
492 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800493 * Interface to listen for the picker scroll state.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800494 */
495 public interface OnScrollListener {
496
497 /**
498 * The view is not scrolling.
499 */
500 public static int SCROLL_STATE_IDLE = 0;
501
502 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700503 * The user is scrolling using touch, and his finger is still on the screen.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800504 */
505 public static int SCROLL_STATE_TOUCH_SCROLL = 1;
506
507 /**
508 * The user had previously been scrolling using touch and performed a fling.
509 */
510 public static int SCROLL_STATE_FLING = 2;
511
512 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800513 * Callback invoked while the number picker scroll state has changed.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800514 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800515 * @param view The view whose scroll state is being reported.
516 * @param scrollState The current scroll state. One of
517 * {@link #SCROLL_STATE_IDLE},
518 * {@link #SCROLL_STATE_TOUCH_SCROLL} or
519 * {@link #SCROLL_STATE_IDLE}.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800520 */
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800521 public void onScrollStateChange(NumberPicker view, int scrollState);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800522 }
523
524 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800525 * Interface used to format current value into a string for presentation.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800526 */
527 public interface Formatter {
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800528
529 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800530 * Formats a string representation of the current value.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800531 *
532 * @param value The currently selected value.
533 * @return A formatted string representation.
534 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800535 public String format(int value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800536 }
Tom Taylorfdf6db62009-09-09 11:37:58 -0700537
Paul Westbrook68f2f542010-01-13 12:13:57 -0800538 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -0800539 * Create a new number picker.
540 *
541 * @param context The application environment.
542 */
543 public NumberPicker(Context context) {
544 this(context, null);
545 }
546
547 /**
548 * Create a new number picker.
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800549 *
550 * @param context The application environment.
551 * @param attrs A collection of attributes.
Paul Westbrook68f2f542010-01-13 12:13:57 -0800552 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800553 public NumberPicker(Context context, AttributeSet attrs) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800554 this(context, attrs, R.attr.numberPickerStyle);
555 }
556
557 /**
558 * Create a new number picker
559 *
560 * @param context the application environment.
561 * @param attrs a collection of attributes.
562 * @param defStyle The default style to apply to this view.
563 */
564 public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
565 super(context, attrs, defStyle);
566
567 // process style attributes
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700568 TypedArray attributesArray = context.obtainStyledAttributes(
569 attrs, R.styleable.NumberPicker, defStyle, 0);
570 final int layoutResId = attributesArray.getResourceId(
571 R.styleable.NumberPicker_internalLayout, DEFAULT_LAYOUT_RESOURCE_ID);
572
573 mHasSelectorWheel = (layoutResId != DEFAULT_LAYOUT_RESOURCE_ID);
574
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800575 mSolidColor = attributesArray.getColor(R.styleable.NumberPicker_solidColor, 0);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700576
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800577 mSelectionDivider = attributesArray.getDrawable(R.styleable.NumberPicker_selectionDivider);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700578
579 final int defSelectionDividerHeight = (int) TypedValue.applyDimension(
580 TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT,
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800581 getResources().getDisplayMetrics());
582 mSelectionDividerHeight = attributesArray.getDimensionPixelSize(
583 R.styleable.NumberPicker_selectionDividerHeight, defSelectionDividerHeight);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700584
585 final int defSelectionDividerDistance = (int) TypedValue.applyDimension(
586 TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE,
587 getResources().getDisplayMetrics());
588 mSelectionDividersDistance = attributesArray.getDimensionPixelSize(
589 R.styleable.NumberPicker_selectionDividersDistance, defSelectionDividerDistance);
590
Svetoslav Ganove8331bd2012-03-02 15:15:35 -0800591 mMinHeight = attributesArray.getDimensionPixelSize(
592 R.styleable.NumberPicker_internalMinHeight, SIZE_UNSPECIFIED);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700593
Svetoslav Ganove8331bd2012-03-02 15:15:35 -0800594 mMaxHeight = attributesArray.getDimensionPixelSize(
595 R.styleable.NumberPicker_internalMaxHeight, SIZE_UNSPECIFIED);
Svetoslav Ganov9f086d82011-11-29 18:27:23 -0800596 if (mMinHeight != SIZE_UNSPECIFIED && mMaxHeight != SIZE_UNSPECIFIED
597 && mMinHeight > mMaxHeight) {
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700598 throw new IllegalArgumentException("minHeight > maxHeight");
599 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700600
601 mMinWidth = attributesArray.getDimensionPixelSize(
602 R.styleable.NumberPicker_internalMinWidth, SIZE_UNSPECIFIED);
603
604 mMaxWidth = attributesArray.getDimensionPixelSize(
605 R.styleable.NumberPicker_internalMaxWidth, SIZE_UNSPECIFIED);
Svetoslav Ganov9f086d82011-11-29 18:27:23 -0800606 if (mMinWidth != SIZE_UNSPECIFIED && mMaxWidth != SIZE_UNSPECIFIED
607 && mMinWidth > mMaxWidth) {
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700608 throw new IllegalArgumentException("minWidth > maxWidth");
609 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800610
Svetoslav Ganovf7c83bc2012-04-12 16:00:13 -0700611 mComputeMaxWidth = (mMaxWidth == SIZE_UNSPECIFIED);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700612
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700613 mVirtualButtonPressedDrawable = attributesArray.getDrawable(
614 R.styleable.NumberPicker_virtualButtonPressedDrawable);
615
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700616 attributesArray.recycle();
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800617
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700618 mPressedStateHelper = new PressedStateHelper();
619
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800620 // By default Linearlayout that we extend is not drawn. This is
621 // its draw() method is not called but dispatchDraw() is called
622 // directly (see ViewGroup.drawChild()). However, this class uses
623 // the fading edge effect implemented by View and we need our
624 // draw() method to be called. Therefore, we declare we will draw.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700625 setWillNotDraw(!mHasSelectorWheel);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800626
627 LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
628 Context.LAYOUT_INFLATER_SERVICE);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700629 inflater.inflate(layoutResId, this, true);
Paul Westbrook68f2f542010-01-13 12:13:57 -0800630
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800631 OnClickListener onClickListener = new OnClickListener() {
Paul Westbrook68f2f542010-01-13 12:13:57 -0800632 public void onClick(View v) {
Svetoslav Ganovb52d9722011-11-07 14:53:34 -0800633 hideSoftInput();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800634 mInputText.clearFocus();
635 if (v.getId() == R.id.increment) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700636 changeValueByOne(true);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800637 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700638 changeValueByOne(false);
Paul Westbrook68f2f542010-01-13 12:13:57 -0800639 }
640 }
641 };
642
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800643 OnLongClickListener onLongClickListener = new OnLongClickListener() {
Paul Westbrook68f2f542010-01-13 12:13:57 -0800644 public boolean onLongClick(View v) {
Svetoslav Ganovb52d9722011-11-07 14:53:34 -0800645 hideSoftInput();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800646 mInputText.clearFocus();
647 if (v.getId() == R.id.increment) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700648 postChangeCurrentByOneFromLongPress(true, 0);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800649 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700650 postChangeCurrentByOneFromLongPress(false, 0);
Paul Westbrook68f2f542010-01-13 12:13:57 -0800651 }
652 return true;
653 }
654 };
655
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800656 // increment button
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700657 if (!mHasSelectorWheel) {
658 mIncrementButton = (ImageButton) findViewById(R.id.increment);
659 mIncrementButton.setOnClickListener(onClickListener);
660 mIncrementButton.setOnLongClickListener(onLongClickListener);
661 } else {
662 mIncrementButton = null;
663 }
Paul Westbrook68f2f542010-01-13 12:13:57 -0800664
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800665 // decrement button
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700666 if (!mHasSelectorWheel) {
667 mDecrementButton = (ImageButton) findViewById(R.id.decrement);
668 mDecrementButton.setOnClickListener(onClickListener);
669 mDecrementButton.setOnLongClickListener(onLongClickListener);
670 } else {
671 mDecrementButton = null;
672 }
Tom Taylorfdf6db62009-09-09 11:37:58 -0700673
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800674 // input text
Svetoslav Ganov012dd5a2011-01-11 16:54:01 -0800675 mInputText = (EditText) findViewById(R.id.numberpicker_input);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800676 mInputText.setOnFocusChangeListener(new OnFocusChangeListener() {
677 public void onFocusChange(View v, boolean hasFocus) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700678 if (hasFocus) {
679 mInputText.selectAll();
680 } else {
681 mInputText.setSelection(0, 0);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800682 validateInputTextView(v);
683 }
684 }
685 });
686 mInputText.setFilters(new InputFilter[] {
687 new InputTextFilter()
688 });
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800689
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800690 mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
Svetoslav Ganova2b41b42012-02-27 15:53:32 -0800691 mInputText.setImeOptions(EditorInfo.IME_ACTION_DONE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800692
693 // initialize constants
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800694 ViewConfiguration configuration = ViewConfiguration.get(context);
695 mTouchSlop = configuration.getScaledTouchSlop();
696 mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
697 mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity()
698 / SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT;
699 mTextSize = (int) mInputText.getTextSize();
700
701 // create the selector wheel paint
702 Paint paint = new Paint();
703 paint.setAntiAlias(true);
704 paint.setTextAlign(Align.CENTER);
705 paint.setTextSize(mTextSize);
706 paint.setTypeface(mInputText.getTypeface());
707 ColorStateList colors = mInputText.getTextColors();
708 int color = colors.getColorForState(ENABLED_STATE_SET, Color.WHITE);
709 paint.setColor(color);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700710 mSelectorWheelPaint = paint;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800711
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800712 // create the fling and adjust scrollers
Svetoslav Ganovbf805622010-12-17 16:00:18 -0800713 mFlingScroller = new Scroller(getContext(), null, true);
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800714 mAdjustScroller = new Scroller(getContext(), new DecelerateInterpolator(2.5f));
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800715
716 updateInputTextView();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700717
718 // If not explicitly specified this view is important for accessibility.
719 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
720 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
721 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800722 }
723
724 @Override
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800725 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700726 if (!mHasSelectorWheel) {
727 super.onLayout(changed, left, top, right, bottom);
728 return;
729 }
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700730 final int msrdWdth = getMeasuredWidth();
731 final int msrdHght = getMeasuredHeight();
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700732
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700733 // Input text centered horizontally.
734 final int inptTxtMsrdWdth = mInputText.getMeasuredWidth();
735 final int inptTxtMsrdHght = mInputText.getMeasuredHeight();
736 final int inptTxtLeft = (msrdWdth - inptTxtMsrdWdth) / 2;
737 final int inptTxtTop = (msrdHght - inptTxtMsrdHght) / 2;
738 final int inptTxtRight = inptTxtLeft + inptTxtMsrdWdth;
739 final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght;
740 mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700741
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700742 if (changed) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700743 // need to do all this when we know our size
744 initializeSelectorWheel();
745 initializeFadingEdges();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700746 mTopSelectionDividerTop = (getHeight() - mSelectionDividersDistance) / 2
747 - mSelectionDividerHeight;
748 mBottomSelectionDividerBottom = mTopSelectionDividerTop + 2 * mSelectionDividerHeight
749 + mSelectionDividersDistance;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700750 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800751 }
752
753 @Override
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700754 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700755 if (!mHasSelectorWheel) {
756 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
757 return;
758 }
Svetoslav Ganov698e1d52011-11-07 18:43:01 -0800759 // Try greedily to fit the max width and height.
760 final int newWidthMeasureSpec = makeMeasureSpec(widthMeasureSpec, mMaxWidth);
761 final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMaxHeight);
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -0700762 super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec);
Svetoslav Ganov698e1d52011-11-07 18:43:01 -0800763 // Flag if we are measured with width or height less than the respective min.
Svetoslav Ganov9f086d82011-11-29 18:27:23 -0800764 final int widthSize = resolveSizeAndStateRespectingMinSize(mMinWidth, getMeasuredWidth(),
765 widthMeasureSpec);
766 final int heightSize = resolveSizeAndStateRespectingMinSize(mMinHeight, getMeasuredHeight(),
767 heightMeasureSpec);
Svetoslav Ganov698e1d52011-11-07 18:43:01 -0800768 setMeasuredDimension(widthSize, heightSize);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -0700769 }
770
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700771 /**
772 * Move to the final position of a scroller. Ensures to force finish the scroller
773 * and if it is not at its final position a scroll of the selector wheel is
774 * performed to fast forward to the final position.
775 *
776 * @param scroller The scroller to whose final position to get.
777 * @return True of the a move was performed, i.e. the scroller was not in final position.
778 */
779 private boolean moveToFinalScrollerPosition(Scroller scroller) {
780 scroller.forceFinished(true);
781 int amountToScroll = scroller.getFinalY() - scroller.getCurrY();
782 int futureScrollOffset = (mCurrentScrollOffset + amountToScroll) % mSelectorElementHeight;
783 int overshootAdjustment = mInitialScrollOffset - futureScrollOffset;
784 if (overshootAdjustment != 0) {
785 if (Math.abs(overshootAdjustment) > mSelectorElementHeight / 2) {
786 if (overshootAdjustment > 0) {
787 overshootAdjustment -= mSelectorElementHeight;
788 } else {
789 overshootAdjustment += mSelectorElementHeight;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800790 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700791 }
792 amountToScroll += overshootAdjustment;
793 scrollBy(0, amountToScroll);
794 return true;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800795 }
796 return false;
797 }
798
799 @Override
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700800 public boolean onInterceptTouchEvent(MotionEvent event) {
801 if (!mHasSelectorWheel || !isEnabled()) {
802 return false;
803 }
804 final int action = event.getActionMasked();
805 switch (action) {
806 case MotionEvent.ACTION_DOWN: {
807 removeAllCallbacks();
808 mInputText.setVisibility(View.INVISIBLE);
809 mLastDownOrMoveEventY = mLastDownEventY = event.getY();
810 mLastDownEventTime = event.getEventTime();
811 mIngonreMoveEvents = false;
812 mShowSoftInputOnTap = false;
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700813 // Handle pressed state before any state change.
814 if (mLastDownEventY < mTopSelectionDividerTop) {
815 if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
816 mPressedStateHelper.buttonPressDelayed(
817 PressedStateHelper.BUTTON_DECREMENT);
818 }
819 } else if (mLastDownEventY > mBottomSelectionDividerBottom) {
820 if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
821 mPressedStateHelper.buttonPressDelayed(
822 PressedStateHelper.BUTTON_INCREMENT);
823 }
824 }
825 // Make sure we support flinging inside scrollables.
Svetoslav Ganov83dc45c2012-04-12 16:19:32 -0700826 getParent().requestDisallowInterceptTouchEvent(true);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700827 if (!mFlingScroller.isFinished()) {
828 mFlingScroller.forceFinished(true);
829 mAdjustScroller.forceFinished(true);
830 onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
831 } else if (!mAdjustScroller.isFinished()) {
832 mFlingScroller.forceFinished(true);
833 mAdjustScroller.forceFinished(true);
834 } else if (mLastDownEventY < mTopSelectionDividerTop) {
835 hideSoftInput();
836 postChangeCurrentByOneFromLongPress(
837 false, ViewConfiguration.getLongPressTimeout());
838 } else if (mLastDownEventY > mBottomSelectionDividerBottom) {
839 hideSoftInput();
840 postChangeCurrentByOneFromLongPress(
841 true, ViewConfiguration.getLongPressTimeout());
842 } else {
843 mShowSoftInputOnTap = true;
844 postBeginSoftInputOnLongPressCommand();
845 }
846 return true;
847 }
848 }
849 return false;
850 }
851
852 @Override
853 public boolean onTouchEvent(MotionEvent event) {
854 if (!isEnabled() || !mHasSelectorWheel) {
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800855 return false;
856 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800857 if (mVelocityTracker == null) {
858 mVelocityTracker = VelocityTracker.obtain();
859 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700860 mVelocityTracker.addMovement(event);
861 int action = event.getActionMasked();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800862 switch (action) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700863 case MotionEvent.ACTION_MOVE: {
864 if (mIngonreMoveEvents) {
865 break;
866 }
867 float currentMoveY = event.getY();
868 if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800869 int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY);
870 if (deltaDownY > mTouchSlop) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700871 removeAllCallbacks();
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800872 onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800873 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700874 } else {
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700875 int deltaMoveY = (int) ((currentMoveY - mLastDownOrMoveEventY));
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700876 scrollBy(0, deltaMoveY);
877 invalidate();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800878 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700879 mLastDownOrMoveEventY = currentMoveY;
880 } break;
881 case MotionEvent.ACTION_UP: {
882 removeBeginSoftInputCommand();
883 removeChangeCurrentByOneFromLongPress();
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700884 mPressedStateHelper.cancel();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800885 VelocityTracker velocityTracker = mVelocityTracker;
886 velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
887 int initialVelocity = (int) velocityTracker.getYVelocity();
888 if (Math.abs(initialVelocity) > mMinimumFlingVelocity) {
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700889 fling(initialVelocity);
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -0800890 onScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800891 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700892 int eventY = (int) event.getY();
893 int deltaMoveY = (int) Math.abs(eventY - mLastDownEventY);
894 long deltaTime = event.getEventTime() - mLastDownEventTime;
895 if (deltaMoveY <= mTouchSlop && deltaTime < ViewConfiguration.getTapTimeout()) {
896 if (mShowSoftInputOnTap) {
897 mShowSoftInputOnTap = false;
898 showSoftInput();
899 } else {
900 int selectorIndexOffset = (eventY / mSelectorElementHeight)
901 - SELECTOR_MIDDLE_ITEM_INDEX;
902 if (selectorIndexOffset > 0) {
903 changeValueByOne(true);
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700904 mPressedStateHelper.buttonTapped(
905 PressedStateHelper.BUTTON_INCREMENT);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700906 } else if (selectorIndexOffset < 0) {
907 changeValueByOne(false);
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -0700908 mPressedStateHelper.buttonTapped(
909 PressedStateHelper.BUTTON_DECREMENT);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700910 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800911 }
912 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700913 ensureScrollWheelAdjusted();
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800914 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700915 onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800916 }
917 mVelocityTracker.recycle();
918 mVelocityTracker = null;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700919 } break;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800920 }
921 return true;
922 }
923
924 @Override
925 public boolean dispatchTouchEvent(MotionEvent event) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700926 final int action = event.getActionMasked();
927 switch (action) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -0700928 case MotionEvent.ACTION_CANCEL:
929 case MotionEvent.ACTION_UP:
930 removeAllCallbacks();
931 break;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800932 }
933 return super.dispatchTouchEvent(event);
934 }
935
936 @Override
937 public boolean dispatchKeyEvent(KeyEvent event) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700938 final int keyCode = event.getKeyCode();
939 switch (keyCode) {
940 case KeyEvent.KEYCODE_DPAD_CENTER:
941 case KeyEvent.KEYCODE_ENTER:
942 removeAllCallbacks();
943 break;
Svetoslav Ganov5dc21d92012-10-07 18:54:42 -0700944 case KeyEvent.KEYCODE_DPAD_DOWN:
945 case KeyEvent.KEYCODE_DPAD_UP:
946 if (!mHasSelectorWheel) {
947 break;
948 }
949 switch (event.getAction()) {
950 case KeyEvent.ACTION_DOWN:
Justin Mattson86cf0cd2012-10-08 11:46:28 -0700951 if (mWrapSelectorWheel || (keyCode == KeyEvent.KEYCODE_DPAD_DOWN)
Svetoslav Ganov5dc21d92012-10-07 18:54:42 -0700952 ? getValue() < getMaxValue() : getValue() > getMinValue()) {
953 requestFocus();
954 mLastHandledDownDpadKeyCode = keyCode;
955 removeAllCallbacks();
956 if (mFlingScroller.isFinished()) {
Justin Mattson86cf0cd2012-10-08 11:46:28 -0700957 changeValueByOne(keyCode == KeyEvent.KEYCODE_DPAD_DOWN);
Svetoslav Ganov5dc21d92012-10-07 18:54:42 -0700958 }
959 return true;
960 }
961 break;
962 case KeyEvent.ACTION_UP:
963 if (mLastHandledDownDpadKeyCode == keyCode) {
964 mLastHandledDownDpadKeyCode = -1;
965 return true;
966 }
967 break;
968 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800969 }
970 return super.dispatchKeyEvent(event);
971 }
972
973 @Override
974 public boolean dispatchTrackballEvent(MotionEvent event) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700975 final int action = event.getActionMasked();
976 switch (action) {
977 case MotionEvent.ACTION_CANCEL:
978 case MotionEvent.ACTION_UP:
979 removeAllCallbacks();
980 break;
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800981 }
982 return super.dispatchTrackballEvent(event);
983 }
984
985 @Override
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700986 protected boolean dispatchHoverEvent(MotionEvent event) {
987 if (!mHasSelectorWheel) {
988 return super.dispatchHoverEvent(event);
Svetoslav Ganov206316a2010-11-19 00:04:05 -0800989 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700990 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
991 final int eventY = (int) event.getY();
992 final int hoveredVirtualViewId;
993 if (eventY < mTopSelectionDividerTop) {
994 hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_DECREMENT;
995 } else if (eventY > mBottomSelectionDividerBottom) {
996 hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INCREMENT;
997 } else {
998 hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INPUT;
999 }
1000 final int action = event.getActionMasked();
1001 AccessibilityNodeProviderImpl provider =
1002 (AccessibilityNodeProviderImpl) getAccessibilityNodeProvider();
1003 switch (action) {
1004 case MotionEvent.ACTION_HOVER_ENTER: {
1005 provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
1006 AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
1007 mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07001008 provider.performAction(hoveredVirtualViewId,
1009 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001010 } break;
1011 case MotionEvent.ACTION_HOVER_MOVE: {
1012 if (mLastHoveredChildVirtualViewId != hoveredVirtualViewId
1013 && mLastHoveredChildVirtualViewId != View.NO_ID) {
1014 provider.sendAccessibilityEventForVirtualView(
1015 mLastHoveredChildVirtualViewId,
1016 AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
1017 provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
1018 AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
1019 mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07001020 provider.performAction(hoveredVirtualViewId,
1021 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001022 }
1023 } break;
1024 case MotionEvent.ACTION_HOVER_EXIT: {
1025 provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
1026 AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
1027 mLastHoveredChildVirtualViewId = View.NO_ID;
1028 } break;
1029 }
1030 }
1031 return false;
1032 }
1033
1034 @Override
1035 public void computeScroll() {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001036 Scroller scroller = mFlingScroller;
1037 if (scroller.isFinished()) {
1038 scroller = mAdjustScroller;
1039 if (scroller.isFinished()) {
1040 return;
1041 }
1042 }
1043 scroller.computeScrollOffset();
1044 int currentScrollerY = scroller.getCurrY();
1045 if (mPreviousScrollerY == 0) {
1046 mPreviousScrollerY = scroller.getStartY();
1047 }
1048 scrollBy(0, currentScrollerY - mPreviousScrollerY);
1049 mPreviousScrollerY = currentScrollerY;
1050 if (scroller.isFinished()) {
1051 onScrollerFinished(scroller);
1052 } else {
1053 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001054 }
1055 }
Tom Taylorfdf6db62009-09-09 11:37:58 -07001056
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001057 @Override
1058 public void setEnabled(boolean enabled) {
1059 super.setEnabled(enabled);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001060 if (!mHasSelectorWheel) {
1061 mIncrementButton.setEnabled(enabled);
1062 }
1063 if (!mHasSelectorWheel) {
1064 mDecrementButton.setEnabled(enabled);
1065 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001066 mInputText.setEnabled(enabled);
1067 }
1068
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001069 @Override
1070 public void scrollBy(int x, int y) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001071 int[] selectorIndices = mSelectorIndices;
Svetoslav Ganov34c06882011-01-03 01:32:34 -08001072 if (!mWrapSelectorWheel && y > 0
1073 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001074 mCurrentScrollOffset = mInitialScrollOffset;
1075 return;
1076 }
Svetoslav Ganov34c06882011-01-03 01:32:34 -08001077 if (!mWrapSelectorWheel && y < 0
1078 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001079 mCurrentScrollOffset = mInitialScrollOffset;
1080 return;
1081 }
1082 mCurrentScrollOffset += y;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001083 while (mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapHeight) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001084 mCurrentScrollOffset -= mSelectorElementHeight;
1085 decrementSelectorIndices(selectorIndices);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001086 setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001087 if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001088 mCurrentScrollOffset = mInitialScrollOffset;
1089 }
1090 }
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001091 while (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapHeight) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001092 mCurrentScrollOffset += mSelectorElementHeight;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001093 incrementSelectorIndices(selectorIndices);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001094 setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001095 if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001096 mCurrentScrollOffset = mInitialScrollOffset;
1097 }
1098 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001099 }
Tom Taylorfdf6db62009-09-09 11:37:58 -07001100
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001101 @Override
Alan Viverettefd639172013-09-18 11:20:38 -07001102 protected int computeVerticalScrollOffset() {
Alan Viverette5ba99f52013-09-13 12:23:20 -07001103 return mCurrentScrollOffset;
1104 }
1105
1106 @Override
Alan Viverettefd639172013-09-18 11:20:38 -07001107 protected int computeVerticalScrollRange() {
1108 return (mMaxValue - mMinValue + 1) * mSelectorElementHeight;
1109 }
1110
1111 @Override
1112 protected int computeVerticalScrollExtent() {
1113 return getHeight();
Alan Viverette5ba99f52013-09-13 12:23:20 -07001114 }
1115
1116 @Override
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001117 public int getSolidColor() {
1118 return mSolidColor;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001119 }
1120
1121 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001122 * Sets the listener to be notified on change of the current value.
1123 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001124 * @param onValueChangedListener The listener.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001125 */
Svetoslav Ganovcedc4462011-01-19 19:25:46 -08001126 public void setOnValueChangedListener(OnValueChangeListener onValueChangedListener) {
1127 mOnValueChangeListener = onValueChangedListener;
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001128 }
1129
1130 /**
1131 * Set listener to be notified for scroll state changes.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001132 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001133 * @param onScrollListener The listener.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001134 */
1135 public void setOnScrollListener(OnScrollListener onScrollListener) {
1136 mOnScrollListener = onScrollListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001137 }
Tom Taylorfdf6db62009-09-09 11:37:58 -07001138
Paul Westbrook68f2f542010-01-13 12:13:57 -08001139 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001140 * Set the formatter to be used for formatting the current value.
1141 * <p>
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001142 * Note: If you have provided alternative values for the values this
1143 * formatter is never invoked.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001144 * </p>
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001145 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001146 * @param formatter The formatter object. If formatter is <code>null</code>,
1147 * {@link String#valueOf(int)} will be used.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001148 *@see #setDisplayedValues(String[])
Paul Westbrook68f2f542010-01-13 12:13:57 -08001149 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001150 public void setFormatter(Formatter formatter) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001151 if (formatter == mFormatter) {
Svetoslav Ganova911d4a2010-12-08 16:11:30 -08001152 return;
1153 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001154 mFormatter = formatter;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001155 initializeSelectorWheelIndices();
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001156 updateInputTextView();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001157 }
Tom Taylorfdf6db62009-09-09 11:37:58 -07001158
Paul Westbrook68f2f542010-01-13 12:13:57 -08001159 /**
1160 * Set the current value for the number picker.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001161 * <p>
1162 * If the argument is less than the {@link NumberPicker#getMinValue()} and
1163 * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the
1164 * current value is set to the {@link NumberPicker#getMinValue()} value.
1165 * </p>
1166 * <p>
1167 * If the argument is less than the {@link NumberPicker#getMinValue()} and
1168 * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the
1169 * current value is set to the {@link NumberPicker#getMaxValue()} value.
1170 * </p>
1171 * <p>
1172 * If the argument is less than the {@link NumberPicker#getMaxValue()} and
1173 * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the
1174 * current value is set to the {@link NumberPicker#getMaxValue()} value.
1175 * </p>
1176 * <p>
1177 * If the argument is less than the {@link NumberPicker#getMaxValue()} and
1178 * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the
1179 * current value is set to the {@link NumberPicker#getMinValue()} value.
1180 * </p>
Paul Westbrook68f2f542010-01-13 12:13:57 -08001181 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001182 * @param value The current value.
1183 * @see #setWrapSelectorWheel(boolean)
1184 * @see #setMinValue(int)
1185 * @see #setMaxValue(int)
Paul Westbrook68f2f542010-01-13 12:13:57 -08001186 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001187 public void setValue(int value) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001188 setValueInternal(value, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001189 }
1190
1191 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001192 * Shows the soft input for its input text.
1193 */
1194 private void showSoftInput() {
1195 InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
1196 if (inputMethodManager != null) {
1197 if (mHasSelectorWheel) {
1198 mInputText.setVisibility(View.VISIBLE);
1199 }
1200 mInputText.requestFocus();
1201 inputMethodManager.showSoftInput(mInputText, 0);
1202 }
1203 }
1204
1205 /**
1206 * Hides the soft input if it is active for the input text.
Svetoslav Ganovb52d9722011-11-07 14:53:34 -08001207 */
1208 private void hideSoftInput() {
1209 InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
1210 if (inputMethodManager != null && inputMethodManager.isActive(mInputText)) {
1211 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001212 if (mHasSelectorWheel) {
1213 mInputText.setVisibility(View.INVISIBLE);
1214 }
Svetoslav Ganovb52d9722011-11-07 14:53:34 -08001215 }
1216 }
1217
1218 /**
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001219 * Computes the max width if no such specified as an attribute.
1220 */
1221 private void tryComputeMaxWidth() {
1222 if (!mComputeMaxWidth) {
1223 return;
1224 }
1225 int maxTextWidth = 0;
1226 if (mDisplayedValues == null) {
1227 float maxDigitWidth = 0;
1228 for (int i = 0; i <= 9; i++) {
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07001229 final float digitWidth = mSelectorWheelPaint.measureText(formatNumberWithLocale(i));
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001230 if (digitWidth > maxDigitWidth) {
1231 maxDigitWidth = digitWidth;
1232 }
1233 }
1234 int numberOfDigits = 0;
1235 int current = mMaxValue;
1236 while (current > 0) {
1237 numberOfDigits++;
1238 current = current / 10;
1239 }
1240 maxTextWidth = (int) (numberOfDigits * maxDigitWidth);
1241 } else {
1242 final int valueCount = mDisplayedValues.length;
1243 for (int i = 0; i < valueCount; i++) {
1244 final float textWidth = mSelectorWheelPaint.measureText(mDisplayedValues[i]);
1245 if (textWidth > maxTextWidth) {
1246 maxTextWidth = (int) textWidth;
1247 }
1248 }
1249 }
1250 maxTextWidth += mInputText.getPaddingLeft() + mInputText.getPaddingRight();
1251 if (mMaxWidth != maxTextWidth) {
1252 if (maxTextWidth > mMinWidth) {
1253 mMaxWidth = maxTextWidth;
1254 } else {
1255 mMaxWidth = mMinWidth;
1256 }
1257 invalidate();
1258 }
1259 }
1260
1261 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001262 * Gets whether the selector wheel wraps when reaching the min/max value.
1263 *
1264 * @return True if the selector wheel wraps.
1265 *
1266 * @see #getMinValue()
1267 * @see #getMaxValue()
1268 */
1269 public boolean getWrapSelectorWheel() {
1270 return mWrapSelectorWheel;
1271 }
1272
1273 /**
1274 * Sets whether the selector wheel shown during flinging/scrolling should
1275 * wrap around the {@link NumberPicker#getMinValue()} and
1276 * {@link NumberPicker#getMaxValue()} values.
1277 * <p>
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001278 * By default if the range (max - min) is more than the number of items shown
1279 * on the selector wheel the selector wheel wrapping is enabled.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001280 * </p>
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -08001281 * <p>
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001282 * <strong>Note:</strong> If the number of items, i.e. the range (
1283 * {@link #getMaxValue()} - {@link #getMinValue()}) is less than
1284 * the number of items shown on the selector wheel, the selector wheel will
1285 * not wrap. Hence, in such a case calling this method is a NOP.
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -08001286 * </p>
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001287 *
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001288 * @param wrapSelectorWheel Whether to wrap.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001289 */
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001290 public void setWrapSelectorWheel(boolean wrapSelectorWheel) {
Svetoslav Ganov3f9c9ea2012-01-24 12:02:31 -08001291 final boolean wrappingAllowed = (mMaxValue - mMinValue) >= mSelectorIndices.length;
1292 if ((!wrapSelectorWheel || wrappingAllowed) && wrapSelectorWheel != mWrapSelectorWheel) {
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001293 mWrapSelectorWheel = wrapSelectorWheel;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001294 }
1295 }
1296
1297 /**
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001298 * Sets the speed at which the numbers be incremented and decremented when
1299 * the up and down buttons are long pressed respectively.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001300 * <p>
1301 * The default value is 300 ms.
1302 * </p>
Paul Westbrook68f2f542010-01-13 12:13:57 -08001303 *
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001304 * @param intervalMillis The speed (in milliseconds) at which the numbers
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001305 * will be incremented and decremented.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001306 */
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001307 public void setOnLongPressUpdateInterval(long intervalMillis) {
1308 mLongPressUpdateInterval = intervalMillis;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001309 }
1310
1311 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001312 * Returns the value of the picker.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001313 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001314 * @return The value.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001315 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001316 public int getValue() {
1317 return mValue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001318 }
Paul Westbrook68f2f542010-01-13 12:13:57 -08001319
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001320 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001321 * Returns the min value of the picker.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001322 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001323 * @return The min value
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001324 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001325 public int getMinValue() {
1326 return mMinValue;
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001327 }
1328
1329 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001330 * Sets the min value of the picker.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001331 *
Svetoslav Ganov7018cfdc2012-11-19 12:33:41 -08001332 * @param minValue The min value inclusive.
1333 *
1334 * <strong>Note:</strong> The length of the displayed values array
1335 * set via {@link #setDisplayedValues(String[])} must be equal to the
1336 * range of selectable numbers which is equal to
1337 * {@link #getMaxValue()} - {@link #getMinValue()} + 1.
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001338 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001339 public void setMinValue(int minValue) {
1340 if (mMinValue == minValue) {
1341 return;
1342 }
1343 if (minValue < 0) {
1344 throw new IllegalArgumentException("minValue must be >= 0");
1345 }
1346 mMinValue = minValue;
1347 if (mMinValue > mValue) {
1348 mValue = mMinValue;
1349 }
1350 boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
1351 setWrapSelectorWheel(wrapSelectorWheel);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001352 initializeSelectorWheelIndices();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001353 updateInputTextView();
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001354 tryComputeMaxWidth();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001355 invalidate();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001356 }
1357
1358 /**
1359 * Returns the max value of the picker.
1360 *
1361 * @return The max value.
1362 */
1363 public int getMaxValue() {
1364 return mMaxValue;
1365 }
1366
1367 /**
1368 * Sets the max value of the picker.
1369 *
Svetoslav Ganov7018cfdc2012-11-19 12:33:41 -08001370 * @param maxValue The max value inclusive.
1371 *
1372 * <strong>Note:</strong> The length of the displayed values array
1373 * set via {@link #setDisplayedValues(String[])} must be equal to the
1374 * range of selectable numbers which is equal to
1375 * {@link #getMaxValue()} - {@link #getMinValue()} + 1.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001376 */
1377 public void setMaxValue(int maxValue) {
1378 if (mMaxValue == maxValue) {
1379 return;
1380 }
1381 if (maxValue < 0) {
1382 throw new IllegalArgumentException("maxValue must be >= 0");
1383 }
1384 mMaxValue = maxValue;
1385 if (mMaxValue < mValue) {
1386 mValue = mMaxValue;
1387 }
1388 boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
1389 setWrapSelectorWheel(wrapSelectorWheel);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001390 initializeSelectorWheelIndices();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001391 updateInputTextView();
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001392 tryComputeMaxWidth();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001393 invalidate();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001394 }
1395
1396 /**
1397 * Gets the values to be displayed instead of string values.
1398 *
1399 * @return The displayed values.
1400 */
1401 public String[] getDisplayedValues() {
1402 return mDisplayedValues;
1403 }
1404
1405 /**
1406 * Sets the values to be displayed.
1407 *
1408 * @param displayedValues The displayed values.
Svetoslav Ganov7018cfdc2012-11-19 12:33:41 -08001409 *
1410 * <strong>Note:</strong> The length of the displayed values array
1411 * must be equal to the range of selectable numbers which is equal to
1412 * {@link #getMaxValue()} - {@link #getMinValue()} + 1.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001413 */
1414 public void setDisplayedValues(String[] displayedValues) {
1415 if (mDisplayedValues == displayedValues) {
1416 return;
1417 }
1418 mDisplayedValues = displayedValues;
1419 if (mDisplayedValues != null) {
1420 // Allow text entry rather than strictly numeric entry.
1421 mInputText.setRawInputType(InputType.TYPE_CLASS_TEXT
1422 | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
1423 } else {
1424 mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
1425 }
1426 updateInputTextView();
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001427 initializeSelectorWheelIndices();
Svetoslav Ganov9f086d82011-11-29 18:27:23 -08001428 tryComputeMaxWidth();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001429 }
1430
1431 @Override
1432 protected float getTopFadingEdgeStrength() {
1433 return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
1434 }
1435
1436 @Override
1437 protected float getBottomFadingEdgeStrength() {
1438 return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
1439 }
1440
1441 @Override
1442 protected void onDetachedFromWindow() {
Romain Guy46bfc482013-08-16 18:38:29 -07001443 super.onDetachedFromWindow();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001444 removeAllCallbacks();
1445 }
1446
1447 @Override
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001448 protected void onDraw(Canvas canvas) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001449 if (!mHasSelectorWheel) {
1450 super.onDraw(canvas);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001451 return;
1452 }
1453 float x = (mRight - mLeft) / 2;
1454 float y = mCurrentScrollOffset;
1455
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001456 // draw the virtual buttons pressed state if needed
1457 if (mVirtualButtonPressedDrawable != null
1458 && mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
1459 if (mDecrementVirtualButtonPressed) {
1460 mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET);
1461 mVirtualButtonPressedDrawable.setBounds(0, 0, mRight, mTopSelectionDividerTop);
1462 mVirtualButtonPressedDrawable.draw(canvas);
1463 }
1464 if (mIncrementVirtualButtonPressed) {
1465 mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET);
1466 mVirtualButtonPressedDrawable.setBounds(0, mBottomSelectionDividerBottom, mRight,
1467 mBottom);
1468 mVirtualButtonPressedDrawable.draw(canvas);
1469 }
1470 }
1471
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001472 // draw the selector wheel
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001473 int[] selectorIndices = mSelectorIndices;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001474 for (int i = 0; i < selectorIndices.length; i++) {
1475 int selectorIndex = selectorIndices[i];
1476 String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001477 // Do not draw the middle item if input is visible since the input
1478 // is shown only if the wheel is static and it covers the middle
1479 // item. Otherwise, if the user starts editing the text via the
1480 // IME he may see a dimmed version of the old value intermixed
1481 // with the new one.
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001482 if (i != SELECTOR_MIDDLE_ITEM_INDEX || mInputText.getVisibility() != VISIBLE) {
1483 canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint);
1484 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001485 y += mSelectorElementHeight;
1486 }
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001487
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001488 // draw the selection dividers
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001489 if (mSelectionDivider != null) {
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001490 // draw the top divider
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001491 int topOfTopDivider = mTopSelectionDividerTop;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001492 int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight;
1493 mSelectionDivider.setBounds(0, topOfTopDivider, mRight, bottomOfTopDivider);
1494 mSelectionDivider.draw(canvas);
1495
1496 // draw the bottom divider
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001497 int bottomOfBottomDivider = mBottomSelectionDividerBottom;
1498 int topOfBottomDivider = bottomOfBottomDivider - mSelectionDividerHeight;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001499 mSelectionDivider.setBounds(0, topOfBottomDivider, mRight, bottomOfBottomDivider);
1500 mSelectionDivider.draw(canvas);
1501 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001502 }
1503
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -07001504 @Override
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001505 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1506 super.onInitializeAccessibilityEvent(event);
1507 event.setClassName(NumberPicker.class.getName());
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001508 event.setScrollable(true);
1509 event.setScrollY((mMinValue + mValue) * mSelectorElementHeight);
1510 event.setMaxScrollY((mMaxValue - mMinValue) * mSelectorElementHeight);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001511 }
1512
1513 @Override
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001514 public AccessibilityNodeProvider getAccessibilityNodeProvider() {
1515 if (!mHasSelectorWheel) {
1516 return super.getAccessibilityNodeProvider();
1517 }
1518 if (mAccessibilityNodeProvider == null) {
1519 mAccessibilityNodeProvider = new AccessibilityNodeProviderImpl();
1520 }
1521 return mAccessibilityNodeProvider;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001522 }
1523
Paul Westbrook68f2f542010-01-13 12:13:57 -08001524 /**
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001525 * Makes a measure spec that tries greedily to use the max value.
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001526 *
1527 * @param measureSpec The measure spec.
Svetoslav Ganov698e1d52011-11-07 18:43:01 -08001528 * @param maxSize The max value for the size.
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001529 * @return A measure spec greedily imposing the max size.
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001530 */
Svetoslav Ganov698e1d52011-11-07 18:43:01 -08001531 private int makeMeasureSpec(int measureSpec, int maxSize) {
Svetoslav Ganov9f086d82011-11-29 18:27:23 -08001532 if (maxSize == SIZE_UNSPECIFIED) {
1533 return measureSpec;
1534 }
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001535 final int size = MeasureSpec.getSize(measureSpec);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001536 final int mode = MeasureSpec.getMode(measureSpec);
1537 switch (mode) {
1538 case MeasureSpec.EXACTLY:
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001539 return measureSpec;
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001540 case MeasureSpec.AT_MOST:
Svetoslav Ganov698e1d52011-11-07 18:43:01 -08001541 return MeasureSpec.makeMeasureSpec(Math.min(size, maxSize), MeasureSpec.EXACTLY);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001542 case MeasureSpec.UNSPECIFIED:
Svetoslav Ganov698e1d52011-11-07 18:43:01 -08001543 return MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.EXACTLY);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001544 default:
Svetoslav Ganovec1e06a2011-10-31 15:52:55 -07001545 throw new IllegalArgumentException("Unknown measure mode: " + mode);
Svetoslav Ganove0c8ab52011-10-25 21:27:29 -07001546 }
1547 }
1548
1549 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001550 * Utility to reconcile a desired size and state, with constraints imposed
1551 * by a MeasureSpec. Tries to respect the min size, unless a different size
1552 * is imposed by the constraints.
Svetoslav Ganov9f086d82011-11-29 18:27:23 -08001553 *
1554 * @param minSize The minimal desired size.
1555 * @param measuredSize The currently measured size.
1556 * @param measureSpec The current measure spec.
1557 * @return The resolved size and state.
1558 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001559 private int resolveSizeAndStateRespectingMinSize(
1560 int minSize, int measuredSize, int measureSpec) {
Svetoslav Ganov9f086d82011-11-29 18:27:23 -08001561 if (minSize != SIZE_UNSPECIFIED) {
1562 final int desiredWidth = Math.max(minSize, measuredSize);
1563 return resolveSizeAndState(desiredWidth, measureSpec, 0);
1564 } else {
1565 return measuredSize;
1566 }
1567 }
1568
1569 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001570 * Resets the selector indices and clear the cached string representation of
1571 * these indices.
Svetoslav Ganova911d4a2010-12-08 16:11:30 -08001572 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001573 private void initializeSelectorWheelIndices() {
Svetoslav Ganova911d4a2010-12-08 16:11:30 -08001574 mSelectorIndexToStringCache.clear();
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001575 int[] selectorIndices = mSelectorIndices;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001576 int current = getValue();
1577 for (int i = 0; i < mSelectorIndices.length; i++) {
1578 int selectorIndex = current + (i - SELECTOR_MIDDLE_ITEM_INDEX);
1579 if (mWrapSelectorWheel) {
1580 selectorIndex = getWrappedSelectorIndex(selectorIndex);
1581 }
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001582 selectorIndices[i] = selectorIndex;
1583 ensureCachedScrollSelectorValue(selectorIndices[i]);
Svetoslav Ganova911d4a2010-12-08 16:11:30 -08001584 }
1585 }
1586
1587 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001588 * Sets the current value of this NumberPicker.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001589 *
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001590 * @param current The new value of the NumberPicker.
1591 * @param notifyChange Whether to notify if the current value changed.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001592 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001593 private void setValueInternal(int current, boolean notifyChange) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001594 if (mValue == current) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001595 return;
1596 }
1597 // Wrap around the values if we go past the start or end
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001598 if (mWrapSelectorWheel) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001599 current = getWrappedSelectorIndex(current);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001600 } else {
1601 current = Math.max(current, mMinValue);
1602 current = Math.min(current, mMaxValue);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001603 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001604 int previous = mValue;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001605 mValue = current;
1606 updateInputTextView();
1607 if (notifyChange) {
1608 notifyChange(previous, current);
1609 }
Svetoslav Ganovfac14f92012-04-12 16:51:04 -07001610 initializeSelectorWheelIndices();
1611 invalidate();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001612 }
1613
1614 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001615 * Changes the current value by one which is increment or
1616 * decrement based on the passes argument.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001617 * decrement the current value.
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001618 *
1619 * @param increment True to increment, false to decrement.
1620 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001621 private void changeValueByOne(boolean increment) {
1622 if (mHasSelectorWheel) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001623 mInputText.setVisibility(View.INVISIBLE);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001624 if (!moveToFinalScrollerPosition(mFlingScroller)) {
1625 moveToFinalScrollerPosition(mAdjustScroller);
1626 }
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001627 mPreviousScrollerY = 0;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001628 if (increment) {
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -07001629 mFlingScroller.startScroll(0, 0, 0, -mSelectorElementHeight, SNAP_SCROLL_DURATION);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001630 } else {
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -07001631 mFlingScroller.startScroll(0, 0, 0, mSelectorElementHeight, SNAP_SCROLL_DURATION);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001632 }
1633 invalidate();
1634 } else {
1635 if (increment) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001636 setValueInternal(mValue + 1, true);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001637 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001638 setValueInternal(mValue - 1, true);
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001639 }
1640 }
1641 }
1642
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001643 private void initializeSelectorWheel() {
1644 initializeSelectorWheelIndices();
1645 int[] selectorIndices = mSelectorIndices;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001646 int totalTextHeight = selectorIndices.length * mTextSize;
Svetoslav Ganov01fa0d72011-06-28 22:08:23 -07001647 float totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001648 float textGapCount = selectorIndices.length;
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001649 mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f);
1650 mSelectorElementHeight = mTextSize + mSelectorTextGapHeight;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001651 // Ensure that the middle item is positioned the same as the text in
1652 // mInputText
Chet Haaseeeafd422011-08-17 18:26:56 -07001653 int editTextTextPosition = mInputText.getBaseline() + mInputText.getTop();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001654 mInitialScrollOffset = editTextTextPosition
1655 - (mSelectorElementHeight * SELECTOR_MIDDLE_ITEM_INDEX);
Svetoslav Ganov6a19fcd2011-06-29 12:26:11 -07001656 mCurrentScrollOffset = mInitialScrollOffset;
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001657 updateInputTextView();
1658 }
1659
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001660 private void initializeFadingEdges() {
1661 setVerticalFadingEdgeEnabled(true);
1662 setFadingEdgeLength((mBottom - mTop - mTextSize) / 2);
1663 }
1664
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001665 /**
1666 * Callback invoked upon completion of a given <code>scroller</code>.
1667 */
1668 private void onScrollerFinished(Scroller scroller) {
1669 if (scroller == mFlingScroller) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001670 if (!ensureScrollWheelAdjusted()) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001671 updateInputTextView();
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001672 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001673 onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001674 } else {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001675 if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
1676 updateInputTextView();
1677 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001678 }
1679 }
1680
1681 /**
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001682 * Handles transition to a given <code>scrollState</code>
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001683 */
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001684 private void onScrollStateChange(int scrollState) {
1685 if (mScrollState == scrollState) {
1686 return;
1687 }
1688 mScrollState = scrollState;
1689 if (mOnScrollListener != null) {
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08001690 mOnScrollListener.onScrollStateChange(this, scrollState);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001691 }
1692 }
1693
1694 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001695 * Flings the selector with the given <code>velocityY</code>.
1696 */
1697 private void fling(int velocityY) {
1698 mPreviousScrollerY = 0;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001699
Svetoslav Ganov234484a2011-12-07 19:06:35 -08001700 if (velocityY > 0) {
1701 mFlingScroller.fling(0, 0, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001702 } else {
Svetoslav Ganov234484a2011-12-07 19:06:35 -08001703 mFlingScroller.fling(0, Integer.MAX_VALUE, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001704 }
1705
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001706 invalidate();
1707 }
1708
1709 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001710 * @return The wrapped index <code>selectorIndex</code> value.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001711 */
1712 private int getWrappedSelectorIndex(int selectorIndex) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001713 if (selectorIndex > mMaxValue) {
1714 return mMinValue + (selectorIndex - mMaxValue) % (mMaxValue - mMinValue) - 1;
1715 } else if (selectorIndex < mMinValue) {
1716 return mMaxValue - (mMinValue - selectorIndex) % (mMaxValue - mMinValue) + 1;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001717 }
1718 return selectorIndex;
1719 }
1720
1721 /**
1722 * Increments the <code>selectorIndices</code> whose string representations
1723 * will be displayed in the selector.
1724 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001725 private void incrementSelectorIndices(int[] selectorIndices) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001726 for (int i = 0; i < selectorIndices.length - 1; i++) {
1727 selectorIndices[i] = selectorIndices[i + 1];
1728 }
1729 int nextScrollSelectorIndex = selectorIndices[selectorIndices.length - 2] + 1;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001730 if (mWrapSelectorWheel && nextScrollSelectorIndex > mMaxValue) {
1731 nextScrollSelectorIndex = mMinValue;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001732 }
1733 selectorIndices[selectorIndices.length - 1] = nextScrollSelectorIndex;
1734 ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
1735 }
1736
1737 /**
1738 * Decrements the <code>selectorIndices</code> whose string representations
1739 * will be displayed in the selector.
1740 */
1741 private void decrementSelectorIndices(int[] selectorIndices) {
1742 for (int i = selectorIndices.length - 1; i > 0; i--) {
1743 selectorIndices[i] = selectorIndices[i - 1];
1744 }
1745 int nextScrollSelectorIndex = selectorIndices[1] - 1;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001746 if (mWrapSelectorWheel && nextScrollSelectorIndex < mMinValue) {
1747 nextScrollSelectorIndex = mMaxValue;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001748 }
1749 selectorIndices[0] = nextScrollSelectorIndex;
1750 ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
1751 }
1752
1753 /**
1754 * Ensures we have a cached string representation of the given <code>
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001755 * selectorIndex</code> to avoid multiple instantiations of the same string.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001756 */
1757 private void ensureCachedScrollSelectorValue(int selectorIndex) {
1758 SparseArray<String> cache = mSelectorIndexToStringCache;
1759 String scrollSelectorValue = cache.get(selectorIndex);
1760 if (scrollSelectorValue != null) {
1761 return;
1762 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001763 if (selectorIndex < mMinValue || selectorIndex > mMaxValue) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001764 scrollSelectorValue = "";
1765 } else {
1766 if (mDisplayedValues != null) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001767 int displayedValueIndex = selectorIndex - mMinValue;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001768 scrollSelectorValue = mDisplayedValues[displayedValueIndex];
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001769 } else {
1770 scrollSelectorValue = formatNumber(selectorIndex);
1771 }
1772 }
1773 cache.put(selectorIndex, scrollSelectorValue);
1774 }
1775
1776 private String formatNumber(int value) {
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07001777 return (mFormatter != null) ? mFormatter.format(value) : formatNumberWithLocale(value);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001778 }
1779
1780 private void validateInputTextView(View v) {
1781 String str = String.valueOf(((TextView) v).getText());
1782 if (TextUtils.isEmpty(str)) {
1783 // Restore to the old value as we don't allow empty values
1784 updateInputTextView();
1785 } else {
1786 // Check the new value and ensure it's in range
1787 int current = getSelectedPos(str.toString());
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001788 setValueInternal(current, true);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001789 }
1790 }
1791
1792 /**
1793 * Updates the view of this NumberPicker. If displayValues were specified in
Svetoslav Ganov9cd5fb22011-01-19 19:19:55 -08001794 * the string corresponding to the index specified by the current value will
1795 * be returned. Otherwise, the formatter specified in {@link #setFormatter}
1796 * will be used to format the number.
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001797 *
1798 * @return Whether the text was updated.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001799 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001800 private boolean updateInputTextView() {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001801 /*
1802 * If we don't have displayed values then use the current number else
1803 * find the correct value in the displayed values for the current
1804 * number.
1805 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001806 String text = (mDisplayedValues == null) ? formatNumber(mValue)
1807 : mDisplayedValues[mValue - mMinValue];
1808 if (!TextUtils.isEmpty(text) && !text.equals(mInputText.getText().toString())) {
1809 mInputText.setText(text);
1810 return true;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001811 }
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -07001812
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001813 return false;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001814 }
1815
1816 /**
1817 * Notifies the listener, if registered, of a change of the value of this
1818 * NumberPicker.
1819 */
1820 private void notifyChange(int previous, int current) {
Svetoslav Ganovcedc4462011-01-19 19:25:46 -08001821 if (mOnValueChangeListener != null) {
1822 mOnValueChangeListener.onValueChange(this, previous, mValue);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001823 }
1824 }
1825
1826 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001827 * Posts a command for changing the current value by one.
1828 *
1829 * @param increment Whether to increment or decrement the value.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001830 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001831 private void postChangeCurrentByOneFromLongPress(boolean increment, long delayMillis) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001832 if (mChangeCurrentByOneFromLongPressCommand == null) {
1833 mChangeCurrentByOneFromLongPressCommand = new ChangeCurrentByOneFromLongPressCommand();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001834 } else {
1835 removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001836 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001837 mChangeCurrentByOneFromLongPressCommand.setStep(increment);
1838 postDelayed(mChangeCurrentByOneFromLongPressCommand, delayMillis);
1839 }
1840
1841 /**
1842 * Removes the command for changing the current value by one.
1843 */
1844 private void removeChangeCurrentByOneFromLongPress() {
1845 if (mChangeCurrentByOneFromLongPressCommand != null) {
1846 removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
1847 }
1848 }
1849
1850 /**
1851 * Posts a command for beginning an edit of the current value via IME on
1852 * long press.
1853 */
1854 private void postBeginSoftInputOnLongPressCommand() {
1855 if (mBeginSoftInputOnLongPressCommand == null) {
1856 mBeginSoftInputOnLongPressCommand = new BeginSoftInputOnLongPressCommand();
1857 } else {
1858 removeCallbacks(mBeginSoftInputOnLongPressCommand);
1859 }
1860 postDelayed(mBeginSoftInputOnLongPressCommand, ViewConfiguration.getLongPressTimeout());
1861 }
1862
1863 /**
1864 * Removes the command for beginning an edit of the current value via IME.
1865 */
1866 private void removeBeginSoftInputCommand() {
1867 if (mBeginSoftInputOnLongPressCommand != null) {
1868 removeCallbacks(mBeginSoftInputOnLongPressCommand);
1869 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001870 }
1871
1872 /**
1873 * Removes all pending callback from the message queue.
1874 */
1875 private void removeAllCallbacks() {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07001876 if (mChangeCurrentByOneFromLongPressCommand != null) {
1877 removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001878 }
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001879 if (mSetSelectionCommand != null) {
1880 removeCallbacks(mSetSelectionCommand);
1881 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001882 if (mBeginSoftInputOnLongPressCommand != null) {
1883 removeCallbacks(mBeginSoftInputOnLongPressCommand);
1884 }
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07001885 mPressedStateHelper.cancel();
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001886 }
1887
1888 /**
1889 * @return The selected index given its displayed <code>value</code>.
1890 */
1891 private int getSelectedPos(String value) {
1892 if (mDisplayedValues == null) {
1893 try {
1894 return Integer.parseInt(value);
1895 } catch (NumberFormatException e) {
1896 // Ignore as if it's not a number we don't care
1897 }
1898 } else {
1899 for (int i = 0; i < mDisplayedValues.length; i++) {
1900 // Don't force the user to type in jan when ja will do
1901 value = value.toLowerCase();
1902 if (mDisplayedValues[i].toLowerCase().startsWith(value)) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001903 return mMinValue + i;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001904 }
1905 }
1906
1907 /*
1908 * The user might have typed in a number into the month field i.e.
1909 * 10 instead of OCT so support that too.
1910 */
1911 try {
1912 return Integer.parseInt(value);
1913 } catch (NumberFormatException e) {
1914
1915 // Ignore as if it's not a number we don't care
1916 }
1917 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -08001918 return mMinValue;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001919 }
1920
1921 /**
1922 * Posts an {@link SetSelectionCommand} from the given <code>selectionStart
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001923 * </code> to <code>selectionEnd</code>.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001924 */
1925 private void postSetSelectionCommand(int selectionStart, int selectionEnd) {
1926 if (mSetSelectionCommand == null) {
1927 mSetSelectionCommand = new SetSelectionCommand();
1928 } else {
1929 removeCallbacks(mSetSelectionCommand);
1930 }
1931 mSetSelectionCommand.mSelectionStart = selectionStart;
1932 mSetSelectionCommand.mSelectionEnd = selectionEnd;
1933 post(mSetSelectionCommand);
1934 }
1935
1936 /**
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07001937 * The numbers accepted by the input text's {@link Filter}
1938 */
1939 private static final char[] DIGIT_CHARACTERS = new char[] {
1940 // Latin digits are the common case
1941 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1942 // Arabic-Indic
1943 '\u0660', '\u0661', '\u0662', '\u0663', '\u0664', '\u0665', '\u0666', '\u0667', '\u0668'
1944 , '\u0669',
1945 // Extended Arabic-Indic
1946 '\u06f0', '\u06f1', '\u06f2', '\u06f3', '\u06f4', '\u06f5', '\u06f6', '\u06f7', '\u06f8'
1947 , '\u06f9'
1948 };
1949
1950 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001951 * Filter for accepting only valid indices or prefixes of the string
1952 * representation of valid indices.
1953 */
1954 class InputTextFilter extends NumberKeyListener {
1955
1956 // XXX This doesn't allow for range limits when controlled by a
1957 // soft input method!
1958 public int getInputType() {
1959 return InputType.TYPE_CLASS_TEXT;
1960 }
1961
1962 @Override
1963 protected char[] getAcceptedChars() {
1964 return DIGIT_CHARACTERS;
1965 }
1966
1967 @Override
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07001968 public CharSequence filter(
1969 CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001970 if (mDisplayedValues == null) {
1971 CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
1972 if (filtered == null) {
1973 filtered = source.subSequence(start, end);
1974 }
1975
1976 String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
1977 + dest.subSequence(dend, dest.length());
1978
1979 if ("".equals(result)) {
1980 return result;
1981 }
1982 int val = getSelectedPos(result);
1983
1984 /*
1985 * Ensure the user can't type in a value greater than the max
1986 * allowed. We have to allow less than min as the user might
1987 * want to delete some numbers and then type a new number.
Sungmin Choi6d8a99f2013-01-25 18:26:46 +09001988 * And prevent multiple-"0" that exceeds the length of upper
1989 * bound number.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001990 */
Sungmin Choi6d8a99f2013-01-25 18:26:46 +09001991 if (val > mMaxValue || result.length() > String.valueOf(mMaxValue).length()) {
Svetoslav Ganov206316a2010-11-19 00:04:05 -08001992 return "";
1993 } else {
1994 return filtered;
1995 }
1996 } else {
1997 CharSequence filtered = String.valueOf(source.subSequence(start, end));
1998 if (TextUtils.isEmpty(filtered)) {
1999 return "";
2000 }
2001 String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
2002 + dest.subSequence(dend, dest.length());
2003 String str = String.valueOf(result).toLowerCase();
2004 for (String val : mDisplayedValues) {
2005 String valLowerCase = val.toLowerCase();
2006 if (valLowerCase.startsWith(str)) {
2007 postSetSelectionCommand(result.length(), val.length());
2008 return val.subSequence(dstart, val.length());
2009 }
2010 }
2011 return "";
2012 }
2013 }
2014 }
2015
2016 /**
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002017 * Ensures that the scroll wheel is adjusted i.e. there is no offset and the
2018 * middle element is in the middle of the widget.
2019 *
2020 * @return Whether an adjustment has been made.
2021 */
2022 private boolean ensureScrollWheelAdjusted() {
2023 // adjust to the closest value
2024 int deltaY = mInitialScrollOffset - mCurrentScrollOffset;
2025 if (deltaY != 0) {
2026 mPreviousScrollerY = 0;
2027 if (Math.abs(deltaY) > mSelectorElementHeight / 2) {
2028 deltaY += (deltaY > 0) ? -mSelectorElementHeight : mSelectorElementHeight;
2029 }
2030 mAdjustScroller.startScroll(0, 0, 0, deltaY, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
2031 invalidate();
2032 return true;
2033 }
2034 return false;
2035 }
2036
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07002037 class PressedStateHelper implements Runnable {
2038 public static final int BUTTON_INCREMENT = 1;
2039 public static final int BUTTON_DECREMENT = 2;
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -07002040
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07002041 private final int MODE_PRESS = 1;
2042 private final int MODE_TAPPED = 2;
2043
2044 private int mManagedButton;
2045 private int mMode;
2046
2047 public void cancel() {
2048 mMode = 0;
2049 mManagedButton = 0;
2050 NumberPicker.this.removeCallbacks(this);
2051 if (mIncrementVirtualButtonPressed) {
2052 mIncrementVirtualButtonPressed = false;
2053 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -07002054 }
Svetoslav Ganov232dd3f2012-04-24 16:07:10 -07002055 mDecrementVirtualButtonPressed = false;
2056 if (mDecrementVirtualButtonPressed) {
2057 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2058 }
2059 }
2060
2061 public void buttonPressDelayed(int button) {
2062 cancel();
2063 mMode = MODE_PRESS;
2064 mManagedButton = button;
2065 NumberPicker.this.postDelayed(this, ViewConfiguration.getTapTimeout());
2066 }
2067
2068 public void buttonTapped(int button) {
2069 cancel();
2070 mMode = MODE_TAPPED;
2071 mManagedButton = button;
2072 NumberPicker.this.post(this);
2073 }
2074
2075 @Override
2076 public void run() {
2077 switch (mMode) {
2078 case MODE_PRESS: {
2079 switch (mManagedButton) {
2080 case BUTTON_INCREMENT: {
2081 mIncrementVirtualButtonPressed = true;
2082 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
2083 } break;
2084 case BUTTON_DECREMENT: {
2085 mDecrementVirtualButtonPressed = true;
2086 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2087 }
2088 }
2089 } break;
2090 case MODE_TAPPED: {
2091 switch (mManagedButton) {
2092 case BUTTON_INCREMENT: {
2093 if (!mIncrementVirtualButtonPressed) {
2094 NumberPicker.this.postDelayed(this,
2095 ViewConfiguration.getPressedStateDuration());
2096 }
2097 mIncrementVirtualButtonPressed ^= true;
2098 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
2099 } break;
2100 case BUTTON_DECREMENT: {
2101 if (!mDecrementVirtualButtonPressed) {
2102 NumberPicker.this.postDelayed(this,
2103 ViewConfiguration.getPressedStateDuration());
2104 }
2105 mDecrementVirtualButtonPressed ^= true;
2106 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2107 }
2108 }
2109 } break;
2110 }
Svetoslav Ganovfe41ce4e2012-04-02 20:31:05 -07002111 }
2112 }
2113
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002114 /**
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002115 * Command for setting the input text selection.
2116 */
2117 class SetSelectionCommand implements Runnable {
2118 private int mSelectionStart;
2119
2120 private int mSelectionEnd;
2121
2122 public void run() {
2123 mInputText.setSelection(mSelectionStart, mSelectionEnd);
2124 }
2125 }
2126
2127 /**
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07002128 * Command for changing the current value from a long press by one.
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002129 */
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07002130 class ChangeCurrentByOneFromLongPressCommand implements Runnable {
2131 private boolean mIncrement;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002132
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002133 private void setStep(boolean increment) {
Svetoslav Ganovb80a3fc2011-09-15 20:02:52 -07002134 mIncrement = increment;
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002135 }
2136
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002137 @Override
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002138 public void run() {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002139 changeValueByOne(mIncrement);
Svetoslav Ganov4bfd7942010-12-07 16:20:24 -08002140 postDelayed(this, mLongPressUpdateInterval);
Svetoslav Ganov206316a2010-11-19 00:04:05 -08002141 }
2142 }
Svetoslav Ganova2b41b42012-02-27 15:53:32 -08002143
2144 /**
2145 * @hide
2146 */
2147 public static class CustomEditText extends EditText {
2148
2149 public CustomEditText(Context context, AttributeSet attrs) {
2150 super(context, attrs);
2151 }
2152
2153 @Override
2154 public void onEditorAction(int actionCode) {
2155 super.onEditorAction(actionCode);
2156 if (actionCode == EditorInfo.IME_ACTION_DONE) {
2157 clearFocus();
2158 }
2159 }
2160 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002161
2162 /**
2163 * Command for beginning soft input on long press.
2164 */
2165 class BeginSoftInputOnLongPressCommand implements Runnable {
2166
2167 @Override
2168 public void run() {
2169 showSoftInput();
2170 mIngonreMoveEvents = true;
2171 }
2172 }
2173
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002174 /**
2175 * Class for managing virtual view tree rooted at this picker.
2176 */
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002177 class AccessibilityNodeProviderImpl extends AccessibilityNodeProvider {
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002178 private static final int UNDEFINED = Integer.MIN_VALUE;
2179
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002180 private static final int VIRTUAL_VIEW_ID_INCREMENT = 1;
2181
2182 private static final int VIRTUAL_VIEW_ID_INPUT = 2;
2183
2184 private static final int VIRTUAL_VIEW_ID_DECREMENT = 3;
2185
2186 private final Rect mTempRect = new Rect();
2187
2188 private final int[] mTempArray = new int[2];
2189
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002190 private int mAccessibilityFocusedView = UNDEFINED;
2191
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002192 @Override
2193 public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
2194 switch (virtualViewId) {
2195 case View.NO_ID:
2196 return createAccessibilityNodeInfoForNumberPicker( mScrollX, mScrollY,
2197 mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop));
2198 case VIRTUAL_VIEW_ID_DECREMENT:
2199 return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_DECREMENT,
2200 getVirtualDecrementButtonText(), mScrollX, mScrollY,
2201 mScrollX + (mRight - mLeft),
2202 mTopSelectionDividerTop + mSelectionDividerHeight);
2203 case VIRTUAL_VIEW_ID_INPUT:
Alan Viverette0e2d2812013-05-21 17:15:30 -07002204 return createAccessibiltyNodeInfoForInputText(mScrollX,
2205 mTopSelectionDividerTop + mSelectionDividerHeight,
2206 mScrollX + (mRight - mLeft),
2207 mBottomSelectionDividerBottom - mSelectionDividerHeight);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002208 case VIRTUAL_VIEW_ID_INCREMENT:
2209 return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_INCREMENT,
2210 getVirtualIncrementButtonText(), mScrollX,
2211 mBottomSelectionDividerBottom - mSelectionDividerHeight,
2212 mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop));
2213 }
2214 return super.createAccessibilityNodeInfo(virtualViewId);
2215 }
2216
2217 @Override
2218 public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String searched,
2219 int virtualViewId) {
2220 if (TextUtils.isEmpty(searched)) {
2221 return Collections.emptyList();
2222 }
2223 String searchedLowerCase = searched.toLowerCase();
2224 List<AccessibilityNodeInfo> result = new ArrayList<AccessibilityNodeInfo>();
2225 switch (virtualViewId) {
2226 case View.NO_ID: {
2227 findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
2228 VIRTUAL_VIEW_ID_DECREMENT, result);
2229 findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
2230 VIRTUAL_VIEW_ID_INPUT, result);
2231 findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
2232 VIRTUAL_VIEW_ID_INCREMENT, result);
2233 return result;
2234 }
2235 case VIRTUAL_VIEW_ID_DECREMENT:
2236 case VIRTUAL_VIEW_ID_INCREMENT:
2237 case VIRTUAL_VIEW_ID_INPUT: {
2238 findAccessibilityNodeInfosByTextInChild(searchedLowerCase, virtualViewId,
2239 result);
2240 return result;
2241 }
2242 }
2243 return super.findAccessibilityNodeInfosByText(searched, virtualViewId);
2244 }
2245
2246 @Override
Svetoslav Ganovaa780c12012-04-19 23:01:39 -07002247 public boolean performAction(int virtualViewId, int action, Bundle arguments) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002248 switch (virtualViewId) {
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002249 case View.NO_ID: {
2250 switch (action) {
2251 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
2252 if (mAccessibilityFocusedView != virtualViewId) {
2253 mAccessibilityFocusedView = virtualViewId;
2254 requestAccessibilityFocus();
2255 return true;
2256 }
2257 } return false;
2258 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
2259 if (mAccessibilityFocusedView == virtualViewId) {
2260 mAccessibilityFocusedView = UNDEFINED;
2261 clearAccessibilityFocus();
2262 return true;
2263 }
2264 return false;
2265 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07002266 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002267 if (NumberPicker.this.isEnabled()
2268 && (getWrapSelectorWheel() || getValue() < getMaxValue())) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07002269 changeValueByOne(true);
2270 return true;
2271 }
2272 } return false;
2273 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002274 if (NumberPicker.this.isEnabled()
2275 && (getWrapSelectorWheel() || getValue() > getMinValue())) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07002276 changeValueByOne(false);
2277 return true;
2278 }
2279 } return false;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002280 }
2281 } break;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002282 case VIRTUAL_VIEW_ID_INPUT: {
2283 switch (action) {
2284 case AccessibilityNodeInfo.ACTION_FOCUS: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002285 if (NumberPicker.this.isEnabled() && !mInputText.isFocused()) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002286 return mInputText.requestFocus();
2287 }
2288 } break;
2289 case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002290 if (NumberPicker.this.isEnabled() && mInputText.isFocused()) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002291 mInputText.clearFocus();
2292 return true;
2293 }
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002294 return false;
2295 }
2296 case AccessibilityNodeInfo.ACTION_CLICK: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002297 if (NumberPicker.this.isEnabled()) {
2298 showSoftInput();
2299 return true;
2300 }
2301 return false;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002302 }
2303 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
2304 if (mAccessibilityFocusedView != virtualViewId) {
2305 mAccessibilityFocusedView = virtualViewId;
2306 sendAccessibilityEventForVirtualView(virtualViewId,
2307 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
2308 mInputText.invalidate();
2309 return true;
2310 }
2311 } return false;
2312 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
2313 if (mAccessibilityFocusedView == virtualViewId) {
2314 mAccessibilityFocusedView = UNDEFINED;
2315 sendAccessibilityEventForVirtualView(virtualViewId,
2316 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
2317 mInputText.invalidate();
2318 return true;
2319 }
2320 } return false;
2321 default: {
2322 return mInputText.performAccessibilityAction(action, arguments);
2323 }
2324 }
2325 } return false;
2326 case VIRTUAL_VIEW_ID_INCREMENT: {
2327 switch (action) {
2328 case AccessibilityNodeInfo.ACTION_CLICK: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002329 if (NumberPicker.this.isEnabled()) {
2330 NumberPicker.this.changeValueByOne(true);
2331 sendAccessibilityEventForVirtualView(virtualViewId,
2332 AccessibilityEvent.TYPE_VIEW_CLICKED);
2333 return true;
2334 }
2335 } return false;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002336 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
2337 if (mAccessibilityFocusedView != virtualViewId) {
2338 mAccessibilityFocusedView = virtualViewId;
2339 sendAccessibilityEventForVirtualView(virtualViewId,
2340 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
2341 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
2342 return true;
2343 }
2344 } return false;
2345 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
2346 if (mAccessibilityFocusedView == virtualViewId) {
2347 mAccessibilityFocusedView = UNDEFINED;
2348 sendAccessibilityEventForVirtualView(virtualViewId,
2349 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
2350 invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
2351 return true;
2352 }
2353 } return false;
2354 }
2355 } return false;
2356 case VIRTUAL_VIEW_ID_DECREMENT: {
2357 switch (action) {
2358 case AccessibilityNodeInfo.ACTION_CLICK: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002359 if (NumberPicker.this.isEnabled()) {
2360 final boolean increment = (virtualViewId == VIRTUAL_VIEW_ID_INCREMENT);
2361 NumberPicker.this.changeValueByOne(increment);
2362 sendAccessibilityEventForVirtualView(virtualViewId,
2363 AccessibilityEvent.TYPE_VIEW_CLICKED);
2364 return true;
2365 }
2366 } return false;
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002367 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
2368 if (mAccessibilityFocusedView != virtualViewId) {
2369 mAccessibilityFocusedView = virtualViewId;
2370 sendAccessibilityEventForVirtualView(virtualViewId,
2371 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
2372 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2373 return true;
2374 }
2375 } return false;
2376 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
2377 if (mAccessibilityFocusedView == virtualViewId) {
2378 mAccessibilityFocusedView = UNDEFINED;
2379 sendAccessibilityEventForVirtualView(virtualViewId,
2380 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
2381 invalidate(0, 0, mRight, mTopSelectionDividerTop);
2382 return true;
2383 }
2384 } return false;
2385 }
2386 } return false;
2387 }
2388 return super.performAction(virtualViewId, action, arguments);
2389 }
2390
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002391 public void sendAccessibilityEventForVirtualView(int virtualViewId, int eventType) {
2392 switch (virtualViewId) {
2393 case VIRTUAL_VIEW_ID_DECREMENT: {
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002394 if (hasVirtualDecrementButton()) {
2395 sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
2396 getVirtualDecrementButtonText());
2397 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002398 } break;
2399 case VIRTUAL_VIEW_ID_INPUT: {
2400 sendAccessibilityEventForVirtualText(eventType);
2401 } break;
2402 case VIRTUAL_VIEW_ID_INCREMENT: {
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002403 if (hasVirtualIncrementButton()) {
2404 sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
2405 getVirtualIncrementButtonText());
2406 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002407 } break;
2408 }
2409 }
2410
2411 private void sendAccessibilityEventForVirtualText(int eventType) {
Svetoslav Ganova9092762012-09-06 19:57:00 -07002412 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2413 AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
2414 mInputText.onInitializeAccessibilityEvent(event);
2415 mInputText.onPopulateAccessibilityEvent(event);
2416 event.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
2417 requestSendAccessibilityEvent(NumberPicker.this, event);
2418 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002419 }
2420
2421 private void sendAccessibilityEventForVirtualButton(int virtualViewId, int eventType,
2422 String text) {
Svetoslav Ganova9092762012-09-06 19:57:00 -07002423 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2424 AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
2425 event.setClassName(Button.class.getName());
2426 event.setPackageName(mContext.getPackageName());
2427 event.getText().add(text);
2428 event.setEnabled(NumberPicker.this.isEnabled());
2429 event.setSource(NumberPicker.this, virtualViewId);
2430 requestSendAccessibilityEvent(NumberPicker.this, event);
2431 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002432 }
2433
2434 private void findAccessibilityNodeInfosByTextInChild(String searchedLowerCase,
2435 int virtualViewId, List<AccessibilityNodeInfo> outResult) {
2436 switch (virtualViewId) {
2437 case VIRTUAL_VIEW_ID_DECREMENT: {
2438 String text = getVirtualDecrementButtonText();
2439 if (!TextUtils.isEmpty(text)
2440 && text.toString().toLowerCase().contains(searchedLowerCase)) {
2441 outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT));
2442 }
2443 } return;
2444 case VIRTUAL_VIEW_ID_INPUT: {
2445 CharSequence text = mInputText.getText();
2446 if (!TextUtils.isEmpty(text) &&
2447 text.toString().toLowerCase().contains(searchedLowerCase)) {
2448 outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT));
2449 return;
2450 }
2451 CharSequence contentDesc = mInputText.getText();
2452 if (!TextUtils.isEmpty(contentDesc) &&
2453 contentDesc.toString().toLowerCase().contains(searchedLowerCase)) {
2454 outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT));
2455 return;
2456 }
2457 } break;
2458 case VIRTUAL_VIEW_ID_INCREMENT: {
2459 String text = getVirtualIncrementButtonText();
2460 if (!TextUtils.isEmpty(text)
2461 && text.toString().toLowerCase().contains(searchedLowerCase)) {
2462 outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT));
2463 }
2464 } return;
2465 }
2466 }
2467
Alan Viverette0e2d2812013-05-21 17:15:30 -07002468 private AccessibilityNodeInfo createAccessibiltyNodeInfoForInputText(
2469 int left, int top, int right, int bottom) {
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002470 AccessibilityNodeInfo info = mInputText.createAccessibilityNodeInfo();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002471 info.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002472 if (mAccessibilityFocusedView != VIRTUAL_VIEW_ID_INPUT) {
2473 info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
2474 }
2475 if (mAccessibilityFocusedView == VIRTUAL_VIEW_ID_INPUT) {
2476 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
2477 }
Alan Viverette0e2d2812013-05-21 17:15:30 -07002478 Rect boundsInParent = mTempRect;
2479 boundsInParent.set(left, top, right, bottom);
2480 info.setVisibleToUser(isVisibleToUser(boundsInParent));
2481 info.setBoundsInParent(boundsInParent);
2482 Rect boundsInScreen = boundsInParent;
2483 int[] locationOnScreen = mTempArray;
2484 getLocationOnScreen(locationOnScreen);
2485 boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
2486 info.setBoundsInScreen(boundsInScreen);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002487 return info;
2488 }
2489
2490 private AccessibilityNodeInfo createAccessibilityNodeInfoForVirtualButton(int virtualViewId,
2491 String text, int left, int top, int right, int bottom) {
2492 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
2493 info.setClassName(Button.class.getName());
2494 info.setPackageName(mContext.getPackageName());
2495 info.setSource(NumberPicker.this, virtualViewId);
2496 info.setParent(NumberPicker.this);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002497 info.setText(text);
2498 info.setClickable(true);
2499 info.setLongClickable(true);
2500 info.setEnabled(NumberPicker.this.isEnabled());
2501 Rect boundsInParent = mTempRect;
2502 boundsInParent.set(left, top, right, bottom);
Guang Zhu0d607fb2012-05-11 19:34:56 -07002503 info.setVisibleToUser(isVisibleToUser(boundsInParent));
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002504 info.setBoundsInParent(boundsInParent);
2505 Rect boundsInScreen = boundsInParent;
2506 int[] locationOnScreen = mTempArray;
2507 getLocationOnScreen(locationOnScreen);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002508 boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
2509 info.setBoundsInScreen(boundsInScreen);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002510
2511 if (mAccessibilityFocusedView != virtualViewId) {
2512 info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
2513 }
2514 if (mAccessibilityFocusedView == virtualViewId) {
2515 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
2516 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002517 if (NumberPicker.this.isEnabled()) {
2518 info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
2519 }
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002520
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002521 return info;
2522 }
2523
2524 private AccessibilityNodeInfo createAccessibilityNodeInfoForNumberPicker(int left, int top,
2525 int right, int bottom) {
2526 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
Guang Zhu0d607fb2012-05-11 19:34:56 -07002527 info.setClassName(NumberPicker.class.getName());
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002528 info.setPackageName(mContext.getPackageName());
2529 info.setSource(NumberPicker.this);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002530
2531 if (hasVirtualDecrementButton()) {
2532 info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_DECREMENT);
2533 }
Guang Zhu0d607fb2012-05-11 19:34:56 -07002534 info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002535 if (hasVirtualIncrementButton()) {
2536 info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INCREMENT);
2537 }
2538
Svetoslav Ganov4528b4e2012-05-15 18:24:10 -07002539 info.setParent((View) getParentForAccessibility());
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002540 info.setEnabled(NumberPicker.this.isEnabled());
2541 info.setScrollable(true);
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002542
2543 final float applicationScale =
2544 getContext().getResources().getCompatibilityInfo().applicationScale;
2545
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002546 Rect boundsInParent = mTempRect;
2547 boundsInParent.set(left, top, right, bottom);
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002548 boundsInParent.scale(applicationScale);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002549 info.setBoundsInParent(boundsInParent);
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002550
Guang Zhu0d607fb2012-05-11 19:34:56 -07002551 info.setVisibleToUser(isVisibleToUser());
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002552
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002553 Rect boundsInScreen = boundsInParent;
2554 int[] locationOnScreen = mTempArray;
2555 getLocationOnScreen(locationOnScreen);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002556 boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
Svetoslav Ganov983119a2012-07-03 21:04:10 -07002557 boundsInScreen.scale(applicationScale);
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002558 info.setBoundsInScreen(boundsInScreen);
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002559
2560 if (mAccessibilityFocusedView != View.NO_ID) {
2561 info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
2562 }
2563 if (mAccessibilityFocusedView == View.NO_ID) {
2564 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
2565 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002566 if (NumberPicker.this.isEnabled()) {
2567 if (getWrapSelectorWheel() || getValue() < getMaxValue()) {
2568 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
2569 }
2570 if (getWrapSelectorWheel() || getValue() > getMinValue()) {
2571 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
2572 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07002573 }
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002574
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002575 return info;
2576 }
2577
Svetoslav Ganov791fd312012-05-14 15:12:30 -07002578 private boolean hasVirtualDecrementButton() {
2579 return getWrapSelectorWheel() || getValue() > getMinValue();
2580 }
2581
2582 private boolean hasVirtualIncrementButton() {
2583 return getWrapSelectorWheel() || getValue() < getMaxValue();
2584 }
2585
Svetoslav Ganovd11e6152012-03-20 12:13:02 -07002586 private String getVirtualDecrementButtonText() {
2587 int value = mValue - 1;
2588 if (mWrapSelectorWheel) {
2589 value = getWrappedSelectorIndex(value);
2590 }
2591 if (value >= mMinValue) {
2592 return (mDisplayedValues == null) ? formatNumber(value)
2593 : mDisplayedValues[value - mMinValue];
2594 }
2595 return null;
2596 }
2597
2598 private String getVirtualIncrementButtonText() {
2599 int value = mValue + 1;
2600 if (mWrapSelectorWheel) {
2601 value = getWrappedSelectorIndex(value);
2602 }
2603 if (value <= mMaxValue) {
2604 return (mDisplayedValues == null) ? formatNumber(value)
2605 : mDisplayedValues[value - mMinValue];
2606 }
2607 return null;
2608 }
2609 }
Fabrice Di Megliod88e3052012-09-21 12:15:23 -07002610
2611 static private String formatNumberWithLocale(int value) {
2612 return String.format(Locale.getDefault(), "%d", value);
2613 }
Paul Westbrook7762d932009-12-11 14:13:48 -08002614}