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