TimePicker.java revision 25f84f323c607bbd9133432fd789ba29b2dcd4d4
1/*
2 * Copyright (C) 2007 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
17package android.widget;
18
19import com.android.internal.R;
20
21import android.annotation.Widget;
22import android.content.Context;
23import android.os.Parcel;
24import android.os.Parcelable;
25import android.util.AttributeSet;
26import android.view.LayoutInflater;
27import android.view.View;
28import android.widget.NumberPicker.OnValueChangedListener;
29
30import java.text.DateFormatSymbols;
31import java.util.Calendar;
32
33/**
34 * A view for selecting the time of day, in either 24 hour or AM/PM mode.
35 *
36 * The hour, each minute digit, and AM/PM (if applicable) can be conrolled by
37 * vertical spinners.
38 *
39 * The hour can be entered by keyboard input.  Entering in two digit hours
40 * can be accomplished by hitting two digits within a timeout of about a
41 * second (e.g. '1' then '2' to select 12).
42 *
43 * The minutes can be entered by entering single digits.
44 *
45 * Under AM/PM mode, the user can hit 'a', 'A", 'p' or 'P' to pick.
46 *
47 * For a dialog using this view, see {@link android.app.TimePickerDialog}.
48 *
49 * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-timepicker.html">Time Picker
50 * tutorial</a>.</p>
51 */
52@Widget
53public class TimePicker extends FrameLayout {
54
55    private static final boolean DEFAULT_ENABLED_STATE = true;
56
57    /**
58     * A no-op callback used in the constructor to avoid null checks
59     * later in the code.
60     */
61    private static final OnTimeChangedListener NO_OP_CHANGE_LISTENER = new OnTimeChangedListener() {
62        public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
63        }
64    };
65
66    // state
67    private int mCurrentHour = 0; // 0-23
68    private int mCurrentMinute = 0; // 0-59
69    private Boolean mIs24HourView = false;
70    private boolean mIsAm;
71
72    // ui components
73    private final NumberPicker mHourSpinner;
74    private final NumberPicker mMinuteSpinner;
75    private final NumberPicker mAmPmSpinner;
76    private final TextView mDivider;
77
78    private final String[] mAmPmStrings;
79
80    private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
81
82    // callbacks
83    private OnTimeChangedListener mOnTimeChangedListener;
84
85    /**
86     * The callback interface used to indicate the time has been adjusted.
87     */
88    public interface OnTimeChangedListener {
89
90        /**
91         * @param view The view associated with this listener.
92         * @param hourOfDay The current hour.
93         * @param minute The current minute.
94         */
95        void onTimeChanged(TimePicker view, int hourOfDay, int minute);
96    }
97
98    public TimePicker(Context context) {
99        this(context, null);
100    }
101
102    public TimePicker(Context context, AttributeSet attrs) {
103        this(context, attrs, 0);
104    }
105
106    public TimePicker(Context context, AttributeSet attrs, int defStyle) {
107        super(context, attrs, defStyle);
108
109        LayoutInflater inflater =
110                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
111        inflater.inflate(R.layout.time_picker,
112            this, // we are the parent
113            true);
114
115        // hour
116        mHourSpinner = (NumberPicker) findViewById(R.id.hour);
117        mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangedListener() {
118            public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
119                mCurrentHour = newVal;
120                if (!mIs24HourView) {
121                    // adjust from [1-12] to [0-11] internally, with the times
122                    // written "12:xx" being the start of the half-day
123                    if (mCurrentHour == 12) {
124                        mCurrentHour = 0;
125                    }
126                    if (!mIsAm) {
127                        // PM means 12 hours later than nominal
128                        mCurrentHour += 12;
129                    }
130                }
131                onTimeChanged();
132            }
133        });
134
135        // divider
136        mDivider = (TextView) findViewById(R.id.divider);
137        mDivider.setText(R.string.time_picker_separator);
138
139        // digits of minute
140        mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
141        mMinuteSpinner.setMinValue(0);
142        mMinuteSpinner.setMaxValue(59);
143        mMinuteSpinner.setOnLongPressUpdateInterval(100);
144        mMinuteSpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
145        mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangedListener() {
146            public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
147                mCurrentMinute = newVal;
148                onTimeChanged();
149            }
150        });
151
152        // am/pm
153        mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
154        mAmPmSpinner.setOnValueChangedListener(new OnValueChangedListener() {
155            public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
156                picker.requestFocus();
157                if (mIsAm) {
158                    // Currently AM switching to PM
159                    if (mCurrentHour < 12) {
160                        mCurrentHour += 12;
161                    }
162                } else {
163                    // Currently PM switching to AM
164                    if (mCurrentHour >= 12) {
165                        mCurrentHour -= 12;
166                    }
167                }
168                mIsAm = !mIsAm;
169                onTimeChanged();
170            }
171        });
172
173        /* Get the localized am/pm strings and use them in the spinner */
174        mAmPmStrings = new DateFormatSymbols().getAmPmStrings();
175
176        // now that the hour/minute picker objects have been initialized, set
177        // the hour range properly based on the 12/24 hour display mode.
178        configurePickerRanges();
179
180        // initialize to current time
181        Calendar cal = Calendar.getInstance();
182        setOnTimeChangedListener(NO_OP_CHANGE_LISTENER);
183
184        // by default we're not in 24 hour mode
185        setCurrentHour(cal.get(Calendar.HOUR_OF_DAY));
186        setCurrentMinute(cal.get(Calendar.MINUTE));
187
188        if (!isEnabled()) {
189            setEnabled(false);
190        }
191    }
192
193    @Override
194    public void setEnabled(boolean enabled) {
195        if (mIsEnabled == enabled) {
196            return;
197        }
198        super.setEnabled(enabled);
199        mMinuteSpinner.setEnabled(enabled);
200        mDivider.setEnabled(enabled);
201        mHourSpinner.setEnabled(enabled);
202        mAmPmSpinner.setEnabled(enabled);
203        mIsEnabled = enabled;
204    }
205
206    @Override
207    public boolean isEnabled() {
208        return mIsEnabled;
209    }
210
211    /**
212     * Used to save / restore state of time picker
213     */
214    private static class SavedState extends BaseSavedState {
215
216        private final int mHour;
217        private final int mMinute;
218
219        private SavedState(Parcelable superState, int hour, int minute) {
220            super(superState);
221            mHour = hour;
222            mMinute = minute;
223        }
224
225        private SavedState(Parcel in) {
226            super(in);
227            mHour = in.readInt();
228            mMinute = in.readInt();
229        }
230
231        public int getHour() {
232            return mHour;
233        }
234
235        public int getMinute() {
236            return mMinute;
237        }
238
239        @Override
240        public void writeToParcel(Parcel dest, int flags) {
241            super.writeToParcel(dest, flags);
242            dest.writeInt(mHour);
243            dest.writeInt(mMinute);
244        }
245
246        @SuppressWarnings("unused")
247        public static final Parcelable.Creator<SavedState> CREATOR
248                = new Creator<SavedState>() {
249            public SavedState createFromParcel(Parcel in) {
250                return new SavedState(in);
251            }
252
253            public SavedState[] newArray(int size) {
254                return new SavedState[size];
255            }
256        };
257    }
258
259    @Override
260    protected Parcelable onSaveInstanceState() {
261        Parcelable superState = super.onSaveInstanceState();
262        return new SavedState(superState, mCurrentHour, mCurrentMinute);
263    }
264
265    @Override
266    protected void onRestoreInstanceState(Parcelable state) {
267        SavedState ss = (SavedState) state;
268        super.onRestoreInstanceState(ss.getSuperState());
269        setCurrentHour(ss.getHour());
270        setCurrentMinute(ss.getMinute());
271    }
272
273    /**
274     * Set the callback that indicates the time has been adjusted by the user.
275     * @param onTimeChangedListener the callback, should not be null.
276     */
277    public void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener) {
278        mOnTimeChangedListener = onTimeChangedListener;
279    }
280
281    /**
282     * @return The current hour (0-23).
283     */
284    public Integer getCurrentHour() {
285        return mCurrentHour;
286    }
287
288    /**
289     * Set the current hour.
290     */
291    public void setCurrentHour(Integer currentHour) {
292        this.mCurrentHour = currentHour;
293        updateHourDisplay();
294    }
295
296    /**
297     * Set whether in 24 hour or AM/PM mode.
298     * @param is24HourView True = 24 hour mode. False = AM/PM.
299     */
300    public void setIs24HourView(Boolean is24HourView) {
301        if (mIs24HourView != is24HourView) {
302            mIs24HourView = is24HourView;
303            configurePickerRanges();
304            updateHourDisplay();
305        }
306    }
307
308    /**
309     * @return true if this is in 24 hour view else false.
310     */
311    public boolean is24HourView() {
312        return mIs24HourView;
313    }
314
315    /**
316     * @return The current minute.
317     */
318    public Integer getCurrentMinute() {
319        return mCurrentMinute;
320    }
321
322    /**
323     * Set the current minute (0-59).
324     */
325    public void setCurrentMinute(Integer currentMinute) {
326        this.mCurrentMinute = currentMinute;
327        updateMinuteDisplay();
328    }
329
330    @Override
331    public int getBaseline() {
332        return mHourSpinner.getBaseline();
333    }
334
335    /**
336     * Set the state of the spinners appropriate to the current hour.
337     */
338    private void updateHourDisplay() {
339        int currentHour = mCurrentHour;
340        if (!mIs24HourView) {
341            // convert [0,23] ordinal to wall clock display
342            if (currentHour > 12) {
343                currentHour -= 12;
344            } else if (currentHour == 0) {
345                currentHour = 12;
346            }
347        }
348        mHourSpinner.setValue(currentHour);
349        mIsAm = mCurrentHour < 12;
350        mAmPmSpinner.setValue(mIsAm ? Calendar.AM : Calendar.PM);
351        onTimeChanged();
352    }
353
354    private void configurePickerRanges() {
355        if (mIs24HourView) {
356            mHourSpinner.setMinValue(0);
357            mHourSpinner.setMaxValue(23);
358            mHourSpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
359            mAmPmSpinner.setVisibility(View.GONE);
360        } else {
361            mHourSpinner.setMinValue(1);
362            mHourSpinner.setMaxValue(12);
363            mHourSpinner.setFormatter(null);
364            mAmPmSpinner.setVisibility(View.VISIBLE);
365            mAmPmSpinner.setMinValue(0);
366            mAmPmSpinner.setMaxValue(1);
367            mAmPmSpinner.setDisplayedValues(mAmPmStrings);
368        }
369    }
370
371    private void onTimeChanged() {
372        if (mOnTimeChangedListener != null) {
373            mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute());
374        }
375    }
376
377    /**
378     * Set the state of the spinners appropriate to the current minute.
379     */
380    private void updateMinuteDisplay() {
381        mMinuteSpinner.setValue(mCurrentMinute);
382        onTimeChanged();
383    }
384}
385