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