DayPickerView.java revision 60b674e07bf7346a673abd4a5f40bddeca16e7ff
1/*
2 * Copyright (C) 2014 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 android.widget;
18
19import com.android.internal.widget.ViewPager;
20import com.android.internal.R;
21
22import android.content.Context;
23import android.content.res.ColorStateList;
24import android.content.res.TypedArray;
25import android.util.AttributeSet;
26import android.util.MathUtils;
27
28import java.util.Calendar;
29import java.util.Locale;
30
31import libcore.icu.LocaleData;
32
33/**
34 * This displays a list of months in a calendar format with selectable days.
35 */
36class DayPickerView extends ViewPager {
37    private static final int DEFAULT_START_YEAR = 1900;
38    private static final int DEFAULT_END_YEAR = 2100;
39
40    private final Calendar mSelectedDay = Calendar.getInstance();
41    private final Calendar mMinDate = Calendar.getInstance();
42    private final Calendar mMaxDate = Calendar.getInstance();
43
44    private final DayPickerAdapter mAdapter;
45
46    /** Temporary calendar used for date calculations. */
47    private Calendar mTempCalendar;
48
49    private OnDaySelectedListener mOnDaySelectedListener;
50
51    public DayPickerView(Context context) {
52        this(context, null);
53    }
54
55    public DayPickerView(Context context, AttributeSet attrs) {
56        this(context, attrs, R.attr.calendarViewStyle);
57    }
58
59    public DayPickerView(Context context, AttributeSet attrs, int defStyleAttr) {
60        this(context, attrs, defStyleAttr, 0);
61    }
62
63    public DayPickerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
64        super(context, attrs, defStyleAttr, defStyleRes);
65
66        final TypedArray a = context.obtainStyledAttributes(attrs,
67                R.styleable.CalendarView, defStyleAttr, defStyleRes);
68
69        final int firstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek,
70                LocaleData.get(Locale.getDefault()).firstDayOfWeek);
71
72        final String minDate = a.getString(R.styleable.CalendarView_minDate);
73        final String maxDate = a.getString(R.styleable.CalendarView_maxDate);
74
75        final int monthTextAppearanceResId = a.getResourceId(
76                R.styleable.CalendarView_monthTextAppearance,
77                R.style.TextAppearance_Material_Widget_Calendar_Month);
78        final int dayOfWeekTextAppearanceResId = a.getResourceId(
79                R.styleable.CalendarView_weekDayTextAppearance,
80                R.style.TextAppearance_Material_Widget_Calendar_DayOfWeek);
81        final int dayTextAppearanceResId = a.getResourceId(
82                R.styleable.CalendarView_dateTextAppearance,
83                R.style.TextAppearance_Material_Widget_Calendar_Day);
84
85        final ColorStateList daySelectorColor = a.getColorStateList(
86                R.styleable.CalendarView_daySelectorColor);
87
88        a.recycle();
89
90        // Set up adapter.
91        mAdapter = new DayPickerAdapter(context,
92                R.layout.date_picker_month_item_material, R.id.month_view);
93        mAdapter.setMonthTextAppearance(monthTextAppearanceResId);
94        mAdapter.setDayOfWeekTextAppearance(dayOfWeekTextAppearanceResId);
95        mAdapter.setDayTextAppearance(dayTextAppearanceResId);
96        mAdapter.setDaySelectorColor(daySelectorColor);
97
98        setAdapter(mAdapter);
99
100        // Set up min and max dates.
101        final Calendar tempDate = Calendar.getInstance();
102        if (!CalendarView.parseDate(minDate, tempDate)) {
103            tempDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
104        }
105        final long minDateMillis = tempDate.getTimeInMillis();
106
107        if (!CalendarView.parseDate(maxDate, tempDate)) {
108            tempDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
109        }
110        final long maxDateMillis = tempDate.getTimeInMillis();
111
112        if (maxDateMillis < minDateMillis) {
113            throw new IllegalArgumentException("maxDate must be >= minDate");
114        }
115
116        final long setDateMillis = MathUtils.constrain(
117                System.currentTimeMillis(), minDateMillis, maxDateMillis);
118
119        setFirstDayOfWeek(firstDayOfWeek);
120        setMinDate(minDateMillis);
121        setMaxDate(maxDateMillis);
122        setDate(setDateMillis, false);
123
124        // Proxy selection callbacks to our own listener.
125        mAdapter.setOnDaySelectedListener(new DayPickerAdapter.OnDaySelectedListener() {
126            @Override
127            public void onDaySelected(DayPickerAdapter adapter, Calendar day) {
128                if (mOnDaySelectedListener != null) {
129                    mOnDaySelectedListener.onDaySelected(DayPickerView.this, day);
130                }
131            }
132
133            @Override
134            public void onNavigationClick(DayPickerAdapter view, int direction, boolean animate) {
135                // ViewPager clamps input values, so we don't need to worry
136                // about passing invalid indices.
137                final int nextItem = getCurrentItem() + direction;
138                setCurrentItem(nextItem, animate);
139            }
140        });
141    }
142
143    public void setDayOfWeekTextAppearance(int resId) {
144        mAdapter.setDayOfWeekTextAppearance(resId);
145    }
146
147    public int getDayOfWeekTextAppearance() {
148        return mAdapter.getDayOfWeekTextAppearance();
149    }
150
151    public void setDayTextAppearance(int resId) {
152        mAdapter.setDayTextAppearance(resId);
153    }
154
155    public int getDayTextAppearance() {
156        return mAdapter.getDayTextAppearance();
157    }
158
159    /**
160     * Sets the currently selected date to the specified timestamp. Jumps
161     * immediately to the new date. To animate to the new date, use
162     * {@link #setDate(long, boolean)}.
163     *
164     * @param timeInMillis the target day in milliseconds
165     */
166    public void setDate(long timeInMillis) {
167        setDate(timeInMillis, false);
168    }
169
170    /**
171     * Sets the currently selected date to the specified timestamp. Jumps
172     * immediately to the new date, optionally animating the transition.
173     *
174     * @param timeInMillis the target day in milliseconds
175     * @param animate whether to smooth scroll to the new position
176     */
177    public void setDate(long timeInMillis, boolean animate) {
178        setDate(timeInMillis, animate, true);
179    }
180
181    /**
182     * Moves to the month containing the specified day, optionally setting the
183     * day as selected.
184     *
185     * @param timeInMillis the target day in milliseconds
186     * @param animate whether to smooth scroll to the new position
187     * @param setSelected whether to set the specified day as selected
188     */
189    private void setDate(long timeInMillis, boolean animate, boolean setSelected) {
190        // Set the selected day
191        if (setSelected) {
192            mSelectedDay.setTimeInMillis(timeInMillis);
193        }
194
195        final int position = getPositionFromDay(timeInMillis);
196        if (position != getCurrentItem()) {
197            setCurrentItem(position, animate);
198        }
199    }
200
201    public long getDate() {
202        return mSelectedDay.getTimeInMillis();
203    }
204
205    public void setFirstDayOfWeek(int firstDayOfWeek) {
206        mAdapter.setFirstDayOfWeek(firstDayOfWeek);
207    }
208
209    public int getFirstDayOfWeek() {
210        return mAdapter.getFirstDayOfWeek();
211    }
212
213    public void setMinDate(long timeInMillis) {
214        mMinDate.setTimeInMillis(timeInMillis);
215        onRangeChanged();
216    }
217
218    public long getMinDate() {
219        return mMinDate.getTimeInMillis();
220    }
221
222    public void setMaxDate(long timeInMillis) {
223        mMaxDate.setTimeInMillis(timeInMillis);
224        onRangeChanged();
225    }
226
227    public long getMaxDate() {
228        return mMaxDate.getTimeInMillis();
229    }
230
231    /**
232     * Handles changes to date range.
233     */
234    public void onRangeChanged() {
235        mAdapter.setRange(mMinDate, mMaxDate);
236
237        // Changing the min/max date changes the selection position since we
238        // don't really have stable IDs. Jumps immediately to the new position.
239        setDate(mSelectedDay.getTimeInMillis(), false, false);
240    }
241
242    /**
243     * Sets the listener to call when the user selects a day.
244     *
245     * @param listener The listener to call.
246     */
247    public void setOnDaySelectedListener(OnDaySelectedListener listener) {
248        mOnDaySelectedListener = listener;
249    }
250
251    private int getDiffMonths(Calendar start, Calendar end) {
252        final int diffYears = end.get(Calendar.YEAR) - start.get(Calendar.YEAR);
253        return end.get(Calendar.MONTH) - start.get(Calendar.MONTH) + 12 * diffYears;
254    }
255
256    private int getPositionFromDay(long timeInMillis) {
257        final int diffMonthMax = getDiffMonths(mMinDate, mMaxDate);
258        final int diffMonth = getDiffMonths(mMinDate, getTempCalendarForTime(timeInMillis));
259        return MathUtils.constrain(diffMonth, 0, diffMonthMax);
260    }
261
262    private Calendar getTempCalendarForTime(long timeInMillis) {
263        if (mTempCalendar == null) {
264            mTempCalendar = Calendar.getInstance();
265        }
266        mTempCalendar.setTimeInMillis(timeInMillis);
267        return mTempCalendar;
268    }
269
270    /**
271     * Gets the position of the view that is most prominently displayed within the list view.
272     */
273    public int getMostVisiblePosition() {
274        return getCurrentItem();
275    }
276
277    public interface OnDaySelectedListener {
278        public void onDaySelected(DayPickerView view, Calendar day);
279    }
280}
281