DayPickerView.java revision 0ef59ac0e57e9b99d174d4a53f7d9639357743ac
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 mAdapter.setMonthTextAppearance(monthTextAppearanceResId); 93 mAdapter.setDayOfWeekTextAppearance(dayOfWeekTextAppearanceResId); 94 mAdapter.setDayTextAppearance(dayTextAppearanceResId); 95 mAdapter.setDaySelectorColor(daySelectorColor); 96 97 setAdapter(mAdapter); 98 99 // Set up min and max dates. 100 final Calendar tempDate = Calendar.getInstance(); 101 if (!CalendarView.parseDate(minDate, tempDate)) { 102 tempDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1); 103 } 104 final long minDateMillis = tempDate.getTimeInMillis(); 105 106 if (!CalendarView.parseDate(maxDate, tempDate)) { 107 tempDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31); 108 } 109 final long maxDateMillis = tempDate.getTimeInMillis(); 110 111 if (maxDateMillis < minDateMillis) { 112 throw new IllegalArgumentException("maxDate must be >= minDate"); 113 } 114 115 final long setDateMillis = MathUtils.constrain( 116 System.currentTimeMillis(), minDateMillis, maxDateMillis); 117 118 setFirstDayOfWeek(firstDayOfWeek); 119 setMinDate(minDateMillis); 120 setMaxDate(maxDateMillis); 121 setDate(setDateMillis, false); 122 123 // Proxy selection callbacks to our own listener. 124 mAdapter.setOnDaySelectedListener(new DayPickerAdapter.OnDaySelectedListener() { 125 @Override 126 public void onDaySelected(DayPickerAdapter adapter, Calendar day) { 127 if (mOnDaySelectedListener != null) { 128 mOnDaySelectedListener.onDaySelected(DayPickerView.this, day); 129 } 130 } 131 }); 132 } 133 134 public void setDayOfWeekTextAppearance(int resId) { 135 mAdapter.setDayOfWeekTextAppearance(resId); 136 } 137 138 public int getDayOfWeekTextAppearance() { 139 return mAdapter.getDayOfWeekTextAppearance(); 140 } 141 142 public void setDayTextAppearance(int resId) { 143 mAdapter.setDayTextAppearance(resId); 144 } 145 146 public int getDayTextAppearance() { 147 return mAdapter.getDayTextAppearance(); 148 } 149 150 /** 151 * Sets the currently selected date to the specified timestamp. Jumps 152 * immediately to the new date. To animate to the new date, use 153 * {@link #setDate(long, boolean)}. 154 * 155 * @param timeInMillis the target day in milliseconds 156 */ 157 public void setDate(long timeInMillis) { 158 setDate(timeInMillis, false); 159 } 160 161 /** 162 * Sets the currently selected date to the specified timestamp. Jumps 163 * immediately to the new date, optionally animating the transition. 164 * 165 * @param timeInMillis the target day in milliseconds 166 * @param animate whether to smooth scroll to the new position 167 */ 168 public void setDate(long timeInMillis, boolean animate) { 169 setDate(timeInMillis, animate, true); 170 } 171 172 /** 173 * Moves to the month containing the specified day, optionally setting the 174 * day as selected. 175 * 176 * @param timeInMillis the target day in milliseconds 177 * @param animate whether to smooth scroll to the new position 178 * @param setSelected whether to set the specified day as selected 179 */ 180 private void setDate(long timeInMillis, boolean animate, boolean setSelected) { 181 // Set the selected day 182 if (setSelected) { 183 mSelectedDay.setTimeInMillis(timeInMillis); 184 } 185 186 final int position = getPositionFromDay(timeInMillis); 187 if (position != getCurrentItem()) { 188 setCurrentItem(position, animate); 189 } 190 } 191 192 public long getDate() { 193 return mSelectedDay.getTimeInMillis(); 194 } 195 196 public void setFirstDayOfWeek(int firstDayOfWeek) { 197 mAdapter.setFirstDayOfWeek(firstDayOfWeek); 198 } 199 200 public int getFirstDayOfWeek() { 201 return mAdapter.getFirstDayOfWeek(); 202 } 203 204 public void setMinDate(long timeInMillis) { 205 mMinDate.setTimeInMillis(timeInMillis); 206 onRangeChanged(); 207 } 208 209 public long getMinDate() { 210 return mMinDate.getTimeInMillis(); 211 } 212 213 public void setMaxDate(long timeInMillis) { 214 mMaxDate.setTimeInMillis(timeInMillis); 215 onRangeChanged(); 216 } 217 218 public long getMaxDate() { 219 return mMaxDate.getTimeInMillis(); 220 } 221 222 /** 223 * Handles changes to date range. 224 */ 225 public void onRangeChanged() { 226 mAdapter.setRange(mMinDate, mMaxDate); 227 228 // Changing the min/max date changes the selection position since we 229 // don't really have stable IDs. Jumps immediately to the new position. 230 setDate(mSelectedDay.getTimeInMillis(), false, false); 231 } 232 233 /** 234 * Sets the listener to call when the user selects a day. 235 * 236 * @param listener The listener to call. 237 */ 238 public void setOnDaySelectedListener(OnDaySelectedListener listener) { 239 mOnDaySelectedListener = listener; 240 } 241 242 private int getDiffMonths(Calendar start, Calendar end) { 243 final int diffYears = end.get(Calendar.YEAR) - start.get(Calendar.YEAR); 244 return end.get(Calendar.MONTH) - start.get(Calendar.MONTH) + 12 * diffYears; 245 } 246 247 private int getPositionFromDay(long timeInMillis) { 248 final int diffMonthMax = getDiffMonths(mMinDate, mMaxDate); 249 final int diffMonth = getDiffMonths(mMinDate, getTempCalendarForTime(timeInMillis)); 250 return MathUtils.constrain(diffMonth, 0, diffMonthMax); 251 } 252 253 private Calendar getTempCalendarForTime(long timeInMillis) { 254 if (mTempCalendar == null) { 255 mTempCalendar = Calendar.getInstance(); 256 } 257 mTempCalendar.setTimeInMillis(timeInMillis); 258 return mTempCalendar; 259 } 260 261 /** 262 * Gets the position of the view that is most prominently displayed within the list view. 263 */ 264 public int getMostVisiblePosition() { 265 return getCurrentItem(); 266 } 267 268 public interface OnDaySelectedListener { 269 public void onDaySelected(DayPickerView view, Calendar day); 270 } 271} 272