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 17 package android.widget; 18 19 import android.annotation.Widget; 20 import android.content.Context; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 import android.util.AttributeSet; 24 import android.view.LayoutInflater; 25 import android.view.View; 26 import android.widget.NumberPicker; 27 28 import com.android.internal.R; 29 30 import java.text.DateFormatSymbols; 31 import 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 50 public 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