TimePicker.java revision 9066cfe9886ac131c34d59ed0e2d287b0e3c0087
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 android.annotation.Widget;
20import android.content.Context;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.util.AttributeSet;
24import android.view.LayoutInflater;
25import android.view.View;
26
27import com.android.internal.R;
28import com.android.internal.widget.NumberPicker;
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@Widget
50public class TimePicker extends FrameLayout {
51
52    /**
53     * A no-op callback used in the constructor to avoid null checks
54     * later in the code.
55     */
56    private static final OnTimeChangedListener NO_OP_CHANGE_LISTENER = new OnTimeChangedListener() {
57        public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
58        }
59    };
60
61    // state
62    private int mCurrentHour = 0; // 0-23
63    private int mCurrentMinute = 0; // 0-59
64    private Boolean mIs24HourView = false;
65    private boolean mIsAm;
66
67    // ui components
68    private final NumberPicker mHourPicker;
69    private final NumberPicker mMinutePicker;
70    private final Button mAmPmButton;
71    private final String mAmText;
72    private final String mPmText;
73
74    // callbacks
75    private OnTimeChangedListener mOnTimeChangedListener;
76
77    /**
78     * The callback interface used to indicate the time has been adjusted.
79     */
80    public interface OnTimeChangedListener {
81
82        /**
83         * @param view The view associated with this listener.
84         * @param hourOfDay The current hour.
85         * @param minute The current minute.
86         */
87        void onTimeChanged(TimePicker view, int hourOfDay, int minute);
88    }
89
90    public TimePicker(Context context) {
91        this(context, null);
92    }
93
94    public TimePicker(Context context, AttributeSet attrs) {
95        this(context, attrs, 0);
96    }
97
98    public TimePicker(Context context, AttributeSet attrs, int defStyle) {
99        super(context, attrs, defStyle);
100
101        LayoutInflater inflater =
102                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
103        inflater.inflate(R.layout.time_picker,
104            this, // we are the parent
105            true);
106
107        // hour
108        mHourPicker = (NumberPicker) findViewById(R.id.hour);
109        mHourPicker.setOnChangeListener(new NumberPicker.OnChangedListener() {
110            public void onChanged(NumberPicker spinner, int oldVal, int newVal) {
111                mCurrentHour = newVal;
112                if (!mIs24HourView) {
113                    // adjust from [1-12] to [0-11] internally, with the times
114                    // written "12:xx" being the start of the half-day
115                    if (mCurrentHour == 12) {
116                        mCurrentHour = 0;
117                    }
118                    if (!mIsAm) {
119                        // PM means 12 hours later than nominal
120                        mCurrentHour += 12;
121                    }
122                }
123                onTimeChanged();
124            }
125        });
126
127        // digits of minute
128        mMinutePicker = (NumberPicker) findViewById(R.id.minute);
129        mMinutePicker.setRange(0, 59);
130        mMinutePicker.setSpeed(100);
131        mMinutePicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
132        mMinutePicker.setOnChangeListener(new NumberPicker.OnChangedListener() {
133            public void onChanged(NumberPicker spinner, int oldVal, int newVal) {
134                mCurrentMinute = newVal;
135                onTimeChanged();
136            }
137        });
138
139        // am/pm
140        mAmPmButton = (Button) findViewById(R.id.amPm);
141
142        // now that the hour/minute picker objects have been initialized, set
143        // the hour range properly based on the 12/24 hour display mode.
144        configurePickerRanges();
145
146        // initialize to current time
147        Calendar cal = Calendar.getInstance();
148        setOnTimeChangedListener(NO_OP_CHANGE_LISTENER);
149
150        // by default we're not in 24 hour mode
151        setCurrentHour(cal.get(Calendar.HOUR_OF_DAY));
152        setCurrentMinute(cal.get(Calendar.MINUTE));
153
154        mIsAm = (mCurrentHour < 12);
155
156        /* Get the localized am/pm strings and use them in the spinner */
157        DateFormatSymbols dfs = new DateFormatSymbols();
158        String[] dfsAmPm = dfs.getAmPmStrings();
159        mAmText = dfsAmPm[Calendar.AM];
160        mPmText = dfsAmPm[Calendar.PM];
161        mAmPmButton.setText(mIsAm ? mAmText : mPmText);
162        mAmPmButton.setOnClickListener(new OnClickListener() {
163            public void onClick(View v) {
164                requestFocus();
165                if (mIsAm) {
166
167                    // Currently AM switching to PM
168                    if (mCurrentHour < 12) {
169                        mCurrentHour += 12;
170                    }
171                } else {
172
173                    // Currently PM switching to AM
174                    if (mCurrentHour >= 12) {
175                        mCurrentHour -= 12;
176                    }
177                }
178                mIsAm = !mIsAm;
179                mAmPmButton.setText(mIsAm ? mAmText : mPmText);
180                onTimeChanged();
181            }
182        });
183
184        if (!isEnabled()) {
185            setEnabled(false);
186        }
187    }
188
189    @Override
190    public void setEnabled(boolean enabled) {
191        super.setEnabled(enabled);
192        mMinutePicker.setEnabled(enabled);
193        mHourPicker.setEnabled(enabled);
194        mAmPmButton.setEnabled(enabled);
195    }
196
197    /**
198     * Used to save / restore state of time picker
199     */
200    private static class SavedState extends BaseSavedState {
201
202        private final int mHour;
203        private final int mMinute;
204
205        private SavedState(Parcelable superState, int hour, int minute) {
206            super(superState);
207            mHour = hour;
208            mMinute = minute;
209        }
210
211        private SavedState(Parcel in) {
212            super(in);
213            mHour = in.readInt();
214            mMinute = in.readInt();
215        }
216
217        public int getHour() {
218            return mHour;
219        }
220
221        public int getMinute() {
222            return mMinute;
223        }
224
225        @Override
226        public void writeToParcel(Parcel dest, int flags) {
227            super.writeToParcel(dest, flags);
228            dest.writeInt(mHour);
229            dest.writeInt(mMinute);
230        }
231
232        public static final Parcelable.Creator<SavedState> CREATOR
233                = new Creator<SavedState>() {
234            public SavedState createFromParcel(Parcel in) {
235                return new SavedState(in);
236            }
237
238            public SavedState[] newArray(int size) {
239                return new SavedState[size];
240            }
241        };
242    }
243
244    @Override
245    protected Parcelable onSaveInstanceState() {
246        Parcelable superState = super.onSaveInstanceState();
247        return new SavedState(superState, mCurrentHour, mCurrentMinute);
248    }
249
250    @Override
251    protected void onRestoreInstanceState(Parcelable state) {
252        SavedState ss = (SavedState) state;
253        super.onRestoreInstanceState(ss.getSuperState());
254        setCurrentHour(ss.getHour());
255        setCurrentMinute(ss.getMinute());
256    }
257
258    /**
259     * Set the callback that indicates the time has been adjusted by the user.
260     * @param onTimeChangedListener the callback, should not be null.
261     */
262    public void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener) {
263        mOnTimeChangedListener = onTimeChangedListener;
264    }
265
266    /**
267     * @return The current hour (0-23).
268     */
269    public Integer getCurrentHour() {
270        return mCurrentHour;
271    }
272
273    /**
274     * Set the current hour.
275     */
276    public void setCurrentHour(Integer currentHour) {
277        this.mCurrentHour = currentHour;
278        updateHourDisplay();
279    }
280
281    /**
282     * Set whether in 24 hour or AM/PM mode.
283     * @param is24HourView True = 24 hour mode. False = AM/PM.
284     */
285    public void setIs24HourView(Boolean is24HourView) {
286        if (mIs24HourView != is24HourView) {
287            mIs24HourView = is24HourView;
288            configurePickerRanges();
289            updateHourDisplay();
290        }
291    }
292
293    /**
294     * @return true if this is in 24 hour view else false.
295     */
296    public boolean is24HourView() {
297        return mIs24HourView;
298    }
299
300    /**
301     * @return The current minute.
302     */
303    public Integer getCurrentMinute() {
304        return mCurrentMinute;
305    }
306
307    /**
308     * Set the current minute (0-59).
309     */
310    public void setCurrentMinute(Integer currentMinute) {
311        this.mCurrentMinute = currentMinute;
312        updateMinuteDisplay();
313    }
314
315    @Override
316    public int getBaseline() {
317        return mHourPicker.getBaseline();
318    }
319
320    /**
321     * Set the state of the spinners appropriate to the current hour.
322     */
323    private void updateHourDisplay() {
324        int currentHour = mCurrentHour;
325        if (!mIs24HourView) {
326            // convert [0,23] ordinal to wall clock display
327            if (currentHour > 12) currentHour -= 12;
328            else if (currentHour == 0) currentHour = 12;
329        }
330        mHourPicker.setCurrent(currentHour);
331        mIsAm = mCurrentHour < 12;
332        mAmPmButton.setText(mIsAm ? mAmText : mPmText);
333        onTimeChanged();
334    }
335
336    private void configurePickerRanges() {
337        if (mIs24HourView) {
338            mHourPicker.setRange(0, 23);
339            mHourPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
340            mAmPmButton.setVisibility(View.GONE);
341        } else {
342            mHourPicker.setRange(1, 12);
343            mHourPicker.setFormatter(null);
344            mAmPmButton.setVisibility(View.VISIBLE);
345        }
346    }
347
348    private void onTimeChanged() {
349        mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute());
350    }
351
352    /**
353     * Set the state of the spinners appropriate to the current minute.
354     */
355    private void updateMinuteDisplay() {
356        mMinutePicker.setCurrent(mCurrentMinute);
357        mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute());
358    }
359}
360
361