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 17 package android.widget; 18 19 import android.app.Service; 20 import android.content.Context; 21 import android.content.res.Configuration; 22 import android.content.res.TypedArray; 23 import android.database.DataSetObserver; 24 import android.graphics.Canvas; 25 import android.graphics.Paint; 26 import android.graphics.Rect; 27 import android.graphics.drawable.Drawable; 28 import android.icu.util.Calendar; 29 import android.text.format.DateUtils; 30 import android.util.AttributeSet; 31 import android.util.DisplayMetrics; 32 import android.util.TypedValue; 33 import android.view.GestureDetector; 34 import android.view.LayoutInflater; 35 import android.view.MotionEvent; 36 import android.view.View; 37 import android.view.ViewGroup; 38 39 import com.android.internal.R; 40 41 import libcore.icu.LocaleData; 42 43 import java.util.Locale; 44 45 /** 46 * A delegate implementing the legacy CalendarView 47 */ 48 class CalendarViewLegacyDelegate extends CalendarView.AbstractCalendarViewDelegate { 49 /** 50 * Default value whether to show week number. 51 */ 52 private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true; 53 54 /** 55 * The number of milliseconds in a day.e 56 */ 57 private static final long MILLIS_IN_DAY = 86400000L; 58 59 /** 60 * The number of day in a week. 61 */ 62 private static final int DAYS_PER_WEEK = 7; 63 64 /** 65 * The number of milliseconds in a week. 66 */ 67 private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY; 68 69 /** 70 * Affects when the month selection will change while scrolling upe 71 */ 72 private static final int SCROLL_HYST_WEEKS = 2; 73 74 /** 75 * How long the GoTo fling animation should last. 76 */ 77 private static final int GOTO_SCROLL_DURATION = 1000; 78 79 /** 80 * The duration of the adjustment upon a user scroll in milliseconds. 81 */ 82 private static final int ADJUSTMENT_SCROLL_DURATION = 500; 83 84 /** 85 * How long to wait after receiving an onScrollStateChanged notification 86 * before acting on it. 87 */ 88 private static final int SCROLL_CHANGE_DELAY = 40; 89 90 private static final int DEFAULT_SHOWN_WEEK_COUNT = 6; 91 92 private static final int DEFAULT_DATE_TEXT_SIZE = 14; 93 94 private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6; 95 96 private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12; 97 98 private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2; 99 100 private static final int UNSCALED_BOTTOM_BUFFER = 20; 101 102 private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1; 103 104 private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1; 105 106 private final int mWeekSeparatorLineWidth; 107 108 private int mDateTextSize; 109 110 private Drawable mSelectedDateVerticalBar; 111 112 private final int mSelectedDateVerticalBarWidth; 113 114 private int mSelectedWeekBackgroundColor; 115 116 private int mFocusedMonthDateColor; 117 118 private int mUnfocusedMonthDateColor; 119 120 private int mWeekSeparatorLineColor; 121 122 private int mWeekNumberColor; 123 124 private int mWeekDayTextAppearanceResId; 125 126 private int mDateTextAppearanceResId; 127 128 /** 129 * The top offset of the weeks list. 130 */ 131 private int mListScrollTopOffset = 2; 132 133 /** 134 * The visible height of a week view. 135 */ 136 private int mWeekMinVisibleHeight = 12; 137 138 /** 139 * The visible height of a week view. 140 */ 141 private int mBottomBuffer = 20; 142 143 /** 144 * The number of shown weeks. 145 */ 146 private int mShownWeekCount; 147 148 /** 149 * Flag whether to show the week number. 150 */ 151 private boolean mShowWeekNumber; 152 153 /** 154 * The number of day per week to be shown. 155 */ 156 private int mDaysPerWeek = 7; 157 158 /** 159 * The friction of the week list while flinging. 160 */ 161 private float mFriction = .05f; 162 163 /** 164 * Scale for adjusting velocity of the week list while flinging. 165 */ 166 private float mVelocityScale = 0.333f; 167 168 /** 169 * The adapter for the weeks list. 170 */ 171 private WeeksAdapter mAdapter; 172 173 /** 174 * The weeks list. 175 */ 176 private ListView mListView; 177 178 /** 179 * The name of the month to display. 180 */ 181 private TextView mMonthName; 182 183 /** 184 * The header with week day names. 185 */ 186 private ViewGroup mDayNamesHeader; 187 188 /** 189 * Cached abbreviations for day of week names. 190 */ 191 private String[] mDayNamesShort; 192 193 /** 194 * Cached full-length day of week names. 195 */ 196 private String[] mDayNamesLong; 197 198 /** 199 * The first day of the week. 200 */ 201 private int mFirstDayOfWeek; 202 203 /** 204 * Which month should be displayed/highlighted [0-11]. 205 */ 206 private int mCurrentMonthDisplayed = -1; 207 208 /** 209 * Used for tracking during a scroll. 210 */ 211 private long mPreviousScrollPosition; 212 213 /** 214 * Used for tracking which direction the view is scrolling. 215 */ 216 private boolean mIsScrollingUp = false; 217 218 /** 219 * The previous scroll state of the weeks ListView. 220 */ 221 private int mPreviousScrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE; 222 223 /** 224 * The current scroll state of the weeks ListView. 225 */ 226 private int mCurrentScrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE; 227 228 /** 229 * Listener for changes in the selected day. 230 */ 231 private CalendarView.OnDateChangeListener mOnDateChangeListener; 232 233 /** 234 * Command for adjusting the position after a scroll/fling. 235 */ 236 private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(); 237 238 /** 239 * Temporary instance to avoid multiple instantiations. 240 */ 241 private Calendar mTempDate; 242 243 /** 244 * The first day of the focused month. 245 */ 246 private Calendar mFirstDayOfMonth; 247 248 /** 249 * The start date of the range supported by this picker. 250 */ 251 private Calendar mMinDate; 252 253 /** 254 * The end date of the range supported by this picker. 255 */ 256 private Calendar mMaxDate; 257 CalendarViewLegacyDelegate(CalendarView delegator, Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)258 CalendarViewLegacyDelegate(CalendarView delegator, Context context, AttributeSet attrs, 259 int defStyleAttr, int defStyleRes) { 260 super(delegator, context); 261 262 final TypedArray a = context.obtainStyledAttributes(attrs, 263 R.styleable.CalendarView, defStyleAttr, defStyleRes); 264 mShowWeekNumber = a.getBoolean(R.styleable.CalendarView_showWeekNumber, 265 DEFAULT_SHOW_WEEK_NUMBER); 266 mFirstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek, 267 LocaleData.get(Locale.getDefault()).firstDayOfWeek); 268 final String minDate = a.getString(R.styleable.CalendarView_minDate); 269 if (!CalendarView.parseDate(minDate, mMinDate)) { 270 CalendarView.parseDate(DEFAULT_MIN_DATE, mMinDate); 271 } 272 final String maxDate = a.getString(R.styleable.CalendarView_maxDate); 273 if (!CalendarView.parseDate(maxDate, mMaxDate)) { 274 CalendarView.parseDate(DEFAULT_MAX_DATE, mMaxDate); 275 } 276 if (mMaxDate.before(mMinDate)) { 277 throw new IllegalArgumentException("Max date cannot be before min date."); 278 } 279 mShownWeekCount = a.getInt(R.styleable.CalendarView_shownWeekCount, 280 DEFAULT_SHOWN_WEEK_COUNT); 281 mSelectedWeekBackgroundColor = a.getColor( 282 R.styleable.CalendarView_selectedWeekBackgroundColor, 0); 283 mFocusedMonthDateColor = a.getColor( 284 R.styleable.CalendarView_focusedMonthDateColor, 0); 285 mUnfocusedMonthDateColor = a.getColor( 286 R.styleable.CalendarView_unfocusedMonthDateColor, 0); 287 mWeekSeparatorLineColor = a.getColor( 288 R.styleable.CalendarView_weekSeparatorLineColor, 0); 289 mWeekNumberColor = a.getColor(R.styleable.CalendarView_weekNumberColor, 0); 290 mSelectedDateVerticalBar = a.getDrawable( 291 R.styleable.CalendarView_selectedDateVerticalBar); 292 293 mDateTextAppearanceResId = a.getResourceId( 294 R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small); 295 updateDateTextSize(); 296 297 mWeekDayTextAppearanceResId = a.getResourceId( 298 R.styleable.CalendarView_weekDayTextAppearance, 299 DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID); 300 a.recycle(); 301 302 DisplayMetrics displayMetrics = mDelegator.getResources().getDisplayMetrics(); 303 mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 304 UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics); 305 mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 306 UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics); 307 mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 308 UNSCALED_BOTTOM_BUFFER, displayMetrics); 309 mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 310 UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics); 311 mWeekSeparatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 312 UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics); 313 314 LayoutInflater layoutInflater = (LayoutInflater) mContext 315 .getSystemService(Service.LAYOUT_INFLATER_SERVICE); 316 View content = layoutInflater.inflate(R.layout.calendar_view, null, false); 317 mDelegator.addView(content); 318 319 mListView = mDelegator.findViewById(R.id.list); 320 mDayNamesHeader = content.findViewById(R.id.day_names); 321 mMonthName = content.findViewById(R.id.month_name); 322 323 setUpHeader(); 324 setUpListView(); 325 setUpAdapter(); 326 327 // go to today or whichever is close to today min or max date 328 mTempDate.setTimeInMillis(System.currentTimeMillis()); 329 if (mTempDate.before(mMinDate)) { 330 goTo(mMinDate, false, true, true); 331 } else if (mMaxDate.before(mTempDate)) { 332 goTo(mMaxDate, false, true, true); 333 } else { 334 goTo(mTempDate, false, true, true); 335 } 336 337 mDelegator.invalidate(); 338 } 339 340 @Override setShownWeekCount(int count)341 public void setShownWeekCount(int count) { 342 if (mShownWeekCount != count) { 343 mShownWeekCount = count; 344 mDelegator.invalidate(); 345 } 346 } 347 348 @Override getShownWeekCount()349 public int getShownWeekCount() { 350 return mShownWeekCount; 351 } 352 353 @Override setSelectedWeekBackgroundColor(int color)354 public void setSelectedWeekBackgroundColor(int color) { 355 if (mSelectedWeekBackgroundColor != color) { 356 mSelectedWeekBackgroundColor = color; 357 final int childCount = mListView.getChildCount(); 358 for (int i = 0; i < childCount; i++) { 359 WeekView weekView = (WeekView) mListView.getChildAt(i); 360 if (weekView.mHasSelectedDay) { 361 weekView.invalidate(); 362 } 363 } 364 } 365 } 366 367 @Override getSelectedWeekBackgroundColor()368 public int getSelectedWeekBackgroundColor() { 369 return mSelectedWeekBackgroundColor; 370 } 371 372 @Override setFocusedMonthDateColor(int color)373 public void setFocusedMonthDateColor(int color) { 374 if (mFocusedMonthDateColor != color) { 375 mFocusedMonthDateColor = color; 376 final int childCount = mListView.getChildCount(); 377 for (int i = 0; i < childCount; i++) { 378 WeekView weekView = (WeekView) mListView.getChildAt(i); 379 if (weekView.mHasFocusedDay) { 380 weekView.invalidate(); 381 } 382 } 383 } 384 } 385 386 @Override getFocusedMonthDateColor()387 public int getFocusedMonthDateColor() { 388 return mFocusedMonthDateColor; 389 } 390 391 @Override setUnfocusedMonthDateColor(int color)392 public void setUnfocusedMonthDateColor(int color) { 393 if (mUnfocusedMonthDateColor != color) { 394 mUnfocusedMonthDateColor = color; 395 final int childCount = mListView.getChildCount(); 396 for (int i = 0; i < childCount; i++) { 397 WeekView weekView = (WeekView) mListView.getChildAt(i); 398 if (weekView.mHasUnfocusedDay) { 399 weekView.invalidate(); 400 } 401 } 402 } 403 } 404 405 @Override getUnfocusedMonthDateColor()406 public int getUnfocusedMonthDateColor() { 407 return mUnfocusedMonthDateColor; 408 } 409 410 @Override setWeekNumberColor(int color)411 public void setWeekNumberColor(int color) { 412 if (mWeekNumberColor != color) { 413 mWeekNumberColor = color; 414 if (mShowWeekNumber) { 415 invalidateAllWeekViews(); 416 } 417 } 418 } 419 420 @Override getWeekNumberColor()421 public int getWeekNumberColor() { 422 return mWeekNumberColor; 423 } 424 425 @Override setWeekSeparatorLineColor(int color)426 public void setWeekSeparatorLineColor(int color) { 427 if (mWeekSeparatorLineColor != color) { 428 mWeekSeparatorLineColor = color; 429 invalidateAllWeekViews(); 430 } 431 } 432 433 @Override getWeekSeparatorLineColor()434 public int getWeekSeparatorLineColor() { 435 return mWeekSeparatorLineColor; 436 } 437 438 @Override setSelectedDateVerticalBar(int resourceId)439 public void setSelectedDateVerticalBar(int resourceId) { 440 Drawable drawable = mDelegator.getContext().getDrawable(resourceId); 441 setSelectedDateVerticalBar(drawable); 442 } 443 444 @Override setSelectedDateVerticalBar(Drawable drawable)445 public void setSelectedDateVerticalBar(Drawable drawable) { 446 if (mSelectedDateVerticalBar != drawable) { 447 mSelectedDateVerticalBar = drawable; 448 final int childCount = mListView.getChildCount(); 449 for (int i = 0; i < childCount; i++) { 450 WeekView weekView = (WeekView) mListView.getChildAt(i); 451 if (weekView.mHasSelectedDay) { 452 weekView.invalidate(); 453 } 454 } 455 } 456 } 457 458 @Override getSelectedDateVerticalBar()459 public Drawable getSelectedDateVerticalBar() { 460 return mSelectedDateVerticalBar; 461 } 462 463 @Override setWeekDayTextAppearance(int resourceId)464 public void setWeekDayTextAppearance(int resourceId) { 465 if (mWeekDayTextAppearanceResId != resourceId) { 466 mWeekDayTextAppearanceResId = resourceId; 467 setUpHeader(); 468 } 469 } 470 471 @Override getWeekDayTextAppearance()472 public int getWeekDayTextAppearance() { 473 return mWeekDayTextAppearanceResId; 474 } 475 476 @Override setDateTextAppearance(int resourceId)477 public void setDateTextAppearance(int resourceId) { 478 if (mDateTextAppearanceResId != resourceId) { 479 mDateTextAppearanceResId = resourceId; 480 updateDateTextSize(); 481 invalidateAllWeekViews(); 482 } 483 } 484 485 @Override getDateTextAppearance()486 public int getDateTextAppearance() { 487 return mDateTextAppearanceResId; 488 } 489 490 @Override setMinDate(long minDate)491 public void setMinDate(long minDate) { 492 mTempDate.setTimeInMillis(minDate); 493 if (isSameDate(mTempDate, mMinDate)) { 494 return; 495 } 496 mMinDate.setTimeInMillis(minDate); 497 // make sure the current date is not earlier than 498 // the new min date since the latter is used for 499 // calculating the indices in the adapter thus 500 // avoiding out of bounds error 501 Calendar date = mAdapter.mSelectedDate; 502 if (date.before(mMinDate)) { 503 mAdapter.setSelectedDay(mMinDate); 504 } 505 // reinitialize the adapter since its range depends on min date 506 mAdapter.init(); 507 if (date.before(mMinDate)) { 508 setDate(mTempDate.getTimeInMillis()); 509 } else { 510 // we go to the current date to force the ListView to query its 511 // adapter for the shown views since we have changed the adapter 512 // range and the base from which the later calculates item indices 513 // note that calling setDate will not work since the date is the same 514 goTo(date, false, true, false); 515 } 516 } 517 518 @Override getMinDate()519 public long getMinDate() { 520 return mMinDate.getTimeInMillis(); 521 } 522 523 @Override setMaxDate(long maxDate)524 public void setMaxDate(long maxDate) { 525 mTempDate.setTimeInMillis(maxDate); 526 if (isSameDate(mTempDate, mMaxDate)) { 527 return; 528 } 529 mMaxDate.setTimeInMillis(maxDate); 530 // reinitialize the adapter since its range depends on max date 531 mAdapter.init(); 532 Calendar date = mAdapter.mSelectedDate; 533 if (date.after(mMaxDate)) { 534 setDate(mMaxDate.getTimeInMillis()); 535 } else { 536 // we go to the current date to force the ListView to query its 537 // adapter for the shown views since we have changed the adapter 538 // range and the base from which the later calculates item indices 539 // note that calling setDate will not work since the date is the same 540 goTo(date, false, true, false); 541 } 542 } 543 544 @Override getMaxDate()545 public long getMaxDate() { 546 return mMaxDate.getTimeInMillis(); 547 } 548 549 @Override setShowWeekNumber(boolean showWeekNumber)550 public void setShowWeekNumber(boolean showWeekNumber) { 551 if (mShowWeekNumber == showWeekNumber) { 552 return; 553 } 554 mShowWeekNumber = showWeekNumber; 555 mAdapter.notifyDataSetChanged(); 556 setUpHeader(); 557 } 558 559 @Override getShowWeekNumber()560 public boolean getShowWeekNumber() { 561 return mShowWeekNumber; 562 } 563 564 @Override setFirstDayOfWeek(int firstDayOfWeek)565 public void setFirstDayOfWeek(int firstDayOfWeek) { 566 if (mFirstDayOfWeek == firstDayOfWeek) { 567 return; 568 } 569 mFirstDayOfWeek = firstDayOfWeek; 570 mAdapter.init(); 571 mAdapter.notifyDataSetChanged(); 572 setUpHeader(); 573 } 574 575 @Override getFirstDayOfWeek()576 public int getFirstDayOfWeek() { 577 return mFirstDayOfWeek; 578 } 579 580 @Override setDate(long date)581 public void setDate(long date) { 582 setDate(date, false, false); 583 } 584 585 @Override setDate(long date, boolean animate, boolean center)586 public void setDate(long date, boolean animate, boolean center) { 587 mTempDate.setTimeInMillis(date); 588 if (isSameDate(mTempDate, mAdapter.mSelectedDate)) { 589 return; 590 } 591 goTo(mTempDate, animate, true, center); 592 } 593 594 @Override getDate()595 public long getDate() { 596 return mAdapter.mSelectedDate.getTimeInMillis(); 597 } 598 599 @Override setOnDateChangeListener(CalendarView.OnDateChangeListener listener)600 public void setOnDateChangeListener(CalendarView.OnDateChangeListener listener) { 601 mOnDateChangeListener = listener; 602 } 603 604 @Override getBoundsForDate(long date, Rect outBounds)605 public boolean getBoundsForDate(long date, Rect outBounds) { 606 Calendar calendarDate = Calendar.getInstance(); 607 calendarDate.setTimeInMillis(date); 608 int listViewEntryCount = mListView.getCount(); 609 for (int i = 0; i < listViewEntryCount; i++) { 610 WeekView currWeekView = (WeekView) mListView.getChildAt(i); 611 if (currWeekView.getBoundsForDate(calendarDate, outBounds)) { 612 // Found the date in this week. Now need to offset vertically to return correct 613 // bounds in the coordinate system of the entire layout 614 final int[] weekViewPositionOnScreen = new int[2]; 615 final int[] delegatorPositionOnScreen = new int[2]; 616 currWeekView.getLocationOnScreen(weekViewPositionOnScreen); 617 mDelegator.getLocationOnScreen(delegatorPositionOnScreen); 618 final int extraVerticalOffset = 619 weekViewPositionOnScreen[1] - delegatorPositionOnScreen[1]; 620 outBounds.top += extraVerticalOffset; 621 outBounds.bottom += extraVerticalOffset; 622 return true; 623 } 624 } 625 return false; 626 } 627 628 @Override onConfigurationChanged(Configuration newConfig)629 public void onConfigurationChanged(Configuration newConfig) { 630 setCurrentLocale(newConfig.locale); 631 } 632 633 /** 634 * Sets the current locale. 635 * 636 * @param locale The current locale. 637 */ 638 @Override setCurrentLocale(Locale locale)639 protected void setCurrentLocale(Locale locale) { 640 super.setCurrentLocale(locale); 641 642 mTempDate = getCalendarForLocale(mTempDate, locale); 643 mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale); 644 mMinDate = getCalendarForLocale(mMinDate, locale); 645 mMaxDate = getCalendarForLocale(mMaxDate, locale); 646 } updateDateTextSize()647 private void updateDateTextSize() { 648 TypedArray dateTextAppearance = mDelegator.getContext().obtainStyledAttributes( 649 mDateTextAppearanceResId, R.styleable.TextAppearance); 650 mDateTextSize = dateTextAppearance.getDimensionPixelSize( 651 R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE); 652 dateTextAppearance.recycle(); 653 } 654 655 /** 656 * Invalidates all week views. 657 */ invalidateAllWeekViews()658 private void invalidateAllWeekViews() { 659 final int childCount = mListView.getChildCount(); 660 for (int i = 0; i < childCount; i++) { 661 View view = mListView.getChildAt(i); 662 view.invalidate(); 663 } 664 } 665 666 /** 667 * Gets a calendar for locale bootstrapped with the value of a given calendar. 668 * 669 * @param oldCalendar The old calendar. 670 * @param locale The locale. 671 */ getCalendarForLocale(Calendar oldCalendar, Locale locale)672 private static Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { 673 if (oldCalendar == null) { 674 return Calendar.getInstance(locale); 675 } else { 676 final long currentTimeMillis = oldCalendar.getTimeInMillis(); 677 Calendar newCalendar = Calendar.getInstance(locale); 678 newCalendar.setTimeInMillis(currentTimeMillis); 679 return newCalendar; 680 } 681 } 682 683 /** 684 * @return True if the <code>firstDate</code> is the same as the <code> 685 * secondDate</code>. 686 */ isSameDate(Calendar firstDate, Calendar secondDate)687 private static boolean isSameDate(Calendar firstDate, Calendar secondDate) { 688 return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR) 689 && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR)); 690 } 691 692 /** 693 * Creates a new adapter if necessary and sets up its parameters. 694 */ setUpAdapter()695 private void setUpAdapter() { 696 if (mAdapter == null) { 697 mAdapter = new WeeksAdapter(mContext); 698 mAdapter.registerDataSetObserver(new DataSetObserver() { 699 @Override 700 public void onChanged() { 701 if (mOnDateChangeListener != null) { 702 Calendar selectedDay = mAdapter.getSelectedDay(); 703 mOnDateChangeListener.onSelectedDayChange(mDelegator, 704 selectedDay.get(Calendar.YEAR), 705 selectedDay.get(Calendar.MONTH), 706 selectedDay.get(Calendar.DAY_OF_MONTH)); 707 } 708 } 709 }); 710 mListView.setAdapter(mAdapter); 711 } 712 713 // refresh the view with the new parameters 714 mAdapter.notifyDataSetChanged(); 715 } 716 717 /** 718 * Sets up the strings to be used by the header. 719 */ setUpHeader()720 private void setUpHeader() { 721 mDayNamesShort = new String[mDaysPerWeek]; 722 mDayNamesLong = new String[mDaysPerWeek]; 723 for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) { 724 int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i; 725 mDayNamesShort[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay, 726 DateUtils.LENGTH_SHORTEST); 727 mDayNamesLong[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay, 728 DateUtils.LENGTH_LONG); 729 } 730 731 TextView label = (TextView) mDayNamesHeader.getChildAt(0); 732 if (mShowWeekNumber) { 733 label.setVisibility(View.VISIBLE); 734 } else { 735 label.setVisibility(View.GONE); 736 } 737 for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) { 738 label = (TextView) mDayNamesHeader.getChildAt(i); 739 if (mWeekDayTextAppearanceResId > -1) { 740 label.setTextAppearance(mWeekDayTextAppearanceResId); 741 } 742 if (i < mDaysPerWeek + 1) { 743 label.setText(mDayNamesShort[i - 1]); 744 label.setContentDescription(mDayNamesLong[i - 1]); 745 label.setVisibility(View.VISIBLE); 746 } else { 747 label.setVisibility(View.GONE); 748 } 749 } 750 mDayNamesHeader.invalidate(); 751 } 752 753 /** 754 * Sets all the required fields for the list view. 755 */ setUpListView()756 private void setUpListView() { 757 // Configure the listview 758 mListView.setDivider(null); 759 mListView.setItemsCanFocus(true); 760 mListView.setVerticalScrollBarEnabled(false); 761 mListView.setOnScrollListener(new AbsListView.OnScrollListener() { 762 public void onScrollStateChanged(AbsListView view, int scrollState) { 763 CalendarViewLegacyDelegate.this.onScrollStateChanged(view, scrollState); 764 } 765 766 public void onScroll( 767 AbsListView view, int firstVisibleItem, int visibleItemCount, 768 int totalItemCount) { 769 CalendarViewLegacyDelegate.this.onScroll(view, firstVisibleItem, 770 visibleItemCount, totalItemCount); 771 } 772 }); 773 // Make the scrolling behavior nicer 774 mListView.setFriction(mFriction); 775 mListView.setVelocityScale(mVelocityScale); 776 } 777 778 /** 779 * This moves to the specified time in the view. If the time is not already 780 * in range it will move the list so that the first of the month containing 781 * the time is at the top of the view. If the new time is already in view 782 * the list will not be scrolled unless forceScroll is true. This time may 783 * optionally be highlighted as selected as well. 784 * 785 * @param date The time to move to. 786 * @param animate Whether to scroll to the given time or just redraw at the 787 * new location. 788 * @param setSelected Whether to set the given time as selected. 789 * @param forceScroll Whether to recenter even if the time is already 790 * visible. 791 * 792 * @throws IllegalArgumentException if the provided date is before the 793 * range start or after the range end. 794 */ goTo(Calendar date, boolean animate, boolean setSelected, boolean forceScroll)795 private void goTo(Calendar date, boolean animate, boolean setSelected, 796 boolean forceScroll) { 797 if (date.before(mMinDate) || date.after(mMaxDate)) { 798 throw new IllegalArgumentException("timeInMillis must be between the values of " 799 + "getMinDate() and getMaxDate()"); 800 } 801 // Find the first and last entirely visible weeks 802 int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); 803 View firstChild = mListView.getChildAt(0); 804 if (firstChild != null && firstChild.getTop() < 0) { 805 firstFullyVisiblePosition++; 806 } 807 int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1; 808 if (firstChild != null && firstChild.getTop() > mBottomBuffer) { 809 lastFullyVisiblePosition--; 810 } 811 if (setSelected) { 812 mAdapter.setSelectedDay(date); 813 } 814 // Get the week we're going to 815 int position = getWeeksSinceMinDate(date); 816 817 // Check if the selected day is now outside of our visible range 818 // and if so scroll to the month that contains it 819 if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition 820 || forceScroll) { 821 mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis()); 822 mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1); 823 824 setMonthDisplayed(mFirstDayOfMonth); 825 826 // the earliest time we can scroll to is the min date 827 if (mFirstDayOfMonth.before(mMinDate)) { 828 position = 0; 829 } else { 830 position = getWeeksSinceMinDate(mFirstDayOfMonth); 831 } 832 833 mPreviousScrollState = AbsListView.OnScrollListener.SCROLL_STATE_FLING; 834 if (animate) { 835 mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset, 836 GOTO_SCROLL_DURATION); 837 } else { 838 mListView.setSelectionFromTop(position, mListScrollTopOffset); 839 // Perform any after scroll operations that are needed 840 onScrollStateChanged(mListView, AbsListView.OnScrollListener.SCROLL_STATE_IDLE); 841 } 842 } else if (setSelected) { 843 // Otherwise just set the selection 844 setMonthDisplayed(date); 845 } 846 } 847 848 /** 849 * Called when a <code>view</code> transitions to a new <code>scrollState 850 * </code>. 851 */ onScrollStateChanged(AbsListView view, int scrollState)852 private void onScrollStateChanged(AbsListView view, int scrollState) { 853 mScrollStateChangedRunnable.doScrollStateChange(view, scrollState); 854 } 855 856 /** 857 * Updates the title and selected month if the <code>view</code> has moved to a new 858 * month. 859 */ onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)860 private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, 861 int totalItemCount) { 862 WeekView child = (WeekView) view.getChildAt(0); 863 if (child == null) { 864 return; 865 } 866 867 // Figure out where we are 868 long currScroll = 869 view.getFirstVisiblePosition() * child.getHeight() - child.getBottom(); 870 871 // If we have moved since our last call update the direction 872 if (currScroll < mPreviousScrollPosition) { 873 mIsScrollingUp = true; 874 } else if (currScroll > mPreviousScrollPosition) { 875 mIsScrollingUp = false; 876 } else { 877 return; 878 } 879 880 // Use some hysteresis for checking which month to highlight. This 881 // causes the month to transition when two full weeks of a month are 882 // visible when scrolling up, and when the first day in a month reaches 883 // the top of the screen when scrolling down. 884 int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0; 885 if (mIsScrollingUp) { 886 child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset); 887 } else if (offset != 0) { 888 child = (WeekView) view.getChildAt(offset); 889 } 890 891 if (child != null) { 892 // Find out which month we're moving into 893 int month; 894 if (mIsScrollingUp) { 895 month = child.getMonthOfFirstWeekDay(); 896 } else { 897 month = child.getMonthOfLastWeekDay(); 898 } 899 900 // And how it relates to our current highlighted month 901 int monthDiff; 902 if (mCurrentMonthDisplayed == 11 && month == 0) { 903 monthDiff = 1; 904 } else if (mCurrentMonthDisplayed == 0 && month == 11) { 905 monthDiff = -1; 906 } else { 907 monthDiff = month - mCurrentMonthDisplayed; 908 } 909 910 // Only switch months if we're scrolling away from the currently 911 // selected month 912 if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) { 913 Calendar firstDay = child.getFirstDay(); 914 if (mIsScrollingUp) { 915 firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK); 916 } else { 917 firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK); 918 } 919 setMonthDisplayed(firstDay); 920 } 921 } 922 mPreviousScrollPosition = currScroll; 923 mPreviousScrollState = mCurrentScrollState; 924 } 925 926 /** 927 * Sets the month displayed at the top of this view based on time. Override 928 * to add custom events when the title is changed. 929 * 930 * @param calendar A day in the new focus month. 931 */ setMonthDisplayed(Calendar calendar)932 private void setMonthDisplayed(Calendar calendar) { 933 mCurrentMonthDisplayed = calendar.get(Calendar.MONTH); 934 mAdapter.setFocusMonth(mCurrentMonthDisplayed); 935 final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY 936 | DateUtils.FORMAT_SHOW_YEAR; 937 final long millis = calendar.getTimeInMillis(); 938 String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags); 939 mMonthName.setText(newMonthName); 940 mMonthName.invalidate(); 941 } 942 943 /** 944 * @return Returns the number of weeks between the current <code>date</code> 945 * and the <code>mMinDate</code>. 946 */ getWeeksSinceMinDate(Calendar date)947 private int getWeeksSinceMinDate(Calendar date) { 948 if (date.before(mMinDate)) { 949 throw new IllegalArgumentException("fromDate: " + mMinDate.getTime() 950 + " does not precede toDate: " + date.getTime()); 951 } 952 long endTimeMillis = date.getTimeInMillis() 953 + date.getTimeZone().getOffset(date.getTimeInMillis()); 954 long startTimeMillis = mMinDate.getTimeInMillis() 955 + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis()); 956 long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek) 957 * MILLIS_IN_DAY; 958 return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK); 959 } 960 961 /** 962 * Command responsible for acting upon scroll state changes. 963 */ 964 private class ScrollStateRunnable implements Runnable { 965 private AbsListView mView; 966 967 private int mNewState; 968 969 /** 970 * Sets up the runnable with a short delay in case the scroll state 971 * immediately changes again. 972 * 973 * @param view The list view that changed state 974 * @param scrollState The new state it changed to 975 */ doScrollStateChange(AbsListView view, int scrollState)976 public void doScrollStateChange(AbsListView view, int scrollState) { 977 mView = view; 978 mNewState = scrollState; 979 mDelegator.removeCallbacks(this); 980 mDelegator.postDelayed(this, SCROLL_CHANGE_DELAY); 981 } 982 run()983 public void run() { 984 mCurrentScrollState = mNewState; 985 // Fix the position after a scroll or a fling ends 986 if (mNewState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE 987 && mPreviousScrollState != AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { 988 View child = mView.getChildAt(0); 989 if (child == null) { 990 // The view is no longer visible, just return 991 return; 992 } 993 int dist = child.getBottom() - mListScrollTopOffset; 994 if (dist > mListScrollTopOffset) { 995 if (mIsScrollingUp) { 996 mView.smoothScrollBy(dist - child.getHeight(), 997 ADJUSTMENT_SCROLL_DURATION); 998 } else { 999 mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION); 1000 } 1001 } 1002 } 1003 mPreviousScrollState = mNewState; 1004 } 1005 } 1006 1007 /** 1008 * <p> 1009 * This is a specialized adapter for creating a list of weeks with 1010 * selectable days. It can be configured to display the week number, start 1011 * the week on a given day, show a reduced number of days, or display an 1012 * arbitrary number of weeks at a time. 1013 * </p> 1014 */ 1015 private class WeeksAdapter extends BaseAdapter implements View.OnTouchListener { 1016 1017 private int mSelectedWeek; 1018 1019 private GestureDetector mGestureDetector; 1020 1021 private int mFocusedMonth; 1022 1023 private final Calendar mSelectedDate = Calendar.getInstance(); 1024 1025 private int mTotalWeekCount; 1026 WeeksAdapter(Context context)1027 public WeeksAdapter(Context context) { 1028 mContext = context; 1029 mGestureDetector = new GestureDetector(mContext, new WeeksAdapter.CalendarGestureListener()); 1030 init(); 1031 } 1032 1033 /** 1034 * Set up the gesture detector and selected time 1035 */ init()1036 private void init() { 1037 mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); 1038 mTotalWeekCount = getWeeksSinceMinDate(mMaxDate); 1039 if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek 1040 || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) { 1041 mTotalWeekCount++; 1042 } 1043 notifyDataSetChanged(); 1044 } 1045 1046 /** 1047 * Updates the selected day and related parameters. 1048 * 1049 * @param selectedDay The time to highlight 1050 */ setSelectedDay(Calendar selectedDay)1051 public void setSelectedDay(Calendar selectedDay) { 1052 if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR) 1053 && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) { 1054 return; 1055 } 1056 mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis()); 1057 mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); 1058 mFocusedMonth = mSelectedDate.get(Calendar.MONTH); 1059 notifyDataSetChanged(); 1060 } 1061 1062 /** 1063 * @return The selected day of month. 1064 */ getSelectedDay()1065 public Calendar getSelectedDay() { 1066 return mSelectedDate; 1067 } 1068 1069 @Override getCount()1070 public int getCount() { 1071 return mTotalWeekCount; 1072 } 1073 1074 @Override getItem(int position)1075 public Object getItem(int position) { 1076 return null; 1077 } 1078 1079 @Override getItemId(int position)1080 public long getItemId(int position) { 1081 return position; 1082 } 1083 1084 @Override getView(int position, View convertView, ViewGroup parent)1085 public View getView(int position, View convertView, ViewGroup parent) { 1086 WeekView weekView = null; 1087 if (convertView != null) { 1088 weekView = (WeekView) convertView; 1089 } else { 1090 weekView = new WeekView(mContext); 1091 AbsListView.LayoutParams params = 1092 new AbsListView.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, 1093 FrameLayout.LayoutParams.WRAP_CONTENT); 1094 weekView.setLayoutParams(params); 1095 weekView.setClickable(true); 1096 weekView.setOnTouchListener(this); 1097 } 1098 1099 int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get( 1100 Calendar.DAY_OF_WEEK) : -1; 1101 weekView.init(position, selectedWeekDay, mFocusedMonth); 1102 1103 return weekView; 1104 } 1105 1106 /** 1107 * Changes which month is in focus and updates the view. 1108 * 1109 * @param month The month to show as in focus [0-11] 1110 */ setFocusMonth(int month)1111 public void setFocusMonth(int month) { 1112 if (mFocusedMonth == month) { 1113 return; 1114 } 1115 mFocusedMonth = month; 1116 notifyDataSetChanged(); 1117 } 1118 1119 @Override onTouch(View v, MotionEvent event)1120 public boolean onTouch(View v, MotionEvent event) { 1121 if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) { 1122 WeekView weekView = (WeekView) v; 1123 // if we cannot find a day for the given location we are done 1124 if (!weekView.getDayFromLocation(event.getX(), mTempDate)) { 1125 return true; 1126 } 1127 // it is possible that the touched day is outside the valid range 1128 // we draw whole weeks but range end can fall not on the week end 1129 if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { 1130 return true; 1131 } 1132 onDateTapped(mTempDate); 1133 return true; 1134 } 1135 return false; 1136 } 1137 1138 /** 1139 * Maintains the same hour/min/sec but moves the day to the tapped day. 1140 * 1141 * @param day The day that was tapped 1142 */ onDateTapped(Calendar day)1143 private void onDateTapped(Calendar day) { 1144 setSelectedDay(day); 1145 setMonthDisplayed(day); 1146 } 1147 1148 /** 1149 * This is here so we can identify single tap events and set the 1150 * selected day correctly 1151 */ 1152 class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener { 1153 @Override onSingleTapUp(MotionEvent e)1154 public boolean onSingleTapUp(MotionEvent e) { 1155 return true; 1156 } 1157 } 1158 } 1159 1160 /** 1161 * <p> 1162 * This is a dynamic view for drawing a single week. It can be configured to 1163 * display the week number, start the week on a given day, or show a reduced 1164 * number of days. It is intended for use as a single view within a 1165 * ListView. See {@link WeeksAdapter} for usage. 1166 * </p> 1167 */ 1168 private class WeekView extends View { 1169 1170 private final Rect mTempRect = new Rect(); 1171 1172 private final Paint mDrawPaint = new Paint(); 1173 1174 private final Paint mMonthNumDrawPaint = new Paint(); 1175 1176 // Cache the number strings so we don't have to recompute them each time 1177 private String[] mDayNumbers; 1178 1179 // Quick lookup for checking which days are in the focus month 1180 private boolean[] mFocusDay; 1181 1182 // Whether this view has a focused day. 1183 private boolean mHasFocusedDay; 1184 1185 // Whether this view has only focused days. 1186 private boolean mHasUnfocusedDay; 1187 1188 // The first day displayed by this item 1189 private Calendar mFirstDay; 1190 1191 // The month of the first day in this week 1192 private int mMonthOfFirstWeekDay = -1; 1193 1194 // The month of the last day in this week 1195 private int mLastWeekDayMonth = -1; 1196 1197 // The position of this week, equivalent to weeks since the week of Jan 1198 // 1st, 1900 1199 private int mWeek = -1; 1200 1201 // Quick reference to the width of this view, matches parent 1202 private int mWidth; 1203 1204 // The height this view should draw at in pixels, set by height param 1205 private int mHeight; 1206 1207 // If this view contains the selected day 1208 private boolean mHasSelectedDay = false; 1209 1210 // Which day is selected [0-6] or -1 if no day is selected 1211 private int mSelectedDay = -1; 1212 1213 // The number of days + a spot for week number if it is displayed 1214 private int mNumCells; 1215 1216 // The left edge of the selected day 1217 private int mSelectedLeft = -1; 1218 1219 // The right edge of the selected day 1220 private int mSelectedRight = -1; 1221 WeekView(Context context)1222 public WeekView(Context context) { 1223 super(context); 1224 1225 // Sets up any standard paints that will be used 1226 initializePaints(); 1227 } 1228 1229 /** 1230 * Initializes this week view. 1231 * 1232 * @param weekNumber The number of the week this view represents. The 1233 * week number is a zero based index of the weeks since 1234 * {@link android.widget.CalendarView#getMinDate()}. 1235 * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no 1236 * selected day. 1237 * @param focusedMonth The month that is currently in focus i.e. 1238 * highlighted. 1239 */ init(int weekNumber, int selectedWeekDay, int focusedMonth)1240 public void init(int weekNumber, int selectedWeekDay, int focusedMonth) { 1241 mSelectedDay = selectedWeekDay; 1242 mHasSelectedDay = mSelectedDay != -1; 1243 mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek; 1244 mWeek = weekNumber; 1245 mTempDate.setTimeInMillis(mMinDate.getTimeInMillis()); 1246 1247 mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek); 1248 mTempDate.setFirstDayOfWeek(mFirstDayOfWeek); 1249 1250 // Allocate space for caching the day numbers and focus values 1251 mDayNumbers = new String[mNumCells]; 1252 mFocusDay = new boolean[mNumCells]; 1253 1254 // If we're showing the week number calculate it based on Monday 1255 int i = 0; 1256 if (mShowWeekNumber) { 1257 mDayNumbers[0] = String.format(Locale.getDefault(), "%d", 1258 mTempDate.get(Calendar.WEEK_OF_YEAR)); 1259 i++; 1260 } 1261 1262 // Now adjust our starting day based on the start day of the week 1263 int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK); 1264 mTempDate.add(Calendar.DAY_OF_MONTH, diff); 1265 1266 mFirstDay = (Calendar) mTempDate.clone(); 1267 mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH); 1268 1269 mHasUnfocusedDay = true; 1270 for (; i < mNumCells; i++) { 1271 final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth); 1272 mFocusDay[i] = isFocusedDay; 1273 mHasFocusedDay |= isFocusedDay; 1274 mHasUnfocusedDay &= !isFocusedDay; 1275 // do not draw dates outside the valid range to avoid user confusion 1276 if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { 1277 mDayNumbers[i] = ""; 1278 } else { 1279 mDayNumbers[i] = String.format(Locale.getDefault(), "%d", 1280 mTempDate.get(Calendar.DAY_OF_MONTH)); 1281 } 1282 mTempDate.add(Calendar.DAY_OF_MONTH, 1); 1283 } 1284 // We do one extra add at the end of the loop, if that pushed us to 1285 // new month undo it 1286 if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) { 1287 mTempDate.add(Calendar.DAY_OF_MONTH, -1); 1288 } 1289 mLastWeekDayMonth = mTempDate.get(Calendar.MONTH); 1290 1291 updateSelectionPositions(); 1292 } 1293 1294 /** 1295 * Initialize the paint instances. 1296 */ initializePaints()1297 private void initializePaints() { 1298 mDrawPaint.setFakeBoldText(false); 1299 mDrawPaint.setAntiAlias(true); 1300 mDrawPaint.setStyle(Paint.Style.FILL); 1301 1302 mMonthNumDrawPaint.setFakeBoldText(true); 1303 mMonthNumDrawPaint.setAntiAlias(true); 1304 mMonthNumDrawPaint.setStyle(Paint.Style.FILL); 1305 mMonthNumDrawPaint.setTextAlign(Paint.Align.CENTER); 1306 mMonthNumDrawPaint.setTextSize(mDateTextSize); 1307 } 1308 1309 /** 1310 * Returns the month of the first day in this week. 1311 * 1312 * @return The month the first day of this view is in. 1313 */ getMonthOfFirstWeekDay()1314 public int getMonthOfFirstWeekDay() { 1315 return mMonthOfFirstWeekDay; 1316 } 1317 1318 /** 1319 * Returns the month of the last day in this week 1320 * 1321 * @return The month the last day of this view is in 1322 */ getMonthOfLastWeekDay()1323 public int getMonthOfLastWeekDay() { 1324 return mLastWeekDayMonth; 1325 } 1326 1327 /** 1328 * Returns the first day in this view. 1329 * 1330 * @return The first day in the view. 1331 */ getFirstDay()1332 public Calendar getFirstDay() { 1333 return mFirstDay; 1334 } 1335 1336 /** 1337 * Calculates the day that the given x position is in, accounting for 1338 * week number. 1339 * 1340 * @param x The x position of the touch event. 1341 * @return True if a day was found for the given location. 1342 */ getDayFromLocation(float x, Calendar outCalendar)1343 public boolean getDayFromLocation(float x, Calendar outCalendar) { 1344 final boolean isLayoutRtl = isLayoutRtl(); 1345 1346 int start; 1347 int end; 1348 1349 if (isLayoutRtl) { 1350 start = 0; 1351 end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; 1352 } else { 1353 start = mShowWeekNumber ? mWidth / mNumCells : 0; 1354 end = mWidth; 1355 } 1356 1357 if (x < start || x > end) { 1358 outCalendar.clear(); 1359 return false; 1360 } 1361 1362 // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels 1363 int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start)); 1364 1365 if (isLayoutRtl) { 1366 dayPosition = mDaysPerWeek - 1 - dayPosition; 1367 } 1368 1369 outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis()); 1370 outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition); 1371 1372 return true; 1373 } 1374 getBoundsForDate(Calendar date, Rect outBounds)1375 public boolean getBoundsForDate(Calendar date, Rect outBounds) { 1376 Calendar currDay = Calendar.getInstance(); 1377 currDay.setTime(mFirstDay.getTime()); 1378 for (int i = 0; i < mDaysPerWeek; i++) { 1379 if ((date.get(Calendar.YEAR) == currDay.get(Calendar.YEAR)) 1380 && (date.get(Calendar.MONTH) == currDay.get(Calendar.MONTH)) 1381 && (date.get(Calendar.DAY_OF_MONTH) == currDay.get(Calendar.DAY_OF_MONTH))) { 1382 // We found the matching date. Follow the logic in the draw pass that divides 1383 // the available horizontal space equally between all the entries in this week. 1384 // Note that if we're showing week number, the start entry will be that number. 1385 int cellSize = mWidth / mNumCells; 1386 if (isLayoutRtl()) { 1387 outBounds.left = cellSize * 1388 (mShowWeekNumber ? (mNumCells - i - 2) : (mNumCells - i - 1)); 1389 } else { 1390 outBounds.left = cellSize * (mShowWeekNumber ? i + 1 : i); 1391 } 1392 outBounds.top = 0; 1393 outBounds.right = outBounds.left + cellSize; 1394 outBounds.bottom = getHeight(); 1395 return true; 1396 } 1397 // Add one day 1398 currDay.add(Calendar.DAY_OF_MONTH, 1); 1399 } 1400 return false; 1401 } 1402 1403 @Override onDraw(Canvas canvas)1404 protected void onDraw(Canvas canvas) { 1405 drawBackground(canvas); 1406 drawWeekNumbersAndDates(canvas); 1407 drawWeekSeparators(canvas); 1408 drawSelectedDateVerticalBars(canvas); 1409 } 1410 1411 /** 1412 * This draws the selection highlight if a day is selected in this week. 1413 * 1414 * @param canvas The canvas to draw on 1415 */ drawBackground(Canvas canvas)1416 private void drawBackground(Canvas canvas) { 1417 if (!mHasSelectedDay) { 1418 return; 1419 } 1420 mDrawPaint.setColor(mSelectedWeekBackgroundColor); 1421 1422 mTempRect.top = mWeekSeparatorLineWidth; 1423 mTempRect.bottom = mHeight; 1424 1425 final boolean isLayoutRtl = isLayoutRtl(); 1426 1427 if (isLayoutRtl) { 1428 mTempRect.left = 0; 1429 mTempRect.right = mSelectedLeft - 2; 1430 } else { 1431 mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0; 1432 mTempRect.right = mSelectedLeft - 2; 1433 } 1434 canvas.drawRect(mTempRect, mDrawPaint); 1435 1436 if (isLayoutRtl) { 1437 mTempRect.left = mSelectedRight + 3; 1438 mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; 1439 } else { 1440 mTempRect.left = mSelectedRight + 3; 1441 mTempRect.right = mWidth; 1442 } 1443 canvas.drawRect(mTempRect, mDrawPaint); 1444 } 1445 1446 /** 1447 * Draws the week and month day numbers for this week. 1448 * 1449 * @param canvas The canvas to draw on 1450 */ drawWeekNumbersAndDates(Canvas canvas)1451 private void drawWeekNumbersAndDates(Canvas canvas) { 1452 final float textHeight = mDrawPaint.getTextSize(); 1453 final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeparatorLineWidth; 1454 final int nDays = mNumCells; 1455 final int divisor = 2 * nDays; 1456 1457 mDrawPaint.setTextAlign(Paint.Align.CENTER); 1458 mDrawPaint.setTextSize(mDateTextSize); 1459 1460 int i = 0; 1461 1462 if (isLayoutRtl()) { 1463 for (; i < nDays - 1; i++) { 1464 mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor 1465 : mUnfocusedMonthDateColor); 1466 int x = (2 * i + 1) * mWidth / divisor; 1467 canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint); 1468 } 1469 if (mShowWeekNumber) { 1470 mDrawPaint.setColor(mWeekNumberColor); 1471 int x = mWidth - mWidth / divisor; 1472 canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); 1473 } 1474 } else { 1475 if (mShowWeekNumber) { 1476 mDrawPaint.setColor(mWeekNumberColor); 1477 int x = mWidth / divisor; 1478 canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); 1479 i++; 1480 } 1481 for (; i < nDays; i++) { 1482 mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor 1483 : mUnfocusedMonthDateColor); 1484 int x = (2 * i + 1) * mWidth / divisor; 1485 canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint); 1486 } 1487 } 1488 } 1489 1490 /** 1491 * Draws a horizontal line for separating the weeks. 1492 * 1493 * @param canvas The canvas to draw on. 1494 */ drawWeekSeparators(Canvas canvas)1495 private void drawWeekSeparators(Canvas canvas) { 1496 // If it is the topmost fully visible child do not draw separator line 1497 int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); 1498 if (mListView.getChildAt(0).getTop() < 0) { 1499 firstFullyVisiblePosition++; 1500 } 1501 if (firstFullyVisiblePosition == mWeek) { 1502 return; 1503 } 1504 mDrawPaint.setColor(mWeekSeparatorLineColor); 1505 mDrawPaint.setStrokeWidth(mWeekSeparatorLineWidth); 1506 float startX; 1507 float stopX; 1508 if (isLayoutRtl()) { 1509 startX = 0; 1510 stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; 1511 } else { 1512 startX = mShowWeekNumber ? mWidth / mNumCells : 0; 1513 stopX = mWidth; 1514 } 1515 canvas.drawLine(startX, 0, stopX, 0, mDrawPaint); 1516 } 1517 1518 /** 1519 * Draws the selected date bars if this week has a selected day. 1520 * 1521 * @param canvas The canvas to draw on 1522 */ drawSelectedDateVerticalBars(Canvas canvas)1523 private void drawSelectedDateVerticalBars(Canvas canvas) { 1524 if (!mHasSelectedDay) { 1525 return; 1526 } 1527 mSelectedDateVerticalBar.setBounds( 1528 mSelectedLeft - mSelectedDateVerticalBarWidth / 2, 1529 mWeekSeparatorLineWidth, 1530 mSelectedLeft + mSelectedDateVerticalBarWidth / 2, 1531 mHeight); 1532 mSelectedDateVerticalBar.draw(canvas); 1533 mSelectedDateVerticalBar.setBounds( 1534 mSelectedRight - mSelectedDateVerticalBarWidth / 2, 1535 mWeekSeparatorLineWidth, 1536 mSelectedRight + mSelectedDateVerticalBarWidth / 2, 1537 mHeight); 1538 mSelectedDateVerticalBar.draw(canvas); 1539 } 1540 1541 @Override onSizeChanged(int w, int h, int oldw, int oldh)1542 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 1543 mWidth = w; 1544 updateSelectionPositions(); 1545 } 1546 1547 /** 1548 * This calculates the positions for the selected day lines. 1549 */ updateSelectionPositions()1550 private void updateSelectionPositions() { 1551 if (mHasSelectedDay) { 1552 final boolean isLayoutRtl = isLayoutRtl(); 1553 int selectedPosition = mSelectedDay - mFirstDayOfWeek; 1554 if (selectedPosition < 0) { 1555 selectedPosition += 7; 1556 } 1557 if (mShowWeekNumber && !isLayoutRtl) { 1558 selectedPosition++; 1559 } 1560 if (isLayoutRtl) { 1561 mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells; 1562 1563 } else { 1564 mSelectedLeft = selectedPosition * mWidth / mNumCells; 1565 } 1566 mSelectedRight = mSelectedLeft + mWidth / mNumCells; 1567 } 1568 } 1569 1570 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)1571 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1572 mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView 1573 .getPaddingBottom()) / mShownWeekCount; 1574 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight); 1575 } 1576 } 1577 1578 } 1579