OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / frameworks / base / core / java / android / widget / NumberPicker.java
1 /*
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
17 package android.widget;
18
19 import com.android.internal.R;
20
21 import android.annotation.Widget;
22 import android.content.Context;
23 import android.os.Handler;
24 import android.text.InputFilter;
25 import android.text.InputType;
26 import android.text.Spanned;
27 import android.text.method.NumberKeyListener;
28 import android.util.AttributeSet;
29 import android.view.LayoutInflater;
30 import android.view.View;
31
32 /**
33  * A view for selecting a number
34  *
35  * For a dialog using this view, see {@link android.app.TimePickerDialog}.
36  * @hide
37  */
38 @Widget
39 public class NumberPicker extends LinearLayout {
40
41     /**
42      * The callback interface used to indicate the number value has been adjusted.
43      */
44     public interface OnChangedListener {
45         /**
46          * @param picker The NumberPicker associated with this listener.
47          * @param oldVal The previous value.
48          * @param newVal The new value.
49          */
50         void onChanged(NumberPicker picker, int oldVal, int newVal);
51     }
52
53     /**
54      * Interface used to format the number into a string for presentation
55      */
56     public interface Formatter {
57         String toString(int value);
58     }
59
60     /*
61      * Use a custom NumberPicker formatting callback to use two-digit
62      * minutes strings like "01".  Keeping a static formatter etc. is the
63      * most efficient way to do this; it avoids creating temporary objects
64      * on every call to format().
65      */
66     public static final NumberPicker.Formatter TWO_DIGIT_FORMATTER =
67             new NumberPicker.Formatter() {
68                 final StringBuilder mBuilder = new StringBuilder();
69                 final java.util.Formatter mFmt = new java.util.Formatter(mBuilder);
70                 final Object[] mArgs = new Object[1];
71                 public String toString(int value) {
72                     mArgs[0] = value;
73                     mBuilder.delete(0, mBuilder.length());
74                     mFmt.format("%02d", mArgs);
75                     return mFmt.toString();
76                 }
77         };
78
79     private final Handler mHandler;
80     private final Runnable mRunnable = new Runnable() {
81         public void run() {
82             if (mIncrement) {
83                 changeCurrent(mCurrent + 1);
84                 mHandler.postDelayed(this, mSpeed);
85             } else if (mDecrement) {
86                 changeCurrent(mCurrent - 1);
87                 mHandler.postDelayed(this, mSpeed);
88             }
89         }
90     };
91
92     private final EditText mText;
93     private final InputFilter mNumberInputFilter;
94
95     private String[] mDisplayedValues;
96
97     /**
98      * Lower value of the range of numbers allowed for the NumberPicker
99      */
100     private int mStart;
101
102     /**
103      * Upper value of the range of numbers allowed for the NumberPicker
104      */
105     private int mEnd;
106
107     /**
108      * Current value of this NumberPicker
109      */
110     private int mCurrent;
111
112     /**
113      * Previous value of this NumberPicker.
114      */
115     private int mPrevious;
116     private OnChangedListener mListener;
117     private Formatter mFormatter;
118     private long mSpeed = 300;
119
120     private boolean mIncrement;
121     private boolean mDecrement;
122
123     /**
124      * Create a new number picker
125      * @param context the application environment
126      */
127     public NumberPicker(Context context) {
128         this(context, null);
129     }
130
131     /**
132      * Create a new number picker
133      * @param context the application environment
134      * @param attrs a collection of attributes
135      */
136     public NumberPicker(Context context, AttributeSet attrs) {
137         super(context, attrs);
138         setOrientation(VERTICAL);
139         LayoutInflater inflater =
140                 (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
141         inflater.inflate(R.layout.number_picker, this, true);
142         mHandler = new Handler();
143
144         OnClickListener clickListener = new OnClickListener() {
145             public void onClick(View v) {
146                 validateInput(mText);
147                 if (!mText.hasFocus()) mText.requestFocus();
148
149                 // now perform the increment/decrement
150                 if (R.id.increment == v.getId()) {
151                     changeCurrent(mCurrent + 1);
152                 } else if (R.id.decrement == v.getId()) {
153                     changeCurrent(mCurrent - 1);
154                 }
155             }
156         };
157
158         OnFocusChangeListener focusListener = new OnFocusChangeListener() {
159             public void onFocusChange(View v, boolean hasFocus) {
160
161                 /* When focus is lost check that the text field
162                  * has valid values.
163                  */
164                 if (!hasFocus) {
165                     validateInput(v);
166                 }
167             }
168         };
169
170         OnLongClickListener longClickListener = new OnLongClickListener() {
171             /**
172              * We start the long click here but rely on the {@link NumberPickerButton}
173              * to inform us when the long click has ended.
174              */
175             public boolean onLongClick(View v) {
176                 /* The text view may still have focus so clear it's focus which will
177                  * trigger the on focus changed and any typed values to be pulled.
178                  */
179                 mText.clearFocus();
180
181                 if (R.id.increment == v.getId()) {
182                     mIncrement = true;
183                     mHandler.post(mRunnable);
184                 } else if (R.id.decrement == v.getId()) {
185                     mDecrement = true;
186                     mHandler.post(mRunnable);
187                 }
188                 return true;
189             }
190         };
191
192         InputFilter inputFilter = new NumberPickerInputFilter();
193         mNumberInputFilter = new NumberRangeKeyListener();
194         mIncrementButton = (NumberPickerButton) findViewById(R.id.increment);
195         mIncrementButton.setOnClickListener(clickListener);
196         mIncrementButton.setOnLongClickListener(longClickListener);
197         mIncrementButton.setNumberPicker(this);
198
199         mDecrementButton = (NumberPickerButton) findViewById(R.id.decrement);
200         mDecrementButton.setOnClickListener(clickListener);
201         mDecrementButton.setOnLongClickListener(longClickListener);
202         mDecrementButton.setNumberPicker(this);
203
204         mText = (EditText) findViewById(R.id.timepicker_input);
205         mText.setOnFocusChangeListener(focusListener);
206         mText.setFilters(new InputFilter[] {inputFilter});
207         mText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
208
209         if (!isEnabled()) {
210             setEnabled(false);
211         }
212     }
213
214     /**
215      * Set the enabled state of this view. The interpretation of the enabled
216      * state varies by subclass.
217      *
218      * @param enabled True if this view is enabled, false otherwise.
219      */
220     @Override
221     public void setEnabled(boolean enabled) {
222         super.setEnabled(enabled);
223         mIncrementButton.setEnabled(enabled);
224         mDecrementButton.setEnabled(enabled);
225         mText.setEnabled(enabled);
226     }
227
228     /**
229      * Set the callback that indicates the number has been adjusted by the user.
230      * @param listener the callback, should not be null.
231      */
232     public void setOnChangeListener(OnChangedListener listener) {
233         mListener = listener;
234     }
235
236     /**
237      * Set the formatter that will be used to format the number for presentation
238      * @param formatter the formatter object.  If formatter is null, String.valueOf()
239      * will be used
240      */
241     public void setFormatter(Formatter formatter) {
242         mFormatter = formatter;
243     }
244
245     /**
246      * Set the range of numbers allowed for the number picker. The current
247      * value will be automatically set to the start.
248      *
249      * @param start the start of the range (inclusive)
250      * @param end the end of the range (inclusive)
251      */
252     public void setRange(int start, int end) {
253         setRange(start, end, null/*displayedValues*/);
254     }
255
256     /**
257      * Set the range of numbers allowed for the number picker. The current
258      * value will be automatically set to the start. Also provide a mapping
259      * for values used to display to the user.
260      *
261      * @param start the start of the range (inclusive)
262      * @param end the end of the range (inclusive)
263      * @param displayedValues the values displayed to the user.
264      */
265     public void setRange(int start, int end, String[] displayedValues) {
266         mDisplayedValues = displayedValues;
267         mStart = start;
268         mEnd = end;
269         mCurrent = start;
270         updateView();
271
272         if (displayedValues != null) {
273             // Allow text entry rather than strictly numeric entry.
274             mText.setRawInputType(InputType.TYPE_CLASS_TEXT |
275                     InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
276         }
277     }
278
279     /**
280      * Set the current value for the number picker.
281      *
282      * @param current the current value the start of the range (inclusive)
283      * @throws IllegalArgumentException when current is not within the range
284      *         of of the number picker
285      */
286     public void setCurrent(int current) {
287         if (current < mStart || current > mEnd) {
288             throw new IllegalArgumentException(
289                     "current should be >= start and <= end");
290         }
291         mCurrent = current;
292         updateView();
293     }
294
295     /**
296      * Sets the speed at which the numbers will scroll when the +/-
297      * buttons are longpressed
298      *
299      * @param speed The speed (in milliseconds) at which the numbers will scroll
300      * default 300ms
301      */
302     public void setSpeed(long speed) {
303         mSpeed = speed;
304     }
305
306     private String formatNumber(int value) {
307         return (mFormatter != null)
308                 ? mFormatter.toString(value)
309                 : String.valueOf(value);
310     }
311
312     /**
313      * Sets the current value of this NumberPicker, and sets mPrevious to the previous
314      * value.  If current is greater than mEnd less than mStart, the value of mCurrent
315      * is wrapped around.
316      *
317      * Subclasses can override this to change the wrapping behavior
318      *
319      * @param current the new value of the NumberPicker
320      */
321     protected void changeCurrent(int current) {
322         // Wrap around the values if we go past the start or end
323         if (current > mEnd) {
324             current = mStart;
325         } else if (current < mStart) {
326             current = mEnd;
327         }
328         mPrevious = mCurrent;
329         mCurrent = current;
330         notifyChange();
331         updateView();
332     }
333
334     /**
335      * Notifies the listener, if registered, of a change of the value of this
336      * NumberPicker.
337      */
338     private void notifyChange() {
339         if (mListener != null) {
340             mListener.onChanged(this, mPrevious, mCurrent);
341         }
342     }
343
344     /**
345      * Updates the view of this NumberPicker.  If displayValues were specified
346      * in {@link #setRange}, the string corresponding to the index specified by
347      * the current value will be returned.  Otherwise, the formatter specified
348      * in {@link setFormatter} will be used to format the number.
349      */
350     private void updateView() {
351         /* If we don't have displayed values then use the
352          * current number else find the correct value in the
353          * displayed values for the current number.
354          */
355         if (mDisplayedValues == null) {
356             mText.setText(formatNumber(mCurrent));
357         } else {
358             mText.setText(mDisplayedValues[mCurrent - mStart]);
359         }
360         mText.setSelection(mText.getText().length());
361     }
362
363     private void validateCurrentView(CharSequence str) {
364         int val = getSelectedPos(str.toString());
365         if ((val >= mStart) && (val <= mEnd)) {
366             if (mCurrent != val) {
367                 mPrevious = mCurrent;
368                 mCurrent = val;
369                 notifyChange();
370             }
371         }
372         updateView();
373     }
374
375     private void validateInput(View v) {
376         String str = String.valueOf(((TextView) v).getText());
377         if ("".equals(str)) {
378
379             // Restore to the old value as we don't allow empty values
380             updateView();
381         } else {
382
383             // Check the new value and ensure it's in range
384             validateCurrentView(str);
385         }
386     }
387
388     /**
389      * @hide
390      */
391     public void cancelIncrement() {
392         mIncrement = false;
393     }
394
395     /**
396      * @hide
397      */
398     public void cancelDecrement() {
399         mDecrement = false;
400     }
401
402     private static final char[] DIGIT_CHARACTERS = new char[] {
403         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
404     };
405
406     private NumberPickerButton mIncrementButton;
407     private NumberPickerButton mDecrementButton;
408
409     private class NumberPickerInputFilter implements InputFilter {
410         public CharSequence filter(CharSequence source, int start, int end,
411                 Spanned dest, int dstart, int dend) {
412             if (mDisplayedValues == null) {
413                 return mNumberInputFilter.filter(source, start, end, dest, dstart, dend);
414             }
415             CharSequence filtered = String.valueOf(source.subSequence(start, end));
416             String result = String.valueOf(dest.subSequence(0, dstart))
417                     + filtered
418                     + dest.subSequence(dend, dest.length());
419             String str = String.valueOf(result).toLowerCase();
420             for (String val : mDisplayedValues) {
421                 val = val.toLowerCase();
422                 if (val.startsWith(str)) {
423                     return filtered;
424                 }
425             }
426             return "";
427         }
428     }
429
430     private class NumberRangeKeyListener extends NumberKeyListener {
431
432         // XXX This doesn't allow for range limits when controlled by a
433         // soft input method!
434         public int getInputType() {
435             return InputType.TYPE_CLASS_NUMBER;
436         }
437
438         @Override
439         protected char[] getAcceptedChars() {
440             return DIGIT_CHARACTERS;
441         }
442
443         @Override
444         public CharSequence filter(CharSequence source, int start, int end,
445                 Spanned dest, int dstart, int dend) {
446
447             CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
448             if (filtered == null) {
449                 filtered = source.subSequence(start, end);
450             }
451
452             String result = String.valueOf(dest.subSequence(0, dstart))
453                     + filtered
454                     + dest.subSequence(dend, dest.length());
455
456             if ("".equals(result)) {
457                 return result;
458             }
459             int val = getSelectedPos(result);
460
461             /* Ensure the user can't type in a value greater
462              * than the max allowed. We have to allow less than min
463              * as the user might want to delete some numbers
464              * and then type a new number.
465              */
466             if (val > mEnd) {
467                 return "";
468             } else {
469                 return filtered;
470             }
471         }
472     }
473
474     private int getSelectedPos(String str) {
475         if (mDisplayedValues == null) {
476             try {
477                 return Integer.parseInt(str);
478             } catch (NumberFormatException e) {
479                 /* Ignore as if it's not a number we don't care */
480             }
481         } else {
482             for (int i = 0; i < mDisplayedValues.length; i++) {
483                 /* Don't force the user to type in jan when ja will do */
484                 str = str.toLowerCase();
485                 if (mDisplayedValues[i].toLowerCase().startsWith(str)) {
486                     return mStart + i;
487                 }
488             }
489
490             /* The user might have typed in a number into the month field i.e.
491              * 10 instead of OCT so support that too.
492              */
493             try {
494                 return Integer.parseInt(str);
495             } catch (NumberFormatException e) {
496
497                 /* Ignore as if it's not a number we don't care */
498             }
499         }
500         return mStart;
501     }
502
503     /**
504      * Returns the current value of the NumberPicker
505      * @return the current value.
506      */
507     public int getCurrent() {
508         return mCurrent;
509     }
510
511     /**
512      * Returns the upper value of the range of the NumberPicker
513      * @return the uppper number of the range.
514      */
515     protected int getEndRange() {
516         return mEnd;
517     }
518
519     /**
520      * Returns the lower value of the range of the NumberPicker
521      * @return the lower number of the range.
522      */
523     protected int getBeginRange() {
524         return mStart;
525     }
526 }