The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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.content.res.TypedArray; |
| 22 | import android.os.Parcel; |
| 23 | import android.os.Parcelable; |
| 24 | import android.text.format.DateFormat; |
| 25 | import android.util.AttributeSet; |
| 26 | import android.util.SparseArray; |
| 27 | import android.view.LayoutInflater; |
Paul Westbrook | 68f2f54 | 2010-01-13 12:13:57 -0800 | [diff] [blame] | 28 | import android.widget.NumberPicker; |
| 29 | import android.widget.NumberPicker.OnChangedListener; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 30 | |
| 31 | import com.android.internal.R; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 32 | |
| 33 | import java.text.DateFormatSymbols; |
Eric Fischer | 03a8017 | 2009-07-23 18:32:42 -0700 | [diff] [blame] | 34 | import java.text.SimpleDateFormat; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 35 | import java.util.Calendar; |
| 36 | |
| 37 | /** |
| 38 | * A view for selecting a month / year / day based on a calendar like layout. |
| 39 | * |
| 40 | * For a dialog using this view, see {@link android.app.DatePickerDialog}. |
| 41 | */ |
| 42 | @Widget |
| 43 | public class DatePicker extends FrameLayout { |
| 44 | |
| 45 | private static final int DEFAULT_START_YEAR = 1900; |
| 46 | private static final int DEFAULT_END_YEAR = 2100; |
| 47 | |
| 48 | /* UI Components */ |
| 49 | private final NumberPicker mDayPicker; |
| 50 | private final NumberPicker mMonthPicker; |
| 51 | private final NumberPicker mYearPicker; |
| 52 | |
| 53 | /** |
| 54 | * How we notify users the date has changed. |
| 55 | */ |
| 56 | private OnDateChangedListener mOnDateChangedListener; |
| 57 | |
| 58 | private int mDay; |
| 59 | private int mMonth; |
| 60 | private int mYear; |
| 61 | |
| 62 | /** |
| 63 | * The callback used to indicate the user changes the date. |
| 64 | */ |
| 65 | public interface OnDateChangedListener { |
| 66 | |
| 67 | /** |
| 68 | * @param view The view associated with this listener. |
| 69 | * @param year The year that was set. |
| 70 | * @param monthOfYear The month that was set (0-11) for compatibility |
| 71 | * with {@link java.util.Calendar}. |
| 72 | * @param dayOfMonth The day of the month that was set. |
| 73 | */ |
| 74 | void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth); |
| 75 | } |
| 76 | |
| 77 | public DatePicker(Context context) { |
| 78 | this(context, null); |
| 79 | } |
| 80 | |
| 81 | public DatePicker(Context context, AttributeSet attrs) { |
| 82 | this(context, attrs, 0); |
| 83 | } |
| 84 | |
| 85 | public DatePicker(Context context, AttributeSet attrs, int defStyle) { |
| 86 | super(context, attrs, defStyle); |
| 87 | |
| 88 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
| 89 | inflater.inflate(R.layout.date_picker, this, true); |
| 90 | |
| 91 | mDayPicker = (NumberPicker) findViewById(R.id.day); |
| 92 | mDayPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); |
| 93 | mDayPicker.setSpeed(100); |
| 94 | mDayPicker.setOnChangeListener(new OnChangedListener() { |
| 95 | public void onChanged(NumberPicker picker, int oldVal, int newVal) { |
| 96 | mDay = newVal; |
Kenneth Andersson | e3491b6 | 2010-03-05 09:16:24 +0100 | [diff] [blame] | 97 | notifyDateChanged(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 98 | } |
| 99 | }); |
| 100 | mMonthPicker = (NumberPicker) findViewById(R.id.month); |
| 101 | mMonthPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); |
| 102 | DateFormatSymbols dfs = new DateFormatSymbols(); |
Eric Fischer | 03a8017 | 2009-07-23 18:32:42 -0700 | [diff] [blame] | 103 | String[] months = dfs.getShortMonths(); |
Eric Fischer | 5fe1e62 | 2010-02-09 15:58:50 -0800 | [diff] [blame] | 104 | |
| 105 | /* |
| 106 | * If the user is in a locale where the month names are numeric, |
| 107 | * use just the number instead of the "month" character for |
| 108 | * consistency with the other fields. |
| 109 | */ |
| 110 | if (months[0].startsWith("1")) { |
| 111 | for (int i = 0; i < months.length; i++) { |
| 112 | months[i] = String.valueOf(i + 1); |
| 113 | } |
| 114 | } |
| 115 | |
Eric Fischer | 03a8017 | 2009-07-23 18:32:42 -0700 | [diff] [blame] | 116 | mMonthPicker.setRange(1, 12, months); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 117 | mMonthPicker.setSpeed(200); |
| 118 | mMonthPicker.setOnChangeListener(new OnChangedListener() { |
| 119 | public void onChanged(NumberPicker picker, int oldVal, int newVal) { |
| 120 | |
| 121 | /* We display the month 1-12 but store it 0-11 so always |
| 122 | * subtract by one to ensure our internal state is always 0-11 |
| 123 | */ |
| 124 | mMonth = newVal - 1; |
Suchi Amalapurapu | 2bf761c | 2009-07-13 16:24:58 -0700 | [diff] [blame] | 125 | // Adjust max day of the month |
| 126 | adjustMaxDay(); |
Kenneth Andersson | e3491b6 | 2010-03-05 09:16:24 +0100 | [diff] [blame] | 127 | notifyDateChanged(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 128 | updateDaySpinner(); |
| 129 | } |
| 130 | }); |
| 131 | mYearPicker = (NumberPicker) findViewById(R.id.year); |
| 132 | mYearPicker.setSpeed(100); |
| 133 | mYearPicker.setOnChangeListener(new OnChangedListener() { |
| 134 | public void onChanged(NumberPicker picker, int oldVal, int newVal) { |
| 135 | mYear = newVal; |
Suchi Amalapurapu | 2bf761c | 2009-07-13 16:24:58 -0700 | [diff] [blame] | 136 | // Adjust max day for leap years if needed |
| 137 | adjustMaxDay(); |
Kenneth Andersson | e3491b6 | 2010-03-05 09:16:24 +0100 | [diff] [blame] | 138 | notifyDateChanged(); |
Suchi Amalapurapu | 2bf761c | 2009-07-13 16:24:58 -0700 | [diff] [blame] | 139 | updateDaySpinner(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 140 | } |
| 141 | }); |
| 142 | |
| 143 | // attributes |
| 144 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker); |
| 145 | |
| 146 | int mStartYear = a.getInt(R.styleable.DatePicker_startYear, DEFAULT_START_YEAR); |
| 147 | int mEndYear = a.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR); |
| 148 | mYearPicker.setRange(mStartYear, mEndYear); |
| 149 | |
| 150 | a.recycle(); |
| 151 | |
| 152 | // initialize to current date |
| 153 | Calendar cal = Calendar.getInstance(); |
| 154 | init(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), null); |
| 155 | |
| 156 | // re-order the number pickers to match the current date format |
Eric Fischer | 03a8017 | 2009-07-23 18:32:42 -0700 | [diff] [blame] | 157 | reorderPickers(months); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 158 | |
| 159 | if (!isEnabled()) { |
| 160 | setEnabled(false); |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | @Override |
| 165 | public void setEnabled(boolean enabled) { |
| 166 | super.setEnabled(enabled); |
| 167 | mDayPicker.setEnabled(enabled); |
| 168 | mMonthPicker.setEnabled(enabled); |
| 169 | mYearPicker.setEnabled(enabled); |
| 170 | } |
| 171 | |
Eric Fischer | 03a8017 | 2009-07-23 18:32:42 -0700 | [diff] [blame] | 172 | private void reorderPickers(String[] months) { |
| 173 | java.text.DateFormat format; |
| 174 | String order; |
| 175 | |
| 176 | /* |
| 177 | * If the user is in a locale where the medium date format is |
| 178 | * still numeric (Japanese and Czech, for example), respect |
| 179 | * the date format order setting. Otherwise, use the order |
| 180 | * that the locale says is appropriate for a spelled-out date. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 181 | */ |
Eric Fischer | 03a8017 | 2009-07-23 18:32:42 -0700 | [diff] [blame] | 182 | |
| 183 | if (months[0].startsWith("1")) { |
| 184 | format = DateFormat.getDateFormat(getContext()); |
| 185 | } else { |
| 186 | format = DateFormat.getMediumDateFormat(getContext()); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 187 | } |
Eric Fischer | 03a8017 | 2009-07-23 18:32:42 -0700 | [diff] [blame] | 188 | |
| 189 | if (format instanceof SimpleDateFormat) { |
| 190 | order = ((SimpleDateFormat) format).toPattern(); |
| 191 | } else { |
| 192 | // Shouldn't happen, but just in case. |
| 193 | order = new String(DateFormat.getDateFormatOrder(getContext())); |
| 194 | } |
| 195 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 196 | /* Remove the 3 pickers from their parent and then add them back in the |
| 197 | * required order. |
| 198 | */ |
| 199 | LinearLayout parent = (LinearLayout) findViewById(R.id.parent); |
| 200 | parent.removeAllViews(); |
Eric Fischer | 03a8017 | 2009-07-23 18:32:42 -0700 | [diff] [blame] | 201 | |
| 202 | boolean quoted = false; |
| 203 | boolean didDay = false, didMonth = false, didYear = false; |
| 204 | |
| 205 | for (int i = 0; i < order.length(); i++) { |
| 206 | char c = order.charAt(i); |
| 207 | |
| 208 | if (c == '\'') { |
| 209 | quoted = !quoted; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 210 | } |
Eric Fischer | 03a8017 | 2009-07-23 18:32:42 -0700 | [diff] [blame] | 211 | |
| 212 | if (!quoted) { |
| 213 | if (c == DateFormat.DATE && !didDay) { |
| 214 | parent.addView(mDayPicker); |
| 215 | didDay = true; |
| 216 | } else if ((c == DateFormat.MONTH || c == 'L') && !didMonth) { |
| 217 | parent.addView(mMonthPicker); |
| 218 | didMonth = true; |
| 219 | } else if (c == DateFormat.YEAR && !didYear) { |
| 220 | parent.addView (mYearPicker); |
| 221 | didYear = true; |
| 222 | } |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | // Shouldn't happen, but just in case. |
| 227 | if (!didMonth) { |
| 228 | parent.addView(mMonthPicker); |
| 229 | } |
| 230 | if (!didDay) { |
| 231 | parent.addView(mDayPicker); |
| 232 | } |
| 233 | if (!didYear) { |
| 234 | parent.addView(mYearPicker); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 235 | } |
| 236 | } |
| 237 | |
| 238 | public void updateDate(int year, int monthOfYear, int dayOfMonth) { |
Kenneth Andersson | e3491b6 | 2010-03-05 09:16:24 +0100 | [diff] [blame] | 239 | if (mYear != year || mMonth != monthOfYear || mDay != dayOfMonth) { |
| 240 | mYear = year; |
| 241 | mMonth = monthOfYear; |
| 242 | mDay = dayOfMonth; |
| 243 | updateSpinners(); |
| 244 | reorderPickers(new DateFormatSymbols().getShortMonths()); |
| 245 | notifyDateChanged(); |
| 246 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 247 | } |
| 248 | |
| 249 | private static class SavedState extends BaseSavedState { |
| 250 | |
| 251 | private final int mYear; |
| 252 | private final int mMonth; |
| 253 | private final int mDay; |
| 254 | |
| 255 | /** |
| 256 | * Constructor called from {@link DatePicker#onSaveInstanceState()} |
| 257 | */ |
| 258 | private SavedState(Parcelable superState, int year, int month, int day) { |
| 259 | super(superState); |
| 260 | mYear = year; |
| 261 | mMonth = month; |
| 262 | mDay = day; |
| 263 | } |
| 264 | |
| 265 | /** |
| 266 | * Constructor called from {@link #CREATOR} |
| 267 | */ |
| 268 | private SavedState(Parcel in) { |
| 269 | super(in); |
| 270 | mYear = in.readInt(); |
| 271 | mMonth = in.readInt(); |
| 272 | mDay = in.readInt(); |
| 273 | } |
| 274 | |
| 275 | public int getYear() { |
| 276 | return mYear; |
| 277 | } |
| 278 | |
| 279 | public int getMonth() { |
| 280 | return mMonth; |
| 281 | } |
| 282 | |
| 283 | public int getDay() { |
| 284 | return mDay; |
| 285 | } |
| 286 | |
| 287 | @Override |
| 288 | public void writeToParcel(Parcel dest, int flags) { |
| 289 | super.writeToParcel(dest, flags); |
| 290 | dest.writeInt(mYear); |
| 291 | dest.writeInt(mMonth); |
| 292 | dest.writeInt(mDay); |
| 293 | } |
| 294 | |
| 295 | public static final Parcelable.Creator<SavedState> CREATOR = |
| 296 | new Creator<SavedState>() { |
| 297 | |
| 298 | public SavedState createFromParcel(Parcel in) { |
| 299 | return new SavedState(in); |
| 300 | } |
| 301 | |
| 302 | public SavedState[] newArray(int size) { |
| 303 | return new SavedState[size]; |
| 304 | } |
| 305 | }; |
| 306 | } |
| 307 | |
| 308 | |
| 309 | /** |
| 310 | * Override so we are in complete control of save / restore for this widget. |
| 311 | */ |
| 312 | @Override |
| 313 | protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { |
| 314 | dispatchThawSelfOnly(container); |
| 315 | } |
| 316 | |
| 317 | @Override |
| 318 | protected Parcelable onSaveInstanceState() { |
| 319 | Parcelable superState = super.onSaveInstanceState(); |
| 320 | |
| 321 | return new SavedState(superState, mYear, mMonth, mDay); |
| 322 | } |
| 323 | |
| 324 | @Override |
| 325 | protected void onRestoreInstanceState(Parcelable state) { |
| 326 | SavedState ss = (SavedState) state; |
| 327 | super.onRestoreInstanceState(ss.getSuperState()); |
| 328 | mYear = ss.getYear(); |
| 329 | mMonth = ss.getMonth(); |
| 330 | mDay = ss.getDay(); |
Nicholas Killewald | 0ba2d47 | 2010-05-03 02:17:08 -0400 | [diff] [blame] | 331 | updateSpinners(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 332 | } |
| 333 | |
| 334 | /** |
| 335 | * Initialize the state. |
| 336 | * @param year The initial year. |
| 337 | * @param monthOfYear The initial month. |
| 338 | * @param dayOfMonth The initial day of the month. |
| 339 | * @param onDateChangedListener How user is notified date is changed by user, can be null. |
| 340 | */ |
| 341 | public void init(int year, int monthOfYear, int dayOfMonth, |
| 342 | OnDateChangedListener onDateChangedListener) { |
| 343 | mYear = year; |
| 344 | mMonth = monthOfYear; |
| 345 | mDay = dayOfMonth; |
| 346 | mOnDateChangedListener = onDateChangedListener; |
| 347 | updateSpinners(); |
| 348 | } |
| 349 | |
| 350 | private void updateSpinners() { |
| 351 | updateDaySpinner(); |
| 352 | mYearPicker.setCurrent(mYear); |
| 353 | |
| 354 | /* The month display uses 1-12 but our internal state stores it |
| 355 | * 0-11 so add one when setting the display. |
| 356 | */ |
| 357 | mMonthPicker.setCurrent(mMonth + 1); |
| 358 | } |
| 359 | |
| 360 | private void updateDaySpinner() { |
| 361 | Calendar cal = Calendar.getInstance(); |
| 362 | cal.set(mYear, mMonth, mDay); |
| 363 | int max = cal.getActualMaximum(Calendar.DAY_OF_MONTH); |
| 364 | mDayPicker.setRange(1, max); |
| 365 | mDayPicker.setCurrent(mDay); |
| 366 | } |
| 367 | |
| 368 | public int getYear() { |
| 369 | return mYear; |
| 370 | } |
| 371 | |
| 372 | public int getMonth() { |
| 373 | return mMonth; |
| 374 | } |
| 375 | |
| 376 | public int getDayOfMonth() { |
| 377 | return mDay; |
| 378 | } |
Suchi Amalapurapu | 2bf761c | 2009-07-13 16:24:58 -0700 | [diff] [blame] | 379 | |
| 380 | private void adjustMaxDay(){ |
| 381 | Calendar cal = Calendar.getInstance(); |
| 382 | cal.set(Calendar.YEAR, mYear); |
| 383 | cal.set(Calendar.MONTH, mMonth); |
| 384 | int max = cal.getActualMaximum(Calendar.DAY_OF_MONTH); |
| 385 | if (mDay > max) { |
| 386 | mDay = max; |
| 387 | } |
| 388 | } |
Kenneth Andersson | e3491b6 | 2010-03-05 09:16:24 +0100 | [diff] [blame] | 389 | |
| 390 | private void notifyDateChanged() { |
| 391 | if (mOnDateChangedListener != null) { |
| 392 | mOnDateChangedListener.onDateChanged(DatePicker.this, mYear, mMonth, mDay); |
| 393 | } |
| 394 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 395 | } |