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