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