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