DatePickerDialog.java revision f6de1f602ffac70987ebc9fc5e887494a23ddd35
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.app.Activity;
20import android.app.DialogFragment;
21import android.os.Bundle;
22import android.util.Log;
23import android.view.LayoutInflater;
24import android.view.View;
25import android.view.View.OnClickListener;
26import android.view.ViewGroup;
27import android.view.Window;
28import android.view.WindowManager;
29import android.view.animation.AlphaAnimation;
30import android.view.animation.Animation;
31import android.widget.Button;
32import android.widget.LinearLayout;
33import android.widget.TextView;
34import android.widget.ViewAnimator;
35
36import com.android.datetimepicker.R;
37import com.android.datetimepicker.Utils;
38import com.android.datetimepicker.date.SimpleMonthAdapter.CalendarDay;
39
40import java.text.SimpleDateFormat;
41import java.util.Calendar;
42import java.util.Locale;
43
44/**
45 * Dialog allowing users to select a date.
46 */
47public class DatePickerDialog extends DialogFragment implements
48        OnClickListener, DatePickerController {
49
50    private static final String TAG = "DatePickerDialog";
51
52    private static final int TOTAL_VIEWS = 2;
53
54    private static final int MONTH_AND_DAY_VIEW = 0;
55    private static final int YEAR_VIEW = 1;
56
57    private static final String KEY_SELECTED_YEAR = "year";
58    private static final String KEY_SELECTED_MONTH = "month";
59    private static final String KEY_SELECTED_DAY = "day";
60    private static final String KEY_LIST_POSITION = "position";
61    private static final String KEY_WEEK_START = "week_start";
62    private static final String KEY_YEAR_START = "year_start";
63    private static final String KEY_YEAR_END = "year_end";
64    private static final String KEY_CURRENT_VIEW = "current_view";
65
66    private static final int DEFAULT_START_YEAR = 1900;
67    private static final int DEFAULT_END_YEAR = 2100;
68
69    private static final int ANIMATION_DURATION = 500;
70
71    private static SimpleDateFormat YEAR_FORMAT = new SimpleDateFormat("yyyy", Locale.getDefault());
72    private static SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("dd", Locale.getDefault());
73
74    private final Calendar mCalendar = Calendar.getInstance();
75    private OnDateSetListener mCallBack;
76
77    private ViewAnimator mAnimator;
78
79    private TextView mDayOfWeekView;
80    private LinearLayout mMonthAndDayView;
81    private TextView mSelectedMonthTextView;
82    private TextView mSelectedDayTextView;
83    private TextView mYearView;
84    private DayPickerView mDayPickerView;
85    private YearPickerView mYearPickerView;
86    private Button mDoneButton;
87
88    private int mCurrentView;
89
90    private int mWeekStart = mCalendar.getFirstDayOfWeek();
91    private int mMinYear = DEFAULT_START_YEAR;
92    private int mMaxYear = DEFAULT_END_YEAR;
93
94    private final View[] mViews = new View[TOTAL_VIEWS];
95
96    /**
97     * The callback used to indicate the user is done filling in the date.
98     */
99    public interface OnDateSetListener {
100
101        /**
102         * @param view The view associated with this listener.
103         * @param year The year that was set.
104         * @param monthOfYear The month that was set (0-11) for compatibility
105         *            with {@link java.util.Calendar}.
106         * @param dayOfMonth The day of the month that was set.
107         */
108        void onDateSet(DatePickerDialog dialog, int year, int monthOfYear, int dayOfMonth);
109    }
110
111    public DatePickerDialog() {
112        // Empty constructor required for dialog fragment.
113    }
114
115    /**
116     * @param callBack How the parent is notified that the date is set.
117     * @param year The initial year of the dialog.
118     * @param monthOfYear The initial month of the dialog.
119     * @param dayOfMonth The initial day of the dialog.
120     */
121    public static DatePickerDialog newInstance(OnDateSetListener callBack, int year,
122            int monthOfYear,
123            int dayOfMonth) {
124        DatePickerDialog ret = new DatePickerDialog();
125        ret.initialize(callBack, year, monthOfYear, dayOfMonth);
126        return ret;
127    }
128
129    public void initialize(OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) {
130        mCallBack = callBack;
131        mCalendar.set(Calendar.YEAR, year);
132        mCalendar.set(Calendar.MONTH, monthOfYear);
133        mCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
134    }
135
136    @Override
137    public void onCreate(Bundle savedInstanceState) {
138        super.onCreate(savedInstanceState);
139        getActivity().getWindow().setSoftInputMode(
140                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
141        if (savedInstanceState != null) {
142            mCalendar.set(Calendar.YEAR, savedInstanceState.getInt(KEY_SELECTED_YEAR));
143            mCalendar.set(Calendar.MONTH, savedInstanceState.getInt(KEY_SELECTED_MONTH));
144            mCalendar.set(Calendar.DAY_OF_MONTH, savedInstanceState.getInt(KEY_SELECTED_DAY));
145        }
146    }
147
148    @Override
149    public void onSaveInstanceState(Bundle outState) {
150        super.onSaveInstanceState(outState);
151        outState.putInt(KEY_SELECTED_YEAR, mCalendar.get(Calendar.YEAR));
152        outState.putInt(KEY_SELECTED_MONTH, mCalendar.get(Calendar.MONTH));
153        outState.putInt(KEY_SELECTED_DAY, mCalendar.get(Calendar.DAY_OF_MONTH));
154        outState.putInt(KEY_LIST_POSITION, mDayPickerView.getFirstVisiblePosition());
155        outState.putInt(KEY_WEEK_START, mWeekStart);
156        outState.putInt(KEY_YEAR_START, mMinYear);
157        outState.putInt(KEY_YEAR_END, mMaxYear);
158        outState.putInt(KEY_CURRENT_VIEW, mCurrentView);
159    }
160
161    @Override
162    public View onCreateView(LayoutInflater inflater, ViewGroup container,
163            Bundle savedInstanceState) {
164        Log.d(TAG, "onCreateView: ");
165        getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
166
167        View view = inflater.inflate(R.layout.date_picker_dialog, null);
168
169        mDayOfWeekView = (TextView) view.findViewById(R.id.date_picker_header);
170        mMonthAndDayView = (LinearLayout) view.findViewById(R.id.date_picker_month_and_day);
171        mMonthAndDayView.setOnClickListener(this);
172        mSelectedMonthTextView = (TextView) view.findViewById(R.id.date_picker_month);
173        mSelectedDayTextView = (TextView) view.findViewById(R.id.date_picker_day);
174        mYearView = (TextView) view.findViewById(R.id.date_picker_year);
175        mYearView.setOnClickListener(this);
176        final Activity activity = getActivity();
177
178        int currentView = MONTH_AND_DAY_VIEW;
179        mDayPickerView = new DayPickerView(activity, this);
180        if (savedInstanceState != null) {
181            Log.d(TAG,
182                    "Setting first visible position: "
183                            + savedInstanceState.getInt(KEY_LIST_POSITION));
184            mDayPickerView.setSelectionFromTop(savedInstanceState.getInt(KEY_LIST_POSITION),
185                    DayPickerView.LIST_TOP_OFFSET);
186            mWeekStart = savedInstanceState.getInt(KEY_WEEK_START);
187            mMinYear = savedInstanceState.getInt(KEY_YEAR_START);
188            mMaxYear = savedInstanceState.getInt(KEY_YEAR_END);
189            currentView = savedInstanceState.getInt(KEY_CURRENT_VIEW);
190        }
191        mYearPickerView = new YearPickerView(activity, this);
192
193        mViews[MONTH_AND_DAY_VIEW] = mDayPickerView;
194        mViews[YEAR_VIEW] = mYearPickerView;
195
196        mAnimator = (ViewAnimator) view.findViewById(R.id.animator);
197        // TODO: Replace with animation decided upon by the design team.
198        Animation animation = new AlphaAnimation(0.0f, 1.0f);
199        animation.setDuration(ANIMATION_DURATION);
200        mAnimator.setInAnimation(animation);
201        // TODO: Replace with animation decided upon by the design team.
202        Animation animation2 = new AlphaAnimation(1.0f, 0.0f);
203        animation2.setDuration(ANIMATION_DURATION);
204        mAnimator.setOutAnimation(animation2);
205        mAnimator.addView(mDayPickerView);
206        mAnimator.addView(mYearPickerView);
207
208        mDoneButton = (Button) view.findViewById(R.id.done);
209        mDoneButton.setOnClickListener(new OnClickListener() {
210
211            @Override
212            public void onClick(View v) {
213                if (mCallBack != null) {
214                    mCallBack.onDateSet(DatePickerDialog.this, mCalendar.get(Calendar.YEAR),
215                            mCalendar.get(Calendar.MONTH), mCalendar.get(Calendar.DAY_OF_MONTH));
216                }
217                dismiss();
218            }
219        });
220
221        updateDisplay();
222        setCurrentView(currentView);
223
224        return view;
225    }
226
227    private void setCurrentView(final int viewIndex) {
228        switch (viewIndex) {
229            case MONTH_AND_DAY_VIEW:
230                mCurrentView = viewIndex;
231                mMonthAndDayView.setSelected(true);
232                mYearView.setSelected(false);
233                mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW);
234                break;
235            case YEAR_VIEW:
236                mCurrentView = viewIndex;
237                mMonthAndDayView.setSelected(false);
238                mYearView.setSelected(true);
239                mAnimator.setDisplayedChild(YEAR_VIEW);
240                break;
241        }
242    }
243
244    private void updateDisplay() {
245        mDayOfWeekView.setText(mCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG,
246                Locale.getDefault()).toUpperCase(Locale.getDefault()));
247        mSelectedMonthTextView.setText(mCalendar.getDisplayName(Calendar.MONTH, Calendar.SHORT,
248                Locale.getDefault()).toUpperCase(Locale.getDefault()));
249        mSelectedDayTextView.setText(DAY_FORMAT.format(mCalendar.getTime()));
250        mYearView.setText(YEAR_FORMAT.format(mCalendar.getTime()));
251    }
252
253    public void setFirstDayOfWeek(int startOfWeek) {
254        if (startOfWeek < Calendar.SUNDAY || startOfWeek > Calendar.SATURDAY) {
255            throw new IllegalArgumentException("Value must be between Calendar.SUNDAY and " +
256                    "Calendar.SATURDAY");
257        }
258        mWeekStart = startOfWeek;
259        if (mDayPickerView != null) {
260            mDayPickerView.onChange();
261        }
262    }
263
264    public void setYearRange(int startYear, int endYear) {
265        if (endYear <= startYear) {
266            throw new IllegalArgumentException("Year end must be larger than year start");
267        }
268        mMinYear = startYear;
269        mMaxYear = endYear;
270        if (mDayPickerView != null) {
271            mDayPickerView.onChange();
272            mYearPickerView.onChange();
273        }
274    }
275
276    public void setOnDateSetListener(OnDateSetListener listener) {
277        mCallBack = listener;
278    }
279
280    // If the newly selected month / year does not contain the currently selected day number,
281    // change the selected day number to the last day of the selected month or year.
282    //      e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
283    //      e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
284    private void adjustDayInMonthIfNeeded(int month, int year) {
285        int day = mCalendar.get(Calendar.DAY_OF_MONTH);
286        int daysInMonth = Utils.getDaysInMonth(month, year);
287        if (day > daysInMonth) {
288            mCalendar.set(Calendar.DAY_OF_MONTH, daysInMonth);
289        }
290    }
291
292    @Override
293    public void onClick(View v) {
294        if (v.getId() == R.id.date_picker_year) {
295            setCurrentView(YEAR_VIEW);
296        } else if (v.getId() == R.id.date_picker_month_and_day) {
297            setCurrentView(MONTH_AND_DAY_VIEW);
298        }
299    }
300
301    @Override
302    public void onYearPickerSelectionChanged(int year) {
303        adjustDayInMonthIfNeeded(mCalendar.get(Calendar.MONTH), year);
304        mCalendar.set(Calendar.YEAR, year);
305        mDayPickerView.setCalendarDate(getSelectedDay());
306        updateDisplay();
307    }
308
309    @Override
310    public void onMonthPickerSelectionChanged(int month) {
311        adjustDayInMonthIfNeeded(month, mCalendar.get(Calendar.YEAR));
312        mCalendar.set(Calendar.MONTH, month);
313        mDayPickerView.setCalendarDate(getSelectedDay());
314        setCurrentView(MONTH_AND_DAY_VIEW);
315        updateDisplay();
316    }
317
318    @Override
319    public void onDayPickerSelectionChanged(int year, int month, int day) {
320        mCalendar.set(Calendar.YEAR, year);
321        mCalendar.set(Calendar.MONTH, month);
322        mCalendar.set(Calendar.DAY_OF_MONTH, day);
323        mYearPickerView.setValue(mCalendar.get(Calendar.YEAR));
324        updateDisplay();
325    }
326
327
328    @Override
329    public CalendarDay getSelectedDay() {
330        return new CalendarDay(mCalendar);
331    }
332
333    @Override
334    public int getMinYear() {
335        return mMinYear;
336    }
337
338    @Override
339    public int getMaxYear() {
340        return mMaxYear;
341    }
342
343    @Override
344    public int getFirstDayOfWeek() {
345        return mWeekStart;
346    }
347}
348