DatePickerDialog.java revision 2e00aa34c051111529290cf23c6ba940c2c0c142
1/* 2 * Copyright (C) 2013 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 com.android.datetimepicker.date; 18 19import android.animation.ObjectAnimator; 20import android.app.Activity; 21import android.app.DialogFragment; 22import android.content.Context; 23import android.os.Bundle; 24import android.os.SystemClock; 25import android.os.Vibrator; 26import android.util.Log; 27import android.view.LayoutInflater; 28import android.view.View; 29import android.view.View.OnClickListener; 30import android.view.ViewGroup; 31import android.view.Window; 32import android.view.WindowManager; 33import android.view.animation.AlphaAnimation; 34import android.view.animation.Animation; 35import android.widget.Button; 36import android.widget.LinearLayout; 37import android.widget.TextView; 38import android.widget.ViewAnimator; 39 40import com.android.datetimepicker.R; 41import com.android.datetimepicker.Utils; 42import com.android.datetimepicker.date.SimpleMonthAdapter.CalendarDay; 43 44import java.text.SimpleDateFormat; 45import java.util.Calendar; 46import java.util.HashSet; 47import java.util.Iterator; 48import java.util.Locale; 49 50/** 51 * Dialog allowing users to select a date. 52 */ 53public class DatePickerDialog extends DialogFragment implements 54 OnClickListener, DatePickerController { 55 56 private static final String TAG = "DatePickerDialog"; 57 58 private static final int UNINITIALIZED = -1; 59 private static final int MONTH_AND_DAY_VIEW = 0; 60 private static final int YEAR_VIEW = 1; 61 62 private static final String KEY_SELECTED_YEAR = "year"; 63 private static final String KEY_SELECTED_MONTH = "month"; 64 private static final String KEY_SELECTED_DAY = "day"; 65 private static final String KEY_LIST_POSITION = "list_position"; 66 private static final String KEY_WEEK_START = "week_start"; 67 private static final String KEY_YEAR_START = "year_start"; 68 private static final String KEY_YEAR_END = "year_end"; 69 private static final String KEY_CURRENT_VIEW = "current_view"; 70 71 private static final int DEFAULT_START_YEAR = 1900; 72 private static final int DEFAULT_END_YEAR = 2100; 73 74 private static final int ANIMATION_DURATION = 300; 75 private static final int ANIMATION_DELAY = 500; 76 77 private static SimpleDateFormat YEAR_FORMAT = new SimpleDateFormat("yyyy", Locale.getDefault()); 78 private static SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("dd", Locale.getDefault()); 79 80 private final Calendar mCalendar = Calendar.getInstance(); 81 private OnDateSetListener mCallBack; 82 private HashSet<OnDateChangedListener> mListeners = new HashSet<OnDateChangedListener>(); 83 84 private ViewAnimator mAnimator; 85 86 private TextView mDayOfWeekView; 87 private LinearLayout mMonthAndDayView; 88 private TextView mSelectedMonthTextView; 89 private TextView mSelectedDayTextView; 90 private TextView mYearView; 91 private DayPickerView mDayPickerView; 92 private YearPickerView mYearPickerView; 93 private Button mDoneButton; 94 95 private int mCurrentView = UNINITIALIZED; 96 97 private int mWeekStart = mCalendar.getFirstDayOfWeek(); 98 private int mMinYear = DEFAULT_START_YEAR; 99 private int mMaxYear = DEFAULT_END_YEAR; 100 101 private Vibrator mVibrator; 102 private long mLastVibrate; 103 104 private boolean mDelayAnimation = true; 105 106 /** 107 * The callback used to indicate the user is done filling in the date. 108 */ 109 public interface OnDateSetListener { 110 111 /** 112 * @param view The view associated with this listener. 113 * @param year The year that was set. 114 * @param monthOfYear The month that was set (0-11) for compatibility 115 * with {@link java.util.Calendar}. 116 * @param dayOfMonth The day of the month that was set. 117 */ 118 void onDateSet(DatePickerDialog dialog, int year, int monthOfYear, int dayOfMonth); 119 } 120 121 /** 122 * The callback used to notify other date picker components of a change in selected date. 123 */ 124 interface OnDateChangedListener { 125 126 public void onDateChanged(); 127 } 128 129 130 public DatePickerDialog() { 131 // Empty constructor required for dialog fragment. 132 } 133 134 /** 135 * @param callBack How the parent is notified that the date is set. 136 * @param year The initial year of the dialog. 137 * @param monthOfYear The initial month of the dialog. 138 * @param dayOfMonth The initial day of the dialog. 139 */ 140 public static DatePickerDialog newInstance(OnDateSetListener callBack, int year, 141 int monthOfYear, 142 int dayOfMonth) { 143 DatePickerDialog ret = new DatePickerDialog(); 144 ret.initialize(callBack, year, monthOfYear, dayOfMonth); 145 return ret; 146 } 147 148 public void initialize(OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) { 149 mCallBack = callBack; 150 mCalendar.set(Calendar.YEAR, year); 151 mCalendar.set(Calendar.MONTH, monthOfYear); 152 mCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth); 153 } 154 155 @Override 156 public void onCreate(Bundle savedInstanceState) { 157 super.onCreate(savedInstanceState); 158 final Activity activity = getActivity(); 159 activity.getWindow().setSoftInputMode( 160 WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); 161 mVibrator = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE); 162 if (savedInstanceState != null) { 163 mCalendar.set(Calendar.YEAR, savedInstanceState.getInt(KEY_SELECTED_YEAR)); 164 mCalendar.set(Calendar.MONTH, savedInstanceState.getInt(KEY_SELECTED_MONTH)); 165 mCalendar.set(Calendar.DAY_OF_MONTH, savedInstanceState.getInt(KEY_SELECTED_DAY)); 166 } 167 } 168 169 @Override 170 public void onSaveInstanceState(Bundle outState) { 171 super.onSaveInstanceState(outState); 172 outState.putInt(KEY_SELECTED_YEAR, mCalendar.get(Calendar.YEAR)); 173 outState.putInt(KEY_SELECTED_MONTH, mCalendar.get(Calendar.MONTH)); 174 outState.putInt(KEY_SELECTED_DAY, mCalendar.get(Calendar.DAY_OF_MONTH)); 175 outState.putInt(KEY_WEEK_START, mWeekStart); 176 outState.putInt(KEY_YEAR_START, mMinYear); 177 outState.putInt(KEY_YEAR_END, mMaxYear); 178 outState.putInt(KEY_CURRENT_VIEW, mCurrentView); 179 int listPosition = -1; 180 if (mCurrentView == MONTH_AND_DAY_VIEW) { 181 listPosition = mDayPickerView.getMostVisiblePosition(); 182 } else if (mCurrentView == YEAR_VIEW) { 183 listPosition = mYearPickerView.getFirstVisiblePosition(); 184 } 185 outState.putInt(KEY_LIST_POSITION, listPosition); 186 } 187 188 @Override 189 public View onCreateView(LayoutInflater inflater, ViewGroup container, 190 Bundle savedInstanceState) { 191 Log.d(TAG, "onCreateView: "); 192 getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE); 193 194 View view = inflater.inflate(R.layout.date_picker_dialog, null); 195 196 mDayOfWeekView = (TextView) view.findViewById(R.id.date_picker_header); 197 mMonthAndDayView = (LinearLayout) view.findViewById(R.id.date_picker_month_and_day); 198 mMonthAndDayView.setOnClickListener(this); 199 mSelectedMonthTextView = (TextView) view.findViewById(R.id.date_picker_month); 200 mSelectedDayTextView = (TextView) view.findViewById(R.id.date_picker_day); 201 mYearView = (TextView) view.findViewById(R.id.date_picker_year); 202 mYearView.setOnClickListener(this); 203 204 int listPosition = -1; 205 int currentView = MONTH_AND_DAY_VIEW; 206 if (savedInstanceState != null) { 207 mWeekStart = savedInstanceState.getInt(KEY_WEEK_START); 208 mMinYear = savedInstanceState.getInt(KEY_YEAR_START); 209 mMaxYear = savedInstanceState.getInt(KEY_YEAR_END); 210 currentView = savedInstanceState.getInt(KEY_CURRENT_VIEW); 211 listPosition = savedInstanceState.getInt(KEY_LIST_POSITION); 212 } 213 214 final Activity activity = getActivity(); 215 mDayPickerView = new DayPickerView(activity, this); 216 mYearPickerView = new YearPickerView(activity, this); 217 218 mAnimator = (ViewAnimator) view.findViewById(R.id.animator); 219 mAnimator.addView(mDayPickerView); 220 mAnimator.addView(mYearPickerView); 221 // TODO: Replace with animation decided upon by the design team. 222 Animation animation = new AlphaAnimation(0.0f, 1.0f); 223 animation.setDuration(ANIMATION_DURATION); 224 mAnimator.setInAnimation(animation); 225 // TODO: Replace with animation decided upon by the design team. 226 Animation animation2 = new AlphaAnimation(1.0f, 0.0f); 227 animation2.setDuration(ANIMATION_DURATION); 228 mAnimator.setOutAnimation(animation2); 229 230 mDoneButton = (Button) view.findViewById(R.id.done); 231 mDoneButton.setOnClickListener(new OnClickListener() { 232 233 @Override 234 public void onClick(View v) { 235 tryVibrate(); 236 if (mCallBack != null) { 237 mCallBack.onDateSet(DatePickerDialog.this, mCalendar.get(Calendar.YEAR), 238 mCalendar.get(Calendar.MONTH), mCalendar.get(Calendar.DAY_OF_MONTH)); 239 } 240 dismiss(); 241 } 242 }); 243 244 updateDisplay(); 245 setCurrentView(currentView); 246 247 if (listPosition != -1) { 248 if (currentView == MONTH_AND_DAY_VIEW) { 249 mDayPickerView.postSetSelection(listPosition); 250 } else if (currentView == YEAR_VIEW) { 251 mYearPickerView.postSetSelection(listPosition); 252 } 253 } 254 return view; 255 } 256 257 private void setCurrentView(final int viewIndex) { 258 switch (viewIndex) { 259 case MONTH_AND_DAY_VIEW: 260 ObjectAnimator pulseAnimator = Utils.getPulseAnimator(mMonthAndDayView, 0.9f, 261 1.05f); 262 if (mDelayAnimation) { 263 pulseAnimator.setStartDelay(ANIMATION_DELAY); 264 mDelayAnimation = false; 265 } 266 mDayPickerView.onDateChanged(); 267 if (mCurrentView != viewIndex) { 268 mMonthAndDayView.setSelected(true); 269 mYearView.setSelected(false); 270 mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW); 271 mCurrentView = viewIndex; 272 } 273 pulseAnimator.start(); 274 break; 275 case YEAR_VIEW: 276 pulseAnimator = Utils.getPulseAnimator(mYearView, 0.85f, 1.1f); 277 if (mDelayAnimation) { 278 pulseAnimator.setStartDelay(ANIMATION_DELAY); 279 mDelayAnimation = false; 280 } 281 mYearPickerView.onDateChanged(); 282 if (mCurrentView != viewIndex) { 283 mMonthAndDayView.setSelected(false); 284 mYearView.setSelected(true); 285 mAnimator.setDisplayedChild(YEAR_VIEW); 286 mCurrentView = viewIndex; 287 } 288 pulseAnimator.start(); 289 break; 290 } 291 } 292 293 private void updateDisplay() { 294 if (mDayOfWeekView != null) { 295 mDayOfWeekView.setText(mCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, 296 Locale.getDefault()).toUpperCase(Locale.getDefault())); 297 } 298 mSelectedMonthTextView.setText(mCalendar.getDisplayName(Calendar.MONTH, Calendar.SHORT, 299 Locale.getDefault()).toUpperCase(Locale.getDefault())); 300 mSelectedDayTextView.setText(DAY_FORMAT.format(mCalendar.getTime())); 301 mYearView.setText(YEAR_FORMAT.format(mCalendar.getTime())); 302 } 303 304 public void setFirstDayOfWeek(int startOfWeek) { 305 if (startOfWeek < Calendar.SUNDAY || startOfWeek > Calendar.SATURDAY) { 306 throw new IllegalArgumentException("Value must be between Calendar.SUNDAY and " + 307 "Calendar.SATURDAY"); 308 } 309 mWeekStart = startOfWeek; 310 if (mDayPickerView != null) { 311 mDayPickerView.onChange(); 312 } 313 } 314 315 public void setYearRange(int startYear, int endYear) { 316 if (endYear <= startYear) { 317 throw new IllegalArgumentException("Year end must be larger than year start"); 318 } 319 mMinYear = startYear; 320 mMaxYear = endYear; 321 if (mDayPickerView != null) { 322 mDayPickerView.onChange(); 323 } 324 } 325 326 public void setOnDateSetListener(OnDateSetListener listener) { 327 mCallBack = listener; 328 } 329 330 // If the newly selected month / year does not contain the currently selected day number, 331 // change the selected day number to the last day of the selected month or year. 332 // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30 333 // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013 334 private void adjustDayInMonthIfNeeded(int month, int year) { 335 int day = mCalendar.get(Calendar.DAY_OF_MONTH); 336 int daysInMonth = Utils.getDaysInMonth(month, year); 337 if (day > daysInMonth) { 338 mCalendar.set(Calendar.DAY_OF_MONTH, daysInMonth); 339 } 340 } 341 342 @Override 343 public void onClick(View v) { 344 tryVibrate(); 345 if (v.getId() == R.id.date_picker_year) { 346 setCurrentView(YEAR_VIEW); 347 } else if (v.getId() == R.id.date_picker_month_and_day) { 348 setCurrentView(MONTH_AND_DAY_VIEW); 349 } 350 } 351 352 @Override 353 public void onYearSelected(int year) { 354 adjustDayInMonthIfNeeded(mCalendar.get(Calendar.MONTH), year); 355 mCalendar.set(Calendar.YEAR, year); 356 updatePickers(); 357 setCurrentView(MONTH_AND_DAY_VIEW); 358 updateDisplay(); 359 } 360 361 @Override 362 public void onDayOfMonthSelected(int year, int month, int day) { 363 mCalendar.set(Calendar.YEAR, year); 364 mCalendar.set(Calendar.MONTH, month); 365 mCalendar.set(Calendar.DAY_OF_MONTH, day); 366 updatePickers(); 367 updateDisplay(); 368 } 369 370 private void updatePickers() { 371 Iterator<OnDateChangedListener> iterator = mListeners.iterator(); 372 while (iterator.hasNext()) { 373 iterator.next().onDateChanged(); 374 } 375 } 376 377 378 @Override 379 public CalendarDay getSelectedDay() { 380 return new CalendarDay(mCalendar); 381 } 382 383 @Override 384 public int getMinYear() { 385 return mMinYear; 386 } 387 388 @Override 389 public int getMaxYear() { 390 return mMaxYear; 391 } 392 393 @Override 394 public int getFirstDayOfWeek() { 395 return mWeekStart; 396 } 397 398 @Override 399 public void registerOnDateChangedListener(OnDateChangedListener listener) { 400 mListeners.add(listener); 401 } 402 403 @Override 404 public void unregisterOnDateChangedListener(OnDateChangedListener listener) { 405 mListeners.remove(listener); 406 } 407 408 /** 409 * Try to vibrate. To prevent this becoming a single continuous vibration, nothing will 410 * happen if we have vibrated very recently. 411 */ 412 @Override 413 public void tryVibrate() { 414 if (mVibrator != null) { 415 long now = SystemClock.uptimeMillis(); 416 // We want to try to vibrate each individual tick discretely. 417 if (now - mLastVibrate >= 125) { 418 mVibrator.vibrate(5); 419 mLastVibrate = now; 420 } 421 } 422 } 423} 424