DatePicker.java revision b19799d69cbbe7f7ca104520e9b07312ab7539af
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15package android.support.v17.leanback.widget.picker; 16 17import android.content.Context; 18import android.content.res.TypedArray; 19import android.support.v17.leanback.R; 20import android.text.TextUtils; 21import android.util.AttributeSet; 22import android.util.Log; 23 24import java.text.DateFormat; 25import java.text.ParseException; 26import java.text.SimpleDateFormat; 27import java.util.ArrayList; 28import java.util.Calendar; 29import java.util.Locale; 30import java.util.TimeZone; 31 32/** 33 * {@link DatePicker} is a directly subclass of {@link Picker}. 34 * This class is a widget for selecting a date. The date can be selected by a 35 * year, month, and day Columns. The "minDate" and "maxDate" from which dates to be selected 36 * can be customized. The columns can be customized by attribute "datePickerFormat" or 37 * {@link #setDatePickerFormat(String)}. 38 * 39 * @attr ref R.styleable#lbDatePicker_android_maxDate 40 * @attr ref R.styleable#lbDatePicker_android_minDate 41 * @attr ref R.styleable#lbDatePicker_datePickerFormat 42 */ 43 44public class DatePicker extends Picker { 45 46 static final String LOG_TAG = "DatePicker"; 47 48 private String mDatePickerFormat; 49 PickerColumn mMonthColumn; 50 PickerColumn mDayColumn; 51 PickerColumn mYearColumn; 52 int mColMonthIndex; 53 int mColDayIndex; 54 int mColYearIndex; 55 56 final static String DATE_FORMAT = "MM/dd/yyyy"; 57 final DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT); 58 PickerConstant mConstant; 59 60 Calendar mMinDate; 61 Calendar mMaxDate; 62 Calendar mCurrentDate; 63 Calendar mTempDate; 64 65 public DatePicker(Context context, AttributeSet attrs, int defStyleAttr) { 66 super(context, attrs, defStyleAttr); 67 68 updateCurrentLocale(); 69 70 final TypedArray attributesArray = context.obtainStyledAttributes(attrs, 71 R.styleable.lbDatePicker); 72 String minDate = attributesArray.getString(R.styleable.lbDatePicker_android_minDate); 73 String maxDate = attributesArray.getString(R.styleable.lbDatePicker_android_maxDate); 74 String datePickerFormat = attributesArray 75 .getString(R.styleable.lbDatePicker_datePickerFormat); 76 if (TextUtils.isEmpty(datePickerFormat)) { 77 datePickerFormat = new String( 78 android.text.format.DateFormat.getDateFormatOrder(context)); 79 } 80 setDatePickerFormat(datePickerFormat); 81 82 mTempDate.clear(); 83 if (!TextUtils.isEmpty(minDate)) { 84 if (!parseDate(minDate, mTempDate)) { 85 mTempDate.set(1900, 0, 1); 86 } 87 } else { 88 mTempDate.set(1900, 0, 1); 89 } 90 setMinDate(mTempDate.getTimeInMillis()); 91 92 mTempDate.clear(); 93 if (!TextUtils.isEmpty(maxDate)) { 94 if (!parseDate(maxDate, mTempDate)) { 95 mTempDate.set(2100, 0, 1); 96 } 97 } else { 98 mTempDate.set(2100, 0, 1); 99 } 100 setMaxDate(mTempDate.getTimeInMillis()); 101 102 } 103 104 private boolean parseDate(String date, Calendar outDate) { 105 try { 106 outDate.setTime(mDateFormat.parse(date)); 107 return true; 108 } catch (ParseException e) { 109 Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); 110 return false; 111 } 112 } 113 114 /** 115 * Changes format of showing dates, e.g. 'YMD'. 116 * @param datePickerFormat Format of showing dates. 117 */ 118 public void setDatePickerFormat(String datePickerFormat) { 119 datePickerFormat = datePickerFormat.toUpperCase(); 120 if (TextUtils.equals(mDatePickerFormat, datePickerFormat)) { 121 return; 122 } 123 mDatePickerFormat = datePickerFormat; 124 mYearColumn = mMonthColumn = mDayColumn = null; 125 mColYearIndex = mColDayIndex = mColMonthIndex = -1; 126 ArrayList<PickerColumn> columns = new ArrayList<PickerColumn>(3); 127 for (int i = 0; i < datePickerFormat.length(); i++) { 128 switch (datePickerFormat.charAt(i)) { 129 case 'Y': 130 if (mYearColumn != null) { 131 throw new IllegalArgumentException("datePicker format error"); 132 } 133 columns.add(mYearColumn = new PickerColumn()); 134 mColYearIndex = i; 135 mYearColumn.setValueLabelFormat("%d"); 136 break; 137 case 'M': 138 if (mMonthColumn != null) { 139 throw new IllegalArgumentException("datePicker format error"); 140 } 141 columns.add(mMonthColumn = new PickerColumn()); 142 mMonthColumn.setValueStaticLabels(mConstant.months); 143 mColMonthIndex = i; 144 break; 145 case 'D': 146 if (mDayColumn != null) { 147 throw new IllegalArgumentException("datePicker format error"); 148 } 149 columns.add(mDayColumn = new PickerColumn()); 150 mDayColumn.setValueLabelFormat("%02d"); 151 mColDayIndex = i; 152 break; 153 default: 154 throw new IllegalArgumentException("datePicker format error"); 155 } 156 } 157 setColumns(columns); 158 updateSpinners(false); 159 } 160 161 /** 162 * Get format of showing dates, e.g. 'YMD'. Default value is from 163 * {@link android.text.format.DateFormat#getDateFormatOrder}. 164 * @return Format of showing dates. 165 */ 166 public String getDatePickerFormat() { 167 return mDatePickerFormat; 168 } 169 170 @Override 171 protected String getSeparator() { 172 return mConstant.dateSeparator; 173 } 174 175 176 private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { 177 if (oldCalendar == null) { 178 return Calendar.getInstance(locale); 179 } else { 180 final long currentTimeMillis = oldCalendar.getTimeInMillis(); 181 Calendar newCalendar = Calendar.getInstance(locale); 182 newCalendar.setTimeInMillis(currentTimeMillis); 183 return newCalendar; 184 } 185 } 186 187 private void updateCurrentLocale() { 188 mConstant = new PickerConstant(Locale.getDefault(), getContext().getResources()); 189 mTempDate = getCalendarForLocale(mTempDate, mConstant.locale); 190 mMinDate = getCalendarForLocale(mMinDate, mConstant.locale); 191 mMaxDate = getCalendarForLocale(mMaxDate, mConstant.locale); 192 mCurrentDate = getCalendarForLocale(mCurrentDate, mConstant.locale); 193 194 if (mMonthColumn != null) { 195 mMonthColumn.setValueStaticLabels(mConstant.months); 196 updateAdapter(mColMonthIndex); 197 } 198 } 199 200 @Override 201 public void onColumnValueChange(int column, int newVal) { 202 mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis()); 203 // take care of wrapping of days and months to update greater fields 204 int oldVal = getColumnAt(column).getCurrentValue(); 205 if (column == mColDayIndex) { 206 mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal); 207 } else if (column == mColMonthIndex) { 208 mTempDate.add(Calendar.MONTH, newVal - oldVal); 209 } else if (column == mColYearIndex) { 210 mTempDate.add(Calendar.YEAR, newVal - oldVal); 211 } else { 212 throw new IllegalArgumentException(); 213 } 214 setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH), 215 mTempDate.get(Calendar.DAY_OF_MONTH)); 216 updateSpinners(false); 217 } 218 219 220 /** 221 * Sets the minimal date supported by this {@link DatePicker} in 222 * milliseconds since January 1, 1970 00:00:00 in 223 * {@link TimeZone#getDefault()} time zone. 224 * 225 * @param minDate The minimal supported date. 226 */ 227 public void setMinDate(long minDate) { 228 mTempDate.setTimeInMillis(minDate); 229 if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR) 230 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) { 231 return; 232 } 233 mMinDate.setTimeInMillis(minDate); 234 if (mCurrentDate.before(mMinDate)) { 235 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); 236 } 237 updateSpinners(false); 238 } 239 240 241 /** 242 * Gets the minimal date supported by this {@link DatePicker} in 243 * milliseconds since January 1, 1970 00:00:00 in 244 * {@link TimeZone#getDefault()} time zone. 245 * <p> 246 * Note: The default minimal date is 01/01/1900. 247 * <p> 248 * 249 * @return The minimal supported date. 250 */ 251 public Calendar getMinDate() { 252 final Calendar minDate = Calendar.getInstance(); 253 minDate.setTimeInMillis(mMinDate.getTimeInMillis()); 254 return minDate; 255 } 256 257 /** 258 * Sets the maximal date supported by this {@link DatePicker} in 259 * milliseconds since January 1, 1970 00:00:00 in 260 * {@link TimeZone#getDefault()} time zone. 261 * 262 * @param maxDate The maximal supported date. 263 */ 264 public void setMaxDate(long maxDate) { 265 mTempDate.setTimeInMillis(maxDate); 266 if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR) 267 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) { 268 return; 269 } 270 mMaxDate.setTimeInMillis(maxDate); 271 if (mCurrentDate.after(mMaxDate)) { 272 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); 273 } 274 updateSpinners(false); 275 } 276 277 /** 278 * Gets the maximal date supported by this {@link DatePicker} in 279 * milliseconds since January 1, 1970 00:00:00 in 280 * {@link TimeZone#getDefault()} time zone. 281 * <p> 282 * Note: The default maximal date is 12/31/2100. 283 * <p> 284 * 285 * @return The maximal supported date. 286 */ 287 public Calendar getMaxDate() { 288 final Calendar maxDate = Calendar.getInstance(); 289 maxDate.setTimeInMillis(mMaxDate.getTimeInMillis()); 290 return maxDate; 291 } 292 293 private void setDate(int year, int month, int dayOfMonth) { 294 mCurrentDate.set(year, month, dayOfMonth); 295 if (mCurrentDate.before(mMinDate)) { 296 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); 297 } else if (mCurrentDate.after(mMaxDate)) { 298 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); 299 } 300 } 301 302 /** 303 * Update the current date. 304 * 305 * @param year The year. 306 * @param month The month which is <strong>starting from zero</strong>. 307 * @param dayOfMonth The day of the month. 308 * @param animation True to run animation to scroll the column. 309 */ 310 public void updateDate(int year, int month, int dayOfMonth, boolean animation) { 311 if (!isNewDate(year, month, dayOfMonth)) { 312 return; 313 } 314 setDate(year, month, dayOfMonth); 315 updateSpinners(animation); 316 } 317 318 private boolean isNewDate(int year, int month, int dayOfMonth) { 319 return (mCurrentDate.get(Calendar.YEAR) != year 320 || mCurrentDate.get(Calendar.MONTH) != dayOfMonth 321 || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month); 322 } 323 324 private void updateSpinners(boolean animation) { 325 // set the spinner ranges respecting the min and max dates 326 boolean dayRangeChanged = false; 327 boolean monthRangeChanged = false; 328 if (mCurrentDate.equals(mMinDate)) { 329 if (mDayColumn != null) { 330 dayRangeChanged |= mDayColumn.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); 331 dayRangeChanged |= mDayColumn 332 .setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); 333 } 334 if (mMonthColumn != null) { 335 monthRangeChanged |= mMonthColumn.setMinValue(mCurrentDate.get(Calendar.MONTH)); 336 monthRangeChanged |= 337 mMonthColumn.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH)); 338 } 339 } else if (mCurrentDate.equals(mMaxDate)) { 340 if (mDayColumn != null) { 341 dayRangeChanged |= mDayColumn 342 .setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH)); 343 dayRangeChanged |= mDayColumn.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); 344 } 345 if (mMonthColumn != null) { 346 monthRangeChanged |= 347 mMonthColumn.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH)); 348 monthRangeChanged |= mMonthColumn.setMaxValue(mCurrentDate.get(Calendar.MONTH)); 349 } 350 } else { 351 if (mDayColumn != null) { 352 dayRangeChanged |= mDayColumn 353 .setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH)); 354 dayRangeChanged |= mDayColumn 355 .setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); 356 } 357 if (mMonthColumn != null) { 358 monthRangeChanged |= 359 mMonthColumn.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH)); 360 monthRangeChanged |= 361 mMonthColumn.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH)); 362 } 363 } 364 365 // year spinner range does not change based on the current date 366 boolean yearRangeChanged = false; 367 if (mYearColumn != null) { 368 yearRangeChanged |= mYearColumn.setMinValue(mMinDate.get(Calendar.YEAR)); 369 yearRangeChanged |= mYearColumn.setMaxValue(mMaxDate.get(Calendar.YEAR)); 370 } 371 372 if (dayRangeChanged) { 373 updateAdapter(mColDayIndex); 374 } 375 if (monthRangeChanged) { 376 updateAdapter(mColMonthIndex); 377 } 378 if (yearRangeChanged) { 379 updateAdapter(mColYearIndex); 380 } 381 // set the spinner values 382 if (mYearColumn != null) { 383 updateValue(mColYearIndex, mCurrentDate.get(Calendar.YEAR), animation); 384 } 385 if (mMonthColumn != null) { 386 updateValue(mColMonthIndex, mCurrentDate.get(Calendar.MONTH), animation); 387 } 388 if (mDayColumn != null) { 389 updateValue(mColDayIndex, mCurrentDate.get(Calendar.DAY_OF_MONTH), animation); 390 } 391 392 } 393 394}