TimePicker.java revision cedc446684e94c9971c38c3206f1f224314bda2a
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.content.res.TypedArray; 24import android.os.Parcel; 25import android.os.Parcelable; 26import android.util.AttributeSet; 27import android.view.LayoutInflater; 28import android.view.View; 29import android.widget.NumberPicker.OnValueChangeListener; 30 31import java.text.DateFormatSymbols; 32import java.util.Calendar; 33 34/** 35 * A view for selecting the time of day, in either 24 hour or AM/PM mode. The 36 * hour, each minute digit, and AM/PM (if applicable) can be conrolled by 37 * vertical spinners. The hour can be entered by keyboard input. Entering in two 38 * digit hours can be accomplished by hitting two digits within a timeout of 39 * about a second (e.g. '1' then '2' to select 12). The minutes can be entered 40 * by entering single digits. Under AM/PM mode, the user can hit 'a', 'A", 'p' 41 * or 'P' to pick. For a dialog using this view, see 42 * {@link android.app.TimePickerDialog}. 43 *<p> 44 * See the <a href="{@docRoot} 45 * resources/tutorials/views/hello-timepicker.html">Time Picker tutorial</a>. 46 * </p> 47 */ 48@Widget 49public class TimePicker extends FrameLayout { 50 51 private static final boolean DEFAULT_ENABLED_STATE = true; 52 53 private static final int HOURS_IN_HALF_DAY = 12; 54 55 /** 56 * A no-op callback used in the constructor to avoid null checks later in 57 * the code. 58 */ 59 private static final OnTimeChangedListener NO_OP_CHANGE_LISTENER = new OnTimeChangedListener() { 60 public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { 61 } 62 }; 63 64 // state 65 private boolean mIs24HourView; 66 67 private boolean mIsAm; 68 69 // ui components 70 private final NumberPicker mHourSpinner; 71 72 private final NumberPicker mMinuteSpinner; 73 74 private final NumberPicker mAmPmSpinner; 75 76 private final TextView mDivider; 77 78 // Note that the legacy implementation of the TimePicker is 79 // using a button for toggling between AM/PM while the new 80 // version uses a NumberPicker spinner. Therefore the code 81 // accommodates these two cases to be backwards compatible. 82 private final Button mAmPmButton; 83 84 private final String[] mAmPmStrings; 85 86 private boolean mIsEnabled = DEFAULT_ENABLED_STATE; 87 88 // callbacks 89 private OnTimeChangedListener mOnTimeChangedListener; 90 91 /** 92 * The callback interface used to indicate the time has been adjusted. 93 */ 94 public interface OnTimeChangedListener { 95 96 /** 97 * @param view The view associated with this listener. 98 * @param hourOfDay The current hour. 99 * @param minute The current minute. 100 */ 101 void onTimeChanged(TimePicker view, int hourOfDay, int minute); 102 } 103 104 public TimePicker(Context context) { 105 this(context, null); 106 } 107 108 public TimePicker(Context context, AttributeSet attrs) { 109 this(context, attrs, R.attr.timePickerStyle); 110 } 111 112 public TimePicker(Context context, AttributeSet attrs, int defStyle) { 113 super(context, attrs, defStyle); 114 115 // process style attributes 116 TypedArray attributesArray = context.obtainStyledAttributes( 117 attrs, R.styleable.TimePicker, defStyle, 0); 118 int layoutResourceId = attributesArray.getResourceId( 119 R.styleable.TimePicker_layout, R.layout.time_picker); 120 attributesArray.recycle(); 121 122 LayoutInflater inflater = (LayoutInflater) context.getSystemService( 123 Context.LAYOUT_INFLATER_SERVICE); 124 inflater.inflate(layoutResourceId, this, true); 125 126 // hour 127 mHourSpinner = (NumberPicker) findViewById(R.id.hour); 128 mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { 129 public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { 130 if (!is24HourView()) { 131 int minValue = mHourSpinner.getMinValue(); 132 int maxValue = mHourSpinner.getMaxValue(); 133 // toggle AM/PM if the spinner has wrapped and not in 24 134 // format 135 if ((oldVal == maxValue && newVal == minValue) 136 || (oldVal == minValue && newVal == maxValue)) { 137 mIsAm = !mIsAm; 138 updateAmPmControl(); 139 } 140 } 141 onTimeChanged(); 142 } 143 }); 144 145 // divider (only for the new widget style) 146 mDivider = (TextView) findViewById(R.id.divider); 147 if (mDivider != null) { 148 mDivider.setText(R.string.time_picker_separator); 149 } 150 151 // minute 152 mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); 153 mMinuteSpinner.setMinValue(0); 154 mMinuteSpinner.setMaxValue(59); 155 mMinuteSpinner.setOnLongPressUpdateInterval(100); 156 mMinuteSpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); 157 mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { 158 public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { 159 int minValue = mMinuteSpinner.getMinValue(); 160 int maxValue = mMinuteSpinner.getMaxValue(); 161 if (oldVal == maxValue && newVal == minValue) { 162 int currentHour = mHourSpinner.getValue(); 163 // toggle AM/PM if the spinner is about to wrap 164 if (!is24HourView() && currentHour == mHourSpinner.getMaxValue()) { 165 mIsAm = !mIsAm; 166 updateAmPmControl(); 167 } 168 mHourSpinner.setValue(currentHour + 1); 169 } else if (oldVal == minValue && newVal == maxValue) { 170 int currentHour = mHourSpinner.getValue(); 171 // toggle AM/PM if the spinner is about to wrap 172 if (!is24HourView() && currentHour == mHourSpinner.getMinValue()) { 173 mIsAm = !mIsAm; 174 updateAmPmControl(); 175 } 176 mHourSpinner.setValue(currentHour - 1); 177 } 178 onTimeChanged(); 179 } 180 }); 181 182 /* Get the localized am/pm strings and use them in the spinner */ 183 mAmPmStrings = new DateFormatSymbols().getAmPmStrings(); 184 185 // am/pm 186 View amPmView = findViewById(R.id.amPm); 187 if (amPmView instanceof Button) { 188 mAmPmSpinner = null; 189 mAmPmButton = (Button) amPmView; 190 mAmPmButton.setOnClickListener(new OnClickListener() { 191 public void onClick(View button) { 192 button.requestFocus(); 193 mIsAm = !mIsAm; 194 updateAmPmControl(); 195 } 196 }); 197 } else { 198 mAmPmButton = null; 199 mAmPmSpinner = (NumberPicker) amPmView; 200 mAmPmSpinner.setMinValue(0); 201 mAmPmSpinner.setMaxValue(1); 202 mAmPmSpinner.setDisplayedValues(mAmPmStrings); 203 mAmPmSpinner.setOnValueChangedListener(new OnValueChangeListener() { 204 public void onValueChange(NumberPicker picker, int oldVal, int newVal) { 205 picker.requestFocus(); 206 mIsAm = !mIsAm; 207 updateAmPmControl(); 208 } 209 }); 210 } 211 212 // update controls to initial state 213 updateHourControl(); 214 updateAmPmControl(); 215 216 // initialize to current time 217 Calendar calendar = Calendar.getInstance(); 218 setOnTimeChangedListener(NO_OP_CHANGE_LISTENER); 219 220 // set to current time 221 setCurrentHour(calendar.get(Calendar.HOUR_OF_DAY)); 222 setCurrentMinute(calendar.get(Calendar.MINUTE)); 223 224 if (!isEnabled()) { 225 setEnabled(false); 226 } 227 } 228 229 @Override 230 public void setEnabled(boolean enabled) { 231 if (mIsEnabled == enabled) { 232 return; 233 } 234 super.setEnabled(enabled); 235 mMinuteSpinner.setEnabled(enabled); 236 if (mDivider != null) { 237 mDivider.setEnabled(enabled); 238 } 239 mHourSpinner.setEnabled(enabled); 240 if (mAmPmSpinner != null) { 241 mAmPmSpinner.setEnabled(enabled); 242 } else { 243 mAmPmButton.setEnabled(enabled); 244 } 245 mIsEnabled = enabled; 246 } 247 248 @Override 249 public boolean isEnabled() { 250 return mIsEnabled; 251 } 252 253 /** 254 * Used to save / restore state of time picker 255 */ 256 private static class SavedState extends BaseSavedState { 257 258 private final int mHour; 259 260 private final int mMinute; 261 262 private SavedState(Parcelable superState, int hour, int minute) { 263 super(superState); 264 mHour = hour; 265 mMinute = minute; 266 } 267 268 private SavedState(Parcel in) { 269 super(in); 270 mHour = in.readInt(); 271 mMinute = in.readInt(); 272 } 273 274 public int getHour() { 275 return mHour; 276 } 277 278 public int getMinute() { 279 return mMinute; 280 } 281 282 @Override 283 public void writeToParcel(Parcel dest, int flags) { 284 super.writeToParcel(dest, flags); 285 dest.writeInt(mHour); 286 dest.writeInt(mMinute); 287 } 288 289 @SuppressWarnings("unused") 290 public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() { 291 public SavedState createFromParcel(Parcel in) { 292 return new SavedState(in); 293 } 294 295 public SavedState[] newArray(int size) { 296 return new SavedState[size]; 297 } 298 }; 299 } 300 301 @Override 302 protected Parcelable onSaveInstanceState() { 303 Parcelable superState = super.onSaveInstanceState(); 304 return new SavedState(superState, getCurrentHour(), getCurrentMinute()); 305 } 306 307 @Override 308 protected void onRestoreInstanceState(Parcelable state) { 309 SavedState ss = (SavedState) state; 310 super.onRestoreInstanceState(ss.getSuperState()); 311 setCurrentHour(ss.getHour()); 312 setCurrentMinute(ss.getMinute()); 313 } 314 315 /** 316 * Set the callback that indicates the time has been adjusted by the user. 317 * 318 * @param onTimeChangedListener the callback, should not be null. 319 */ 320 public void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener) { 321 mOnTimeChangedListener = onTimeChangedListener; 322 } 323 324 /** 325 * @return The current hour in the range (0-23). 326 */ 327 public Integer getCurrentHour() { 328 int currentHour = mHourSpinner.getValue(); 329 if (is24HourView() || mIsAm) { 330 return currentHour; 331 } else { 332 return (currentHour == HOURS_IN_HALF_DAY) ? 0 : currentHour + HOURS_IN_HALF_DAY; 333 } 334 } 335 336 /** 337 * Set the current hour. 338 */ 339 public void setCurrentHour(Integer currentHour) { 340 // why was Integer used in the first place? 341 if (currentHour == null || currentHour == getCurrentHour()) { 342 return; 343 } 344 if (!is24HourView()) { 345 // convert [0,23] ordinal to wall clock display 346 if (currentHour > HOURS_IN_HALF_DAY) { 347 currentHour -= HOURS_IN_HALF_DAY; 348 mIsAm = false; 349 } else { 350 if (currentHour == 0) { 351 currentHour = HOURS_IN_HALF_DAY; 352 } 353 mIsAm = true; 354 } 355 updateAmPmControl(); 356 } 357 mHourSpinner.setValue(currentHour); 358 onTimeChanged(); 359 } 360 361 /** 362 * Set whether in 24 hour or AM/PM mode. 363 * 364 * @param is24HourView True = 24 hour mode. False = AM/PM. 365 */ 366 public void setIs24HourView(Boolean is24HourView) { 367 if (mIs24HourView == is24HourView) { 368 return; 369 } 370 mIs24HourView = is24HourView; 371 // cache the current hour since spinner range changes 372 int currentHour = getCurrentHour(); 373 updateHourControl(); 374 // set value after spinner range is updated 375 setCurrentHour(currentHour); 376 updateAmPmControl(); 377 } 378 379 /** 380 * @return true if this is in 24 hour view else false. 381 */ 382 public boolean is24HourView() { 383 return mIs24HourView; 384 } 385 386 /** 387 * @return The current minute. 388 */ 389 public Integer getCurrentMinute() { 390 return mMinuteSpinner.getValue(); 391 } 392 393 /** 394 * Set the current minute (0-59). 395 */ 396 public void setCurrentMinute(Integer currentMinute) { 397 if (currentMinute == getCurrentMinute()) { 398 return; 399 } 400 mMinuteSpinner.setValue(currentMinute); 401 onTimeChanged(); 402 } 403 404 @Override 405 public int getBaseline() { 406 return mHourSpinner.getBaseline(); 407 } 408 409 private void updateHourControl() { 410 if (is24HourView()) { 411 mHourSpinner.setMinValue(0); 412 mHourSpinner.setMaxValue(23); 413 mHourSpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); 414 } else { 415 mHourSpinner.setMinValue(1); 416 mHourSpinner.setMaxValue(12); 417 mHourSpinner.setFormatter(null); 418 } 419 } 420 421 private void updateAmPmControl() { 422 if (is24HourView()) { 423 if (mAmPmSpinner != null) { 424 mAmPmSpinner.setVisibility(View.GONE); 425 } else { 426 mAmPmButton.setVisibility(View.GONE); 427 } 428 } else { 429 int index = mIsAm ? Calendar.AM : Calendar.PM; 430 if (mAmPmSpinner != null) { 431 mAmPmSpinner.setValue(index); 432 mAmPmSpinner.setVisibility(View.VISIBLE); 433 } else { 434 mAmPmButton.setText(mAmPmStrings[index]); 435 mAmPmButton.setVisibility(View.VISIBLE); 436 } 437 } 438 } 439 440 private void onTimeChanged() { 441 if (mOnTimeChangedListener != null) { 442 mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute()); 443 } 444 } 445} 446