| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.widget; |
| |
| import com.android.internal.R; |
| |
| import android.annotation.Widget; |
| import android.content.Context; |
| import android.content.res.TypedArray; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.util.AttributeSet; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.widget.NumberPicker.OnValueChangeListener; |
| |
| import java.text.DateFormatSymbols; |
| import java.util.Calendar; |
| |
| /** |
| * A view for selecting the time of day, in either 24 hour or AM/PM mode. The |
| * hour, each minute digit, and AM/PM (if applicable) can be conrolled by |
| * vertical spinners. The hour can be entered by keyboard input. Entering in two |
| * digit hours can be accomplished by hitting two digits within a timeout of |
| * about a second (e.g. '1' then '2' to select 12). The minutes can be entered |
| * by entering single digits. Under AM/PM mode, the user can hit 'a', 'A", 'p' |
| * or 'P' to pick. For a dialog using this view, see |
| * {@link android.app.TimePickerDialog}. |
| *<p> |
| * See the <a href="{@docRoot} |
| * resources/tutorials/views/hello-timepicker.html">Time Picker tutorial</a>. |
| * </p> |
| */ |
| @Widget |
| public class TimePicker extends FrameLayout { |
| |
| private static final boolean DEFAULT_ENABLED_STATE = true; |
| |
| private static final int HOURS_IN_HALF_DAY = 12; |
| |
| /** |
| * A no-op callback used in the constructor to avoid null checks later in |
| * the code. |
| */ |
| private static final OnTimeChangedListener NO_OP_CHANGE_LISTENER = new OnTimeChangedListener() { |
| public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { |
| } |
| }; |
| |
| // state |
| private boolean mIs24HourView; |
| |
| private boolean mIsAm; |
| |
| // ui components |
| private final NumberPicker mHourSpinner; |
| |
| private final NumberPicker mMinuteSpinner; |
| |
| private final NumberPicker mAmPmSpinner; |
| |
| private final TextView mDivider; |
| |
| // Note that the legacy implementation of the TimePicker is |
| // using a button for toggling between AM/PM while the new |
| // version uses a NumberPicker spinner. Therefore the code |
| // accommodates these two cases to be backwards compatible. |
| private final Button mAmPmButton; |
| |
| private final String[] mAmPmStrings; |
| |
| private boolean mIsEnabled = DEFAULT_ENABLED_STATE; |
| |
| // callbacks |
| private OnTimeChangedListener mOnTimeChangedListener; |
| |
| /** |
| * The callback interface used to indicate the time has been adjusted. |
| */ |
| public interface OnTimeChangedListener { |
| |
| /** |
| * @param view The view associated with this listener. |
| * @param hourOfDay The current hour. |
| * @param minute The current minute. |
| */ |
| void onTimeChanged(TimePicker view, int hourOfDay, int minute); |
| } |
| |
| public TimePicker(Context context) { |
| this(context, null); |
| } |
| |
| public TimePicker(Context context, AttributeSet attrs) { |
| this(context, attrs, R.attr.timePickerStyle); |
| } |
| |
| public TimePicker(Context context, AttributeSet attrs, int defStyle) { |
| super(context, attrs, defStyle); |
| |
| // process style attributes |
| TypedArray attributesArray = context.obtainStyledAttributes( |
| attrs, R.styleable.TimePicker, defStyle, 0); |
| int layoutResourceId = attributesArray.getResourceId( |
| R.styleable.TimePicker_layout, R.layout.time_picker); |
| attributesArray.recycle(); |
| |
| LayoutInflater inflater = (LayoutInflater) context.getSystemService( |
| Context.LAYOUT_INFLATER_SERVICE); |
| inflater.inflate(layoutResourceId, this, true); |
| |
| // hour |
| mHourSpinner = (NumberPicker) findViewById(R.id.hour); |
| mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { |
| public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { |
| if (!is24HourView()) { |
| int minValue = mHourSpinner.getMinValue(); |
| int maxValue = mHourSpinner.getMaxValue(); |
| // toggle AM/PM if the spinner has wrapped and not in 24 |
| // format |
| if ((oldVal == maxValue && newVal == minValue) |
| || (oldVal == minValue && newVal == maxValue)) { |
| mIsAm = !mIsAm; |
| updateAmPmControl(); |
| } |
| } |
| onTimeChanged(); |
| } |
| }); |
| |
| // divider (only for the new widget style) |
| mDivider = (TextView) findViewById(R.id.divider); |
| if (mDivider != null) { |
| mDivider.setText(R.string.time_picker_separator); |
| } |
| |
| // minute |
| mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); |
| mMinuteSpinner.setMinValue(0); |
| mMinuteSpinner.setMaxValue(59); |
| mMinuteSpinner.setOnLongPressUpdateInterval(100); |
| mMinuteSpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); |
| mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { |
| public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { |
| int minValue = mMinuteSpinner.getMinValue(); |
| int maxValue = mMinuteSpinner.getMaxValue(); |
| if (oldVal == maxValue && newVal == minValue) { |
| int currentHour = mHourSpinner.getValue(); |
| // toggle AM/PM if the spinner is about to wrap |
| if (!is24HourView() && currentHour == mHourSpinner.getMaxValue()) { |
| mIsAm = !mIsAm; |
| updateAmPmControl(); |
| } |
| mHourSpinner.setValue(currentHour + 1); |
| } else if (oldVal == minValue && newVal == maxValue) { |
| int currentHour = mHourSpinner.getValue(); |
| // toggle AM/PM if the spinner is about to wrap |
| if (!is24HourView() && currentHour == mHourSpinner.getMinValue()) { |
| mIsAm = !mIsAm; |
| updateAmPmControl(); |
| } |
| mHourSpinner.setValue(currentHour - 1); |
| } |
| onTimeChanged(); |
| } |
| }); |
| |
| /* Get the localized am/pm strings and use them in the spinner */ |
| mAmPmStrings = new DateFormatSymbols().getAmPmStrings(); |
| |
| // am/pm |
| View amPmView = findViewById(R.id.amPm); |
| if (amPmView instanceof Button) { |
| mAmPmSpinner = null; |
| mAmPmButton = (Button) amPmView; |
| mAmPmButton.setOnClickListener(new OnClickListener() { |
| public void onClick(View button) { |
| button.requestFocus(); |
| mIsAm = !mIsAm; |
| updateAmPmControl(); |
| } |
| }); |
| } else { |
| mAmPmButton = null; |
| mAmPmSpinner = (NumberPicker) amPmView; |
| mAmPmSpinner.setMinValue(0); |
| mAmPmSpinner.setMaxValue(1); |
| mAmPmSpinner.setDisplayedValues(mAmPmStrings); |
| mAmPmSpinner.setOnValueChangedListener(new OnValueChangeListener() { |
| public void onValueChange(NumberPicker picker, int oldVal, int newVal) { |
| picker.requestFocus(); |
| mIsAm = !mIsAm; |
| updateAmPmControl(); |
| } |
| }); |
| } |
| |
| // update controls to initial state |
| updateHourControl(); |
| updateAmPmControl(); |
| |
| // initialize to current time |
| Calendar calendar = Calendar.getInstance(); |
| setOnTimeChangedListener(NO_OP_CHANGE_LISTENER); |
| |
| // set to current time |
| setCurrentHour(calendar.get(Calendar.HOUR_OF_DAY)); |
| setCurrentMinute(calendar.get(Calendar.MINUTE)); |
| |
| if (!isEnabled()) { |
| setEnabled(false); |
| } |
| } |
| |
| @Override |
| public void setEnabled(boolean enabled) { |
| if (mIsEnabled == enabled) { |
| return; |
| } |
| super.setEnabled(enabled); |
| mMinuteSpinner.setEnabled(enabled); |
| if (mDivider != null) { |
| mDivider.setEnabled(enabled); |
| } |
| mHourSpinner.setEnabled(enabled); |
| if (mAmPmSpinner != null) { |
| mAmPmSpinner.setEnabled(enabled); |
| } else { |
| mAmPmButton.setEnabled(enabled); |
| } |
| mIsEnabled = enabled; |
| } |
| |
| @Override |
| public boolean isEnabled() { |
| return mIsEnabled; |
| } |
| |
| /** |
| * Used to save / restore state of time picker |
| */ |
| private static class SavedState extends BaseSavedState { |
| |
| private final int mHour; |
| |
| private final int mMinute; |
| |
| private SavedState(Parcelable superState, int hour, int minute) { |
| super(superState); |
| mHour = hour; |
| mMinute = minute; |
| } |
| |
| private SavedState(Parcel in) { |
| super(in); |
| mHour = in.readInt(); |
| mMinute = in.readInt(); |
| } |
| |
| public int getHour() { |
| return mHour; |
| } |
| |
| public int getMinute() { |
| return mMinute; |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| super.writeToParcel(dest, flags); |
| dest.writeInt(mHour); |
| dest.writeInt(mMinute); |
| } |
| |
| @SuppressWarnings("unused") |
| public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() { |
| public SavedState createFromParcel(Parcel in) { |
| return new SavedState(in); |
| } |
| |
| public SavedState[] newArray(int size) { |
| return new SavedState[size]; |
| } |
| }; |
| } |
| |
| @Override |
| protected Parcelable onSaveInstanceState() { |
| Parcelable superState = super.onSaveInstanceState(); |
| return new SavedState(superState, getCurrentHour(), getCurrentMinute()); |
| } |
| |
| @Override |
| protected void onRestoreInstanceState(Parcelable state) { |
| SavedState ss = (SavedState) state; |
| super.onRestoreInstanceState(ss.getSuperState()); |
| setCurrentHour(ss.getHour()); |
| setCurrentMinute(ss.getMinute()); |
| } |
| |
| /** |
| * Set the callback that indicates the time has been adjusted by the user. |
| * |
| * @param onTimeChangedListener the callback, should not be null. |
| */ |
| public void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener) { |
| mOnTimeChangedListener = onTimeChangedListener; |
| } |
| |
| /** |
| * @return The current hour in the range (0-23). |
| */ |
| public Integer getCurrentHour() { |
| int currentHour = mHourSpinner.getValue(); |
| if (is24HourView() || mIsAm) { |
| return currentHour; |
| } else { |
| return (currentHour == HOURS_IN_HALF_DAY) ? 0 : currentHour + HOURS_IN_HALF_DAY; |
| } |
| } |
| |
| /** |
| * Set the current hour. |
| */ |
| public void setCurrentHour(Integer currentHour) { |
| // why was Integer used in the first place? |
| if (currentHour == null || currentHour == getCurrentHour()) { |
| return; |
| } |
| if (!is24HourView()) { |
| // convert [0,23] ordinal to wall clock display |
| if (currentHour > HOURS_IN_HALF_DAY) { |
| currentHour -= HOURS_IN_HALF_DAY; |
| mIsAm = false; |
| } else { |
| if (currentHour == 0) { |
| currentHour = HOURS_IN_HALF_DAY; |
| } |
| mIsAm = true; |
| } |
| updateAmPmControl(); |
| } |
| mHourSpinner.setValue(currentHour); |
| onTimeChanged(); |
| } |
| |
| /** |
| * Set whether in 24 hour or AM/PM mode. |
| * |
| * @param is24HourView True = 24 hour mode. False = AM/PM. |
| */ |
| public void setIs24HourView(Boolean is24HourView) { |
| if (mIs24HourView == is24HourView) { |
| return; |
| } |
| mIs24HourView = is24HourView; |
| // cache the current hour since spinner range changes |
| int currentHour = getCurrentHour(); |
| updateHourControl(); |
| // set value after spinner range is updated |
| setCurrentHour(currentHour); |
| updateAmPmControl(); |
| } |
| |
| /** |
| * @return true if this is in 24 hour view else false. |
| */ |
| public boolean is24HourView() { |
| return mIs24HourView; |
| } |
| |
| /** |
| * @return The current minute. |
| */ |
| public Integer getCurrentMinute() { |
| return mMinuteSpinner.getValue(); |
| } |
| |
| /** |
| * Set the current minute (0-59). |
| */ |
| public void setCurrentMinute(Integer currentMinute) { |
| if (currentMinute == getCurrentMinute()) { |
| return; |
| } |
| mMinuteSpinner.setValue(currentMinute); |
| onTimeChanged(); |
| } |
| |
| @Override |
| public int getBaseline() { |
| return mHourSpinner.getBaseline(); |
| } |
| |
| private void updateHourControl() { |
| if (is24HourView()) { |
| mHourSpinner.setMinValue(0); |
| mHourSpinner.setMaxValue(23); |
| mHourSpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); |
| } else { |
| mHourSpinner.setMinValue(1); |
| mHourSpinner.setMaxValue(12); |
| mHourSpinner.setFormatter(null); |
| } |
| } |
| |
| private void updateAmPmControl() { |
| if (is24HourView()) { |
| if (mAmPmSpinner != null) { |
| mAmPmSpinner.setVisibility(View.GONE); |
| } else { |
| mAmPmButton.setVisibility(View.GONE); |
| } |
| } else { |
| int index = mIsAm ? Calendar.AM : Calendar.PM; |
| if (mAmPmSpinner != null) { |
| mAmPmSpinner.setValue(index); |
| mAmPmSpinner.setVisibility(View.VISIBLE); |
| } else { |
| mAmPmButton.setText(mAmPmStrings[index]); |
| mAmPmButton.setVisibility(View.VISIBLE); |
| } |
| } |
| } |
| |
| private void onTimeChanged() { |
| if (mOnTimeChangedListener != null) { |
| mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute()); |
| } |
| } |
| } |