diff --git a/CHANGELOG.md b/CHANGELOG.md index 39e6c2b0..ce26043e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ Change Log ========== +Version 1.3.0 *(2016-05-16)* +---------------------------- + +* New: MCV `goToNext` and `goToPrevious` API to programmatically trigger paging +* New: Allow users to click on dates outside of current month with `setAllowClickDaysOutsideCurrentMonth` +* New: Set tile width/height separately rather than single tile size +* New: Attributes: mcv_tileWidth, mcv_tileHeight, mcv_calendarMode +* Change: `CalendarMode.WEEK` officially marked `@Experimental`, use with caution +* Change: `getTileSize` is deprecated, use `getTileWidth` and `getTileHeight`. `setTileSize` still works as a convenience method to set width and height at the same time. +* Fix: Issue with arrow not enabled when setting maxDate +* Fix: Issue with number of pages not calculated correctly with maxDate causing last page to be unreachable +* Fix: TalkBack content descriptions for pager view, forward/back arrows, and ability to set them manually +* Fix: Crash while in Week mode when `CalendarPagerAdapter#getItemPosition` is called +* Fix: Calendar Mode is retained on restore instance state +* Fix: Min/Max date range is retained on restore instance state +* Issue: Week mode - Restore instance state shows the previous week of the one that was saved +* Issue: Week mode - Some combinations of first day of week, min/max date can cause the last week not to be pagable Version 1.2.1 *(2016-05-05)* ---------------------------- @@ -21,7 +38,7 @@ Version 1.1.0 *(2015-10-19)* * New: Ability to disable month swiping with `setPagingEnabled()` * Fix [#149](https://github.com/prolificinteractive/material-calendarview/issues/149): save selected dates as a typed List instead of an array. -* Change: Some preformance optimizations +* Change: Some performance optimizations Version 1.0.1 *(2015-09-30)* ---------------------------- diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..02d2524f --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016 Prolific Interactive + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 07095092..00000000 --- a/LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2016 Prolific Interactive - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md index 366e1a70..9f6dd3ad 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + Material Calendar View [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Material%20Calendar%20View-blue.svg?style=flat)](https://android-arsenal.com/details/1/1531) ====================== @@ -11,10 +11,11 @@ and feel, rather than 100% parity with the platform's implementation. Usage ----- -1. Add `compile 'com.prolificinteractive:material-calendarview:1.2.1'` to your dependencies. +1. Add `compile 'com.prolificinteractive:material-calendarview:1.3.0'` to your dependencies. 2. Add `MaterialCalendarView` into your layouts or view hierarchy. 3. Set a `OnDateSelectedListener` or call `MaterialCalendarView.getSelectedDates()` when you need it. + [Javadoc Available Here](http://prolificinteractive.github.io/material-calendarview/) Example: @@ -29,6 +30,20 @@ Example: app:mcv_selectionColor="#00F" /> ``` +#### @Experimental +`CalendarMode.WEEK` and all week mode functionality is officially marked `@Experimental`. All APIs +marked `@Experimental` are subject to change quickly and should not be used in production code. They +are allowed for testing and feedback. + +Major Change in 1.3.0 +--------------------- +* Breaking change: `getTileSize` is deprecated. Use `getTileWidth` or `getTileHeight`. +* Added `goToNext` and `goToPrevious` API to programmatically trigger paging +* Allow users to click on dates outside of current month with `setAllowClickDaysOutsideCurrentMonth` +* Set tile width/height separately rather than single tile size with `setTileWidth` and `setTileHeight` +* Attributes: mcv_tileWidth, mcv_tileHeight, mcv_calendarMode + +See other changes in the [CHANGELOG](/CHANGELOG.md). Major Change in 1.2.0 --------------------- @@ -82,19 +97,14 @@ Contributing Would you like to contribute? Fork us and send a pull request! Be sure to checkout our issues first. -License -======= - ->Copyright 2016 Prolific Interactive -> ->Licensed under the Apache License, Version 2.0 (the "License"); ->you may not use this file except in compliance with the License. ->You may obtain a copy of the License at -> -> http://www.apache.org/licenses/LICENSE-2.0 -> ->Unless required by applicable law or agreed to in writing, software ->distributed under the License is distributed on an "AS IS" BASIS, ->WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ->See the License for the specific language governing permissions and ->limitations under the License. +## License + +Material Calendar View is Copyright (c) 2016 Prolific Interactive. It may be redistributed under the terms specified in the [LICENSE] file. + +[LICENSE]: /LICENSE + +## Maintainers + +![prolific](https://s3.amazonaws.com/prolificsitestaging/logos/Prolific_Logo_Full_Color.png) + +Material Calendar View is maintained and funded by Prolific Interactive. The names and logos are trademarks of Prolific Interactive. diff --git a/build.gradle b/build.gradle index 1aa30e02..df956abf 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ allprojects { repositories { jcenter() // Here for convience when testing new releases - //maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } +// maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } } // Is Release Build? ext.isReleaseVersion = has("release") diff --git a/gradle.properties b/gradle.properties index ad1a06c9..9481178d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,8 +18,8 @@ # org.gradle.parallel=true GROUP=com.prolificinteractive -VERSION_NAME=1.2.1 -VERSION_CODE=13 +VERSION_NAME=1.3.0 +VERSION_CODE=14 POM_PACKAGING=aar POM_NAME=Material CalendarView diff --git a/images/hero.png b/images/hero.png new file mode 100644 index 00000000..1f678e06 Binary files /dev/null and b/images/hero.png differ diff --git a/images/ic_launcher-web.png b/images/ic_launcher-web.png deleted file mode 100644 index 4a52a883..00000000 Binary files a/images/ic_launcher-web.png and /dev/null differ diff --git a/library/build.gradle b/library/build.gradle index 2071f030..2f3b9669 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -15,7 +15,7 @@ android { } dependencies { - compile 'com.android.support:support-v4:23.1.1' + compile 'com.android.support:support-v4:23.4.0' testCompile 'junit:junit:4.12' } diff --git a/library/src/main/java/com/prolificinteractive/materialcalendarview/CalendarMode.java b/library/src/main/java/com/prolificinteractive/materialcalendarview/CalendarMode.java index 5555fbaa..da93e4e9 100644 --- a/library/src/main/java/com/prolificinteractive/materialcalendarview/CalendarMode.java +++ b/library/src/main/java/com/prolificinteractive/materialcalendarview/CalendarMode.java @@ -1,5 +1,6 @@ package com.prolificinteractive.materialcalendarview; +@Experimental public enum CalendarMode { MONTHS(6), diff --git a/library/src/main/java/com/prolificinteractive/materialcalendarview/CalendarPagerAdapter.java b/library/src/main/java/com/prolificinteractive/materialcalendarview/CalendarPagerAdapter.java index e8c4997b..6926a377 100644 --- a/library/src/main/java/com/prolificinteractive/materialcalendarview/CalendarPagerAdapter.java +++ b/library/src/main/java/com/prolificinteractive/materialcalendarview/CalendarPagerAdapter.java @@ -12,7 +12,6 @@ import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Calendar; import java.util.Collections; import java.util.List; @@ -40,7 +39,6 @@ abstract class CalendarPagerAdapter extends PagerAd private DayFormatter dayFormatter = DayFormatter.DEFAULT; private List decorators = new ArrayList<>(); private List decoratorResults = null; - private int firstDayOfTheWeek = Calendar.SUNDAY; private boolean selectionEnabled = true; CalendarPagerAdapter(MaterialCalendarView mcv) { @@ -85,13 +83,14 @@ public CalendarPagerAdapter migrateStateAndReturn(CalendarPagerAdapter new newAdapter.color = color; newAdapter.dateTextAppearance = dateTextAppearance; newAdapter.weekDayTextAppearance = weekDayTextAppearance; - newAdapter.dayFormatter = dayFormatter; - newAdapter.decorators = decorators; newAdapter.showOtherDates = showOtherDates; newAdapter.minDate = minDate; newAdapter.maxDate = maxDate; newAdapter.selectedDates = selectedDates; - newAdapter.firstDayOfTheWeek = firstDayOfTheWeek; + newAdapter.weekDayFormatter = weekDayFormatter; + newAdapter.dayFormatter = dayFormatter; + newAdapter.decorators = decorators; + newAdapter.decoratorResults = decoratorResults; newAdapter.selectionEnabled = selectionEnabled; return newAdapter; } @@ -122,9 +121,9 @@ public int getItemPosition(Object object) { if (!(isInstanceOfView(object))) { return POSITION_NONE; } - MonthView monthView = (MonthView) object; - CalendarDay month = monthView.getMonth(); - if (month == null) { + CalendarPagerView pagerView = (CalendarPagerView) object; + CalendarDay firstViewDay = pagerView.getFirstViewDay(); + if (firstViewDay == null) { return POSITION_NONE; } int index = indexOf((V) object); @@ -137,6 +136,7 @@ public int getItemPosition(Object object) { @Override public Object instantiateItem(ViewGroup container, int position) { V pagerView = createView(position); + pagerView.setContentDescription(mcv.getCalendarContentDescription()); pagerView.setAlpha(0); pagerView.setSelectionEnabled(selectionEnabled); @@ -164,13 +164,6 @@ public Object instantiateItem(ViewGroup container, int position) { return pagerView; } - public void setFirstDayOfWeek(int day) { - firstDayOfTheWeek = day; - for (V pagerView : currentViews) { - pagerView.setFirstDayOfWeek(firstDayOfTheWeek); - } - } - public void setSelectionEnabled(boolean enabled) { selectionEnabled = enabled; for (V pagerView : currentViews) { @@ -327,8 +320,4 @@ protected int getDateTextAppearance() { protected int getWeekDayTextAppearance() { return weekDayTextAppearance == null ? 0 : weekDayTextAppearance; } - - public int getFirstDayOfWeek() { - return firstDayOfTheWeek; - } } diff --git a/library/src/main/java/com/prolificinteractive/materialcalendarview/CalendarPagerView.java b/library/src/main/java/com/prolificinteractive/materialcalendarview/CalendarPagerView.java index 686c9526..60ff7de1 100644 --- a/library/src/main/java/com/prolificinteractive/materialcalendarview/CalendarPagerView.java +++ b/library/src/main/java/com/prolificinteractive/materialcalendarview/CalendarPagerView.java @@ -24,6 +24,8 @@ abstract class CalendarPagerView extends ViewGroup implements View.OnClickListener { protected static final int DEFAULT_DAYS_IN_WEEK = 7; + protected static final int DEFAULT_MAX_WEEKS = 6; + protected static final int DAY_NAMES_ROW = 1; private static final Calendar tempWorkingCalendar = CalendarUtils.getInstance(); private final ArrayList weekDayViews = new ArrayList<>(); private final ArrayList decoratorResults = new ArrayList<>(); @@ -90,26 +92,6 @@ protected int getFirstDayOfWeek() { return firstDayOfWeek; } - public void setFirstDayOfWeek(int dayOfWeek) { - this.firstDayOfWeek = dayOfWeek; - - Calendar calendar = resetAndGetWorkingCalendar(); - calendar.set(DAY_OF_WEEK, dayOfWeek); - for (WeekDayView dayView : weekDayViews) { - dayView.setDayOfWeek(calendar); - calendar.add(DATE, 1); - } - - calendar = resetAndGetWorkingCalendar(); - for (DayView dayView : dayViews) { - CalendarDay day = CalendarDay.from(calendar); - dayView.setDay(day); - calendar.add(DATE, 1); - } - - updateUi(); - } - protected abstract void buildDayViews(Collection dayViews, Calendar calendar); protected abstract boolean isDayEnabled(CalendarDay day); @@ -207,8 +189,8 @@ protected void invalidateDecorators() { @Override public void onClick(View v) { if (v instanceof DayView) { - DayView dayView = (DayView) v; - mcv.onDateClicked(dayView.getDate(), !dayView.isChecked()); + final DayView dayView = (DayView) v; + mcv.onDateClicked(dayView); } } @@ -240,22 +222,24 @@ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec } //The spec width should be a correct multiple - final int measureTileSize = specWidthSize / DEFAULT_DAYS_IN_WEEK; + final int measureTileWidth = specWidthSize / DEFAULT_DAYS_IN_WEEK; + final int measureTileHeight = specHeightSize / getRows(); //Just use the spec sizes setMeasuredDimension(specWidthSize, specHeightSize); int count = getChildCount(); + for (int i = 0; i < count; i++) { final View child = getChildAt(i); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - measureTileSize, + measureTileWidth, MeasureSpec.EXACTLY ); int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( - measureTileSize, + measureTileHeight, MeasureSpec.EXACTLY ); @@ -263,6 +247,12 @@ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec } } + /** + * Return the number of rows to display per page + * @return + */ + protected abstract int getRows(); + /** * {@inheritDoc} */ diff --git a/library/src/main/java/com/prolificinteractive/materialcalendarview/DayView.java b/library/src/main/java/com/prolificinteractive/materialcalendarview/DayView.java index 96d3c32b..09c98b9d 100644 --- a/library/src/main/java/com/prolificinteractive/materialcalendarview/DayView.java +++ b/library/src/main/java/com/prolificinteractive/materialcalendarview/DayView.java @@ -6,9 +6,7 @@ import android.content.res.ColorStateList; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.LinearGradient; import android.graphics.Rect; -import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; import android.graphics.drawable.ShapeDrawable; @@ -43,6 +41,7 @@ class DayView extends CheckedTextView { private final int fadeTime; private Drawable customBackground = null; private Drawable selectionDrawable; + private Drawable mCircleDrawable; private DayFormatter formatter = DayFormatter.DEFAULT; private boolean isInRange = true; @@ -133,7 +132,7 @@ public CalendarDay getDate() { private void setEnabled() { boolean enabled = isInMonth && isInRange && !isDecoratedDisabled; - super.setEnabled(enabled); + super.setEnabled(isInRange && !isDecoratedDisabled); boolean showOtherMonths = showOtherMonths(showOtherDates); boolean showOutOfRange = showOutOfRange(showOtherDates) || showOtherMonths; @@ -153,6 +152,10 @@ private void setEnabled() { shouldBeVisible |= isInMonth && isInRange; } + if (!isInMonth && shouldBeVisible) { + setTextColor(getTextColors().getColorForState( + new int[]{-android.R.attr.state_enabled}, Color.GRAY)); + } setVisibility(shouldBeVisible ? View.VISIBLE : View.INVISIBLE); } @@ -168,11 +171,13 @@ protected void setupSelection(@ShowOtherDates int showOtherDates, boolean inRang @Override protected void onDraw(@NonNull Canvas canvas) { if (customBackground != null) { - canvas.getClipBounds(tempRect); customBackground.setBounds(tempRect); customBackground.setState(getDrawableState()); customBackground.draw(canvas); } + + mCircleDrawable.setBounds(tempRect); + super.onDraw(canvas); } @@ -180,16 +185,17 @@ private void regenerateBackground() { if (selectionDrawable != null) { setBackgroundDrawable(selectionDrawable); } else { - setBackgroundDrawable(generateBackground(selectionColor, fadeTime)); + mCircleDrawable = generateBackground(selectionColor, fadeTime, tempRect); + setBackgroundDrawable(mCircleDrawable); } } - private static Drawable generateBackground(int color, int fadeTime) { + private static Drawable generateBackground(int color, int fadeTime, Rect bounds) { StateListDrawable drawable = new StateListDrawable(); drawable.setExitFadeDuration(fadeTime); drawable.addState(new int[]{android.R.attr.state_checked}, generateCircleDrawable(color)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - drawable.addState(new int[]{android.R.attr.state_pressed}, generateRippleDrawable(color)); + drawable.addState(new int[]{android.R.attr.state_pressed}, generateRippleDrawable(color, bounds)); } else { drawable.addState(new int[]{android.R.attr.state_pressed}, generateCircleDrawable(color)); } @@ -201,20 +207,27 @@ private static Drawable generateBackground(int color, int fadeTime) { private static Drawable generateCircleDrawable(final int color) { ShapeDrawable drawable = new ShapeDrawable(new OvalShape()); - drawable.setShaderFactory(new ShapeDrawable.ShaderFactory() { - @Override - public Shader resize(int width, int height) { - return new LinearGradient(0, 0, 0, 0, color, color, Shader.TileMode.REPEAT); - } - }); + drawable.getPaint().setColor(color); return drawable; } @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private static Drawable generateRippleDrawable(final int color) { + private static Drawable generateRippleDrawable(final int color, Rect bounds) { ColorStateList list = ColorStateList.valueOf(color); Drawable mask = generateCircleDrawable(Color.WHITE); - return new RippleDrawable(list, null, mask); + RippleDrawable rippleDrawable = new RippleDrawable(list, null, mask); +// API 21 + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) { + rippleDrawable.setBounds(bounds); + } + +// API 22. Technically harmless to leave on for API 21 and 23, but not worth risking for 23+ + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1) { + int center = (bounds.left + bounds.right) / 2; + rippleDrawable.setHotspotBounds(center, bounds.top, center, bounds.bottom); + } + + return rippleDrawable; } /** @@ -242,4 +255,24 @@ void applyFacade(DayViewFacade facade) { setText(getLabel()); } } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + calculateBounds(right - left, bottom - top); + regenerateBackground(); + } + + private void calculateBounds(int width, int height) { + final int radius = Math.min(height, width); + // Lollipop platform bug. Rect offset needs to be divided by 4 instead of 2 + final int offsetDivisor = Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP ? 4 : 2; + final int offset = Math.abs(height - width) / offsetDivisor; + + if (width >= height) { + tempRect.set(offset, 0, radius + offset, height); + } else { + tempRect.set(0, offset, width, radius + offset); + } + } } diff --git a/library/src/main/java/com/prolificinteractive/materialcalendarview/Experimental.java b/library/src/main/java/com/prolificinteractive/materialcalendarview/Experimental.java new file mode 100644 index 00000000..7649aa9a --- /dev/null +++ b/library/src/main/java/com/prolificinteractive/materialcalendarview/Experimental.java @@ -0,0 +1,41 @@ +package com.prolificinteractive.materialcalendarview; + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Inspired from https://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/annotations/Beta.java + */ + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Signifies that a public API (public class, method or field) is will almost certainly + * be changed or removed in a future release. An API bearing this annotation should not + * be used or relied upon in production code. APIs exposed with this annotation exist + * to allow broad testing and feedback on experimental features. + **/ +@Retention(RetentionPolicy.CLASS) +@Target({ + ElementType.ANNOTATION_TYPE, + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.METHOD, + ElementType.TYPE}) +@Documented +@Experimental +public @interface Experimental { +} \ No newline at end of file diff --git a/library/src/main/java/com/prolificinteractive/materialcalendarview/MaterialCalendarView.java b/library/src/main/java/com/prolificinteractive/materialcalendarview/MaterialCalendarView.java index 93a1ed22..81826f9b 100644 --- a/library/src/main/java/com/prolificinteractive/materialcalendarview/MaterialCalendarView.java +++ b/library/src/main/java/com/prolificinteractive/materialcalendarview/MaterialCalendarView.java @@ -148,6 +148,7 @@ public class MaterialCalendarView extends ViewGroup { */ public static final int DEFAULT_TILE_SIZE_DP = 44; private static final int DEFAULT_DAYS_IN_WEEK = 7; + private static final int DEFAULT_MAX_WEEKS = 6; private static final int DAY_NAMES_ROW = 1; private static final TitleFormatter DEFAULT_TITLE_FORMATTER = new DateFormatTitleFormatter(); @@ -160,7 +161,7 @@ public class MaterialCalendarView extends ViewGroup { private CalendarPagerAdapter adapter; private CalendarDay currentMonth; private LinearLayout topbar; - private CalendarMode calendarMode = CalendarMode.MONTHS; + private CalendarMode calendarMode; /** * Used for the dynamic calendar height. */ @@ -204,13 +205,17 @@ public void onPageScrolled(int position, float positionOffset, int positionOffse private OnDateSelectedListener listener; private OnMonthChangedListener monthListener; + CharSequence calendarContentDescription; private int accentColor = 0; private int arrowColor = Color.BLACK; private Drawable leftArrowMask; private Drawable rightArrowMask; - private int tileSize = -1; + private int tileHeight = -1; + private int tileWidth = -1; @SelectionMode private int selectionMode = SELECTION_MODE_SINGLE; + private boolean allowClickDaysOutsideCurrentMonth = true; + private int firstDayOfWeek; public MaterialCalendarView(Context context) { this(context, null); @@ -230,21 +235,19 @@ public MaterialCalendarView(Context context, AttributeSet attrs) { } buttonPast = new DirectionButton(getContext()); + buttonPast.setContentDescription(getContext().getString(R.string.previous)); title = new TextView(getContext()); buttonFuture = new DirectionButton(getContext()); + buttonFuture.setContentDescription(getContext().getString(R.string.next)); pager = new CalendarPager(getContext()); - setupChildren(); - title.setOnClickListener(onClickListener); buttonPast.setOnClickListener(onClickListener); buttonFuture.setOnClickListener(onClickListener); titleChanger = new TitleChanger(title); titleChanger.setTitleFormatter(DEFAULT_TITLE_FORMATTER); - adapter = new MonthPagerAdapter(this); - adapter.setTitleFormatter(DEFAULT_TITLE_FORMATTER); - pager.setAdapter(adapter); + pager.setOnPageChangeListener(pageChangeListener); pager.setPageTransformer(false, new ViewPager.PageTransformer() { @Override @@ -257,12 +260,36 @@ public void transformPage(View page, float position) { TypedArray a = context.getTheme() .obtainStyledAttributes(attrs, R.styleable.MaterialCalendarView, 0, 0); try { + int calendarModeIndex = a.getInteger( + R.styleable.MaterialCalendarView_mcv_calendarMode, + 0 + ); + firstDayOfWeek = a.getInteger( + R.styleable.MaterialCalendarView_mcv_firstDayOfWeek, + -1 + ); - int tileSize = a.getDimensionPixelSize(R.styleable.MaterialCalendarView_mcv_tileSize, -1); + if (firstDayOfWeek < 0) { + //Allowing use of Calendar.getInstance() here as a performance optimization + firstDayOfWeek = Calendar.getInstance().getFirstDayOfWeek(); + } + setCalendarDisplayMode(CalendarMode.values()[calendarModeIndex]); + + final int tileSize = a.getDimensionPixelSize(R.styleable.MaterialCalendarView_mcv_tileSize, -1); if (tileSize > 0) { setTileSize(tileSize); } + final int tileWidth = a.getDimensionPixelSize(R.styleable.MaterialCalendarView_mcv_tileWidth, -1); + if (tileWidth > 0) { + setTileWidth(tileWidth); + } + + final int tileHeight = a.getDimensionPixelSize(R.styleable.MaterialCalendarView_mcv_tileHeight, -1); + if (tileHeight > 0) { + setTileHeight(tileHeight); + } + setArrowColor(a.getColor( R.styleable.MaterialCalendarView_mcv_arrowColor, Color.BLACK @@ -317,21 +344,21 @@ public void transformPage(View page, float position) { SHOW_DEFAULTS )); - int firstDayOfWeek = a.getInteger( - R.styleable.MaterialCalendarView_mcv_firstDayOfWeek, - -1 - ); - if (firstDayOfWeek < 0) { - //Allowing use of Calendar.getInstance() here as a performance optimization - firstDayOfWeek = Calendar.getInstance().getFirstDayOfWeek(); - } - setFirstDayOfWeek(firstDayOfWeek); + setAllowClickDaysOutsideCurrentMonth(a.getBoolean( + R.styleable.MaterialCalendarView_mcv_allowClickDaysOutsideCurrentMonth, + true + )); + } catch (Exception e) { e.printStackTrace(); } finally { a.recycle(); } + // Adapter is created while parsing the TypedArray attrs, so setup has to happen after + adapter.setTitleFormatter(DEFAULT_TITLE_FORMATTER); + setupChildren(); + currentMonth = CalendarDay.today(); setCurrentDate(currentMonth); @@ -347,7 +374,6 @@ public void transformPage(View page, float position) { } private void setupChildren() { - topbar = new LinearLayout(getContext()); topbar.setOrientation(LinearLayout.HORIZONTAL); topbar.setClipChildren(false); @@ -421,6 +447,28 @@ public void setSelectionMode(final @SelectionMode int mode) { adapter.setSelectionEnabled(selectionMode != SELECTION_MODE_NONE); } + /** + * Go to previous month or week without using the button {@link #buttonPast}. Should only go to + * previous if {@link #canGoBack()} is true, meaning it's possible to go to the previous month + * or week. + */ + public void goToPrevious() { + if (canGoBack()) { + pager.setCurrentItem(pager.getCurrentItem() - 1, true); + } + } + + /** + * Go to next month or week without using the button {@link #buttonFuture}. Should only go to + * next if {@link #canGoForward()} is enabled, meaning it's possible to go to the next month or + * week. + */ + public void goToNext() { + if (canGoForward()) { + pager.setCurrentItem(pager.getCurrentItem() + 1, true); + } + } + /** * Set calendar display mode. The default mode is Months. * When switching between modes will select todays date, or the selected date, @@ -428,8 +476,9 @@ public void setSelectionMode(final @SelectionMode int mode) { * * @param mode - calendar mode */ + @Experimental public void setCalendarDisplayMode(CalendarMode mode) { - if (calendarMode.equals(mode)) { + if (calendarMode != null && calendarMode.equals(mode)) { return; } @@ -444,9 +493,18 @@ public void setCalendarDisplayMode(CalendarMode mode) { default: throw new IllegalArgumentException("Provided display mode which is not yet implemented"); } - adapter = adapter.migrateStateAndReturn(newAdapter); + if (adapter == null) { + adapter = newAdapter; + } else { + adapter = adapter.migrateStateAndReturn(newAdapter); + } pager.setAdapter(adapter); + setRangeDates(minDate, maxDate); calendarMode = mode; + + // Reset height params after mode change + pager.setLayoutParams(new LayoutParams(calendarMode.visibleWeeksCount + DAY_NAMES_ROW)); + setCurrentDate( selectionMode == SELECTION_MODE_SINGLE && !adapter.getSelectedDates().isEmpty() ? adapter.getSelectedDates().get(0) @@ -470,10 +528,14 @@ public int getSelectionMode() { } /** - * @return the size of tiles in pixels + * Use {@link #getTileWidth()} or {@link #getTileHeight()} instead. This method is deprecated + * and will just return the largest of the two sizes. + * + * @return tile height or width, whichever is larger */ + @Deprecated public int getTileSize() { - return tileSize; + return Math.max(tileHeight, tileWidth); } /** @@ -484,7 +546,8 @@ public int getTileSize() { * @param size the new size for each tile in pixels */ public void setTileSize(int size) { - this.tileSize = size; + this.tileWidth = size; + this.tileHeight = size; requestLayout(); } @@ -496,6 +559,56 @@ public void setTileSizeDp(int tileSizeDp) { setTileSize(dpToPx(tileSizeDp)); } + /** + * @return the height of tiles in pixels + */ + public int getTileHeight() { + return tileHeight; + } + + /** + * Set the height of each tile that makes up the calendar. + * + * @param height the new height for each tile in pixels + */ + public void setTileHeight(int height) { + this.tileHeight = height; + requestLayout(); + } + + /** + * @param tileHeightDp the new height for each tile in dips + * @see #setTileHeight(int) + */ + public void setTileHeightDp(int tileHeightDp) { + setTileHeight(dpToPx(tileHeightDp)); + } + + /** + * @return the width of tiles in pixels + */ + public int getTileWidth() { + return tileWidth; + } + + /** + * Set the width of each tile that makes up the calendar. + * + * @param width the new width for each tile in pixels + */ + public void setTileWidth(int width) { + this.tileWidth = width; + requestLayout(); + } + + /** + * @param tileWidthDp the new width for each tile in dips + * @see #setTileWidth(int) + */ + public void setTileWidthDp(int tileWidthDp) { + setTileWidth(dpToPx(tileWidthDp)); + } + private int dpToPx(int dp) { return (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics() @@ -507,7 +620,7 @@ TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics() * * @return true if there is a future month that can be shown */ - private boolean canGoForward() { + public boolean canGoForward() { return pager.getCurrentItem() < (adapter.getCount() - 1); } @@ -527,7 +640,7 @@ public boolean onTouchEvent(MotionEvent event) { * * @return true if there is a previous month that can be shown */ - private boolean canGoBack() { + public boolean canGoBack() { return pager.getCurrentItem() > 0; } @@ -574,6 +687,44 @@ public void setArrowColor(int color) { invalidate(); } + /** + * Set content description for button past + * + * @param description String to use as content description + */ + public void setContentDescriptionArrowPast(final CharSequence description) { + buttonPast.setContentDescription(description); + } + + /** + * Set content description for button future + * + * @param description String to use as content description + */ + public void setContentDescriptionArrowFuture(final CharSequence description) { + buttonFuture.setContentDescription(description); + } + + /** + * Set content description for calendar + * + * @param description String to use as content description + */ + public void setContentDescriptionCalendar(final CharSequence description) { + calendarContentDescription = description; + } + + /** + * Get content description for calendar + * + * @return calendar's content description + */ + public CharSequence getCalendarContentDescription() { + return calendarContentDescription != null + ? calendarContentDescription + : getContext().getString(R.string.calendar); + } + /** * @return icon used for the left arrow */ @@ -823,6 +974,18 @@ public void setShowOtherDates(@ShowOtherDates int showOtherDates) { adapter.setShowOtherDates(showOtherDates); } + /** + * Allow the user to click on dates from other months that are not out of range. Go to next or + * previous month if a day outside the current month is clicked. The day still need to be + * enabled to be selected. + * Default value is true. Should be used with {@link #SHOW_OTHER_MONTHS}. + * + * @param enabled True to allow the user to click on a day outside current month displayed + */ + public void setAllowClickDaysOutsideCurrentMonth(final boolean enabled) { + this.allowClickDaysOutsideCurrentMonth = enabled; + } + /** * Set a formatter for weekday labels. * @@ -879,6 +1042,13 @@ public int getShowOtherDates() { return adapter.getShowOtherDates(); } + /** + * @return true if allow click on days outside current month displayed + */ + public boolean allowClickDaysOutsideCurrentMonth() { + return allowClickDaysOutsideCurrentMonth; + } + /** * Set a custom formatter for the month/year title * @@ -943,13 +1113,17 @@ protected Parcelable onSaveInstanceState() { ss.dateTextAppearance = adapter.getDateTextAppearance(); ss.weekDayTextAppearance = adapter.getWeekDayTextAppearance(); ss.showOtherDates = getShowOtherDates(); + ss.allowClickDaysOutsideCurrentMonth = allowClickDaysOutsideCurrentMonth(); ss.minDate = getMinimumDate(); ss.maxDate = getMaximumDate(); ss.selectedDates = getSelectedDates(); ss.firstDayOfWeek = getFirstDayOfWeek(); ss.selectionMode = getSelectionMode(); - ss.tileSizePx = getTileSize(); + ss.tileWidthPx = getTileWidth(); + ss.tileHeightPx = getTileHeight(); ss.topbarVisible = getTopbarVisible(); + ss.calendarMode = calendarMode; + ss.currentMonth = currentMonth; return ss; } @@ -961,16 +1135,22 @@ protected void onRestoreInstanceState(Parcelable state) { setDateTextAppearance(ss.dateTextAppearance); setWeekDayTextAppearance(ss.weekDayTextAppearance); setShowOtherDates(ss.showOtherDates); + setAllowClickDaysOutsideCurrentMonth(ss.allowClickDaysOutsideCurrentMonth); + minDate = ss.minDate; + maxDate = ss.maxDate; setRangeDates(ss.minDate, ss.maxDate); clearSelection(); for (CalendarDay calendarDay : ss.selectedDates) { setDateSelected(calendarDay, true); } setFirstDayOfWeek(ss.firstDayOfWeek); - setTileSize(ss.tileSizePx); + setTileWidth(ss.tileWidthPx); + setTileHeight(ss.tileHeightPx); setTopbarVisible(ss.topbarVisible); setSelectionMode(ss.selectionMode); setDynamicHeightEnabled(ss.dynamicHeightEnabled); + setCalendarDisplayMode(ss.calendarMode); + setCurrentDate(ss.currentMonth); } @Override @@ -998,14 +1178,18 @@ public static class SavedState extends BaseSavedState { int dateTextAppearance = 0; int weekDayTextAppearance = 0; int showOtherDates = SHOW_DEFAULTS; + boolean allowClickDaysOutsideCurrentMonth = true; CalendarDay minDate = null; CalendarDay maxDate = null; List selectedDates = new ArrayList<>(); int firstDayOfWeek = Calendar.SUNDAY; - int tileSizePx = -1; + int tileWidthPx = -1; + int tileHeightPx = -1; boolean topbarVisible = true; int selectionMode = SELECTION_MODE_SINGLE; boolean dynamicHeightEnabled = false; + CalendarMode calendarMode = CalendarMode.MONTHS; + CalendarDay currentMonth = null; SavedState(Parcelable superState) { super(superState); @@ -1018,14 +1202,18 @@ public void writeToParcel(@NonNull Parcel out, int flags) { out.writeInt(dateTextAppearance); out.writeInt(weekDayTextAppearance); out.writeInt(showOtherDates); + out.writeByte((byte) (allowClickDaysOutsideCurrentMonth ? 1 : 0)); out.writeParcelable(minDate, 0); out.writeParcelable(maxDate, 0); out.writeTypedList(selectedDates); out.writeInt(firstDayOfWeek); - out.writeInt(tileSizePx); + out.writeInt(tileWidthPx); + out.writeInt(tileHeightPx); out.writeInt(topbarVisible ? 1 : 0); out.writeInt(selectionMode); out.writeInt(dynamicHeightEnabled ? 1 : 0); + out.writeInt(calendarMode == CalendarMode.WEEKS ? 1 : 0); + out.writeParcelable(currentMonth, 0); } public static final Parcelable.Creator CREATOR @@ -1045,15 +1233,19 @@ private SavedState(Parcel in) { dateTextAppearance = in.readInt(); weekDayTextAppearance = in.readInt(); showOtherDates = in.readInt(); + allowClickDaysOutsideCurrentMonth = in.readByte() != 0; ClassLoader loader = CalendarDay.class.getClassLoader(); minDate = in.readParcelable(loader); maxDate = in.readParcelable(loader); in.readTypedList(selectedDates, CalendarDay.CREATOR); firstDayOfWeek = in.readInt(); - tileSizePx = in.readInt(); + tileWidthPx = in.readInt(); + tileHeightPx = in.readInt(); topbarVisible = in.readInt() == 1; selectionMode = in.readInt(); dynamicHeightEnabled = in.readInt() == 1; + calendarMode = in.readInt() == 1 ? CalendarMode.WEEKS : CalendarMode.MONTHS; + currentMonth = in.readParcelable(loader); } } @@ -1079,14 +1271,42 @@ private static int getThemeAccentColor(Context context) { * @see java.util.Calendar */ public void setFirstDayOfWeek(int day) { - adapter.setFirstDayOfWeek(day); + firstDayOfWeek = day; + // TODO: 5/12/16 consider a less nuclear means of resetting the adapter when setting a new + // first day of week and how regular notifyDataSetChanged doesn't work (may require updating + // getItemPosition to flag current object and the ones to the left/right as changed) + CalendarPagerAdapter newAdapter; + switch (calendarMode) { + case MONTHS: + newAdapter = new MonthPagerAdapter(this); + break; + case WEEKS: + newAdapter = new WeekPagerAdapter(this); + break; + default: + throw new IllegalArgumentException("Provided display mode which is not yet implemented"); + } + if (adapter == null) { + adapter = newAdapter; + } else { + adapter = adapter.migrateStateAndReturn(newAdapter); + } + pager.setAdapter(adapter); + setRangeDates(minDate, maxDate); + + setCurrentDate( + selectionMode == SELECTION_MODE_SINGLE && !adapter.getSelectedDates().isEmpty() + ? adapter.getSelectedDates().get(0) + : CalendarDay.today()); + invalidateDecorators(); + updateUi(); } /** * @return The first day of the week as a {@linkplain Calendar} day constant. */ public int getFirstDayOfWeek() { - return adapter.getFirstDayOfWeek(); + return firstDayOfWeek; } /** @@ -1220,7 +1440,8 @@ protected void dispatchOnMonthChanged(final CalendarDay day) { } /** - * Call by MonthView to indicate that a day was clicked and we should handle it + * Call by {@link CalendarPagerView} to indicate that a day was clicked and we should handle it. + * This method will always process the click to the selected date. * * @param date date of the day that was clicked * @param nowSelected true if the date is now selected, false otherwise @@ -1242,6 +1463,29 @@ protected void onDateClicked(@NonNull CalendarDay date, boolean nowSelected) { } } + /** + * Call by {@link CalendarPagerView} to indicate that a day was clicked and we should handle it + * + * @param dayView + */ + protected void onDateClicked(DayView dayView) { + final int currentMonth = getCurrentDate().getMonth(); + final int selectedMonth = dayView.getDate().getMonth(); + + if (calendarMode == CalendarMode.MONTHS) { + if (allowClickDaysOutsideCurrentMonth || currentMonth == selectedMonth) { + if (currentMonth > selectedMonth) { + goToPrevious(); + } else if (currentMonth < selectedMonth) { + goToNext(); + } + onDateClicked(dayView.getDate(), !dayView.isChecked()); + } + } else { + onDateClicked(dayView.getDate(), !dayView.isChecked()); + } + } + /** * Called by the adapter for cases when changes in state result in dates being unselected * @@ -1314,10 +1558,18 @@ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec int desiredTileHeight = desiredHeight / viewTileHeight; int measureTileSize = -1; + int measureTileWidth = -1; + int measureTileHeight = -1; - if (this.tileSize > 0) { - //We have a tileSize set, we should use that - measureTileSize = this.tileSize; + if (this.tileWidth > 0 || this.tileHeight > 0) { + if (this.tileWidth > 0) { + //We have a tileWidth set, we should use that + measureTileWidth = this.tileWidth; + } + if (this.tileHeight > 0) { + //We have a tileHeight set, we should use that + measureTileHeight = this.tileHeight; + } } else if (specWidthMode == MeasureSpec.EXACTLY) { if (specHeightMode == MeasureSpec.EXACTLY) { //Pick the larger of the two explicit sizes @@ -1331,14 +1583,24 @@ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec measureTileSize = desiredTileHeight; } - //Uh oh! We need to default to something, quick! - if (measureTileSize <= 0) { - measureTileSize = dpToPx(DEFAULT_TILE_SIZE_DP); + if (measureTileSize > 0) { + //Use measureTileSize if set + measureTileHeight = measureTileSize; + measureTileWidth = measureTileSize; + } else if (measureTileSize <= 0) { + if (measureTileWidth <= 0) { + //Set width to default if no value were set + measureTileWidth = dpToPx(DEFAULT_TILE_SIZE_DP); + } + if (measureTileHeight <= 0) { + //Set height to default if no value were set + measureTileHeight = dpToPx(DEFAULT_TILE_SIZE_DP); + } } //Calculate our size based off our measured tile size - int measuredWidth = measureTileSize * DEFAULT_DAYS_IN_WEEK; - int measuredHeight = measureTileSize * viewTileHeight; + int measuredWidth = measureTileWidth * DEFAULT_DAYS_IN_WEEK; + int measuredHeight = measureTileHeight * viewTileHeight; //Put padding back in from when we took it away measuredWidth += getPaddingLeft() + getPaddingRight(); @@ -1359,12 +1621,12 @@ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec LayoutParams p = (LayoutParams) child.getLayoutParams(); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - DEFAULT_DAYS_IN_WEEK * measureTileSize, + DEFAULT_DAYS_IN_WEEK * measureTileWidth, MeasureSpec.EXACTLY ); int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( - p.height * measureTileSize, + p.height * measureTileHeight, MeasureSpec.EXACTLY ); diff --git a/library/src/main/java/com/prolificinteractive/materialcalendarview/MonthPagerAdapter.java b/library/src/main/java/com/prolificinteractive/materialcalendarview/MonthPagerAdapter.java index 7123e383..00e72941 100644 --- a/library/src/main/java/com/prolificinteractive/materialcalendarview/MonthPagerAdapter.java +++ b/library/src/main/java/com/prolificinteractive/materialcalendarview/MonthPagerAdapter.java @@ -14,7 +14,7 @@ class MonthPagerAdapter extends CalendarPagerAdapter { @Override protected MonthView createView(int position) { - return new MonthView(mcv, getItem(position), getFirstDayOfWeek()); + return new MonthView(mcv, getItem(position), mcv.getFirstDayOfWeek()); } @Override diff --git a/library/src/main/java/com/prolificinteractive/materialcalendarview/MonthView.java b/library/src/main/java/com/prolificinteractive/materialcalendarview/MonthView.java index 2926e4e5..4ee69c84 100644 --- a/library/src/main/java/com/prolificinteractive/materialcalendarview/MonthView.java +++ b/library/src/main/java/com/prolificinteractive/materialcalendarview/MonthView.java @@ -13,9 +13,6 @@ @SuppressLint("ViewConstructor") class MonthView extends CalendarPagerView { - private static final int DEFAULT_DAYS_IN_WEEK = 7; - private static final int DEFAULT_MAX_WEEKS = 6; - public MonthView(@NonNull MaterialCalendarView view, CalendarDay month, int firstDayOfWeek) { super(view, month, firstDayOfWeek); } @@ -37,4 +34,9 @@ public CalendarDay getMonth() { protected boolean isDayEnabled(CalendarDay day) { return day.getMonth() == getFirstViewDay().getMonth(); } + + @Override + protected int getRows() { + return DEFAULT_MAX_WEEKS + DAY_NAMES_ROW; + } } diff --git a/library/src/main/java/com/prolificinteractive/materialcalendarview/WeekDayView.java b/library/src/main/java/com/prolificinteractive/materialcalendarview/WeekDayView.java index 57b1535c..ac97c2c8 100644 --- a/library/src/main/java/com/prolificinteractive/materialcalendarview/WeekDayView.java +++ b/library/src/main/java/com/prolificinteractive/materialcalendarview/WeekDayView.java @@ -13,6 +13,7 @@ /** * Display a day of the week */ +@Experimental @SuppressLint("ViewConstructor") class WeekDayView extends TextView { diff --git a/library/src/main/java/com/prolificinteractive/materialcalendarview/WeekPagerAdapter.java b/library/src/main/java/com/prolificinteractive/materialcalendarview/WeekPagerAdapter.java index dabfdd27..a1ab577b 100644 --- a/library/src/main/java/com/prolificinteractive/materialcalendarview/WeekPagerAdapter.java +++ b/library/src/main/java/com/prolificinteractive/materialcalendarview/WeekPagerAdapter.java @@ -6,6 +6,7 @@ import java.util.Date; import java.util.concurrent.TimeUnit; +@Experimental public class WeekPagerAdapter extends CalendarPagerAdapter { public WeekPagerAdapter(MaterialCalendarView mcv) { @@ -14,7 +15,7 @@ public WeekPagerAdapter(MaterialCalendarView mcv) { @Override protected WeekView createView(int position) { - return new WeekView(mcv, getItem(position), getFirstDayOfWeek()); + return new WeekView(mcv, getItem(position), mcv.getFirstDayOfWeek()); } @Override @@ -30,7 +31,7 @@ protected boolean isInstanceOfView(Object object) { @Override protected DateRangeIndex createRangeIndex(CalendarDay min, CalendarDay max) { - return new Weekly(min, max, getFirstDayOfWeek()); + return new Weekly(min, max, mcv.getFirstDayOfWeek()); } public static class Weekly implements DateRangeIndex { @@ -41,7 +42,7 @@ public static class Weekly implements DateRangeIndex { public Weekly(@NonNull CalendarDay min, @NonNull CalendarDay max, int firstDayOfWeek) { this.min = getFirstDayOfWeek(min, firstDayOfWeek); - this.count = weekNumberDifference(min, max); + this.count = weekNumberDifference(min, max) + 1; } @Override diff --git a/library/src/main/java/com/prolificinteractive/materialcalendarview/WeekView.java b/library/src/main/java/com/prolificinteractive/materialcalendarview/WeekView.java index 8653357a..3e93d1de 100644 --- a/library/src/main/java/com/prolificinteractive/materialcalendarview/WeekView.java +++ b/library/src/main/java/com/prolificinteractive/materialcalendarview/WeekView.java @@ -10,11 +10,10 @@ * Display a week of {@linkplain DayView}s and * seven {@linkplain WeekDayView}s. */ +@Experimental @SuppressLint("ViewConstructor") public class WeekView extends CalendarPagerView { - private static final int DEFAULT_DAYS_IN_WEEK = 7; - public WeekView(@NonNull MaterialCalendarView view, CalendarDay firstViewDay, int firstDayOfWeek) { @@ -32,4 +31,9 @@ protected void buildDayViews(Collection dayViews, Calendar calendar) { protected boolean isDayEnabled(CalendarDay day) { return true; } + + @Override + protected int getRows() { + return DAY_NAMES_ROW + 1; + } } diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml index d3deee50..7f1e0652 100644 --- a/library/src/main/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -3,14 +3,14 @@ - - - + + + - - - - + + + + @@ -21,10 +21,14 @@ + + - + + + @@ -36,6 +40,10 @@ + + + + diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml new file mode 100644 index 00000000..549c67f7 --- /dev/null +++ b/library/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + + Go to previous + Go to next + Calendar + diff --git a/sample/build.gradle b/sample/build.gradle index 25052394..11d6b179 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -16,10 +16,10 @@ android { dependencies { // You should use the commented out line below in you're application. // We depend on the source directly here so that development is easier. - compile project(':library') - //compile 'com.prolificinteractive:material-calendarview:1.2.1' +// compile project(':library') + compile 'com.prolificinteractive:material-calendarview:1.3.0' - compile 'com.android.support:appcompat-v7:23.3.0' - compile 'com.android.support:recyclerview-v7:23.3.0' + compile 'com.android.support:appcompat-v7:23.4.0' + compile 'com.android.support:recyclerview-v7:23.4.0' compile 'com.jakewharton:butterknife:7.0.1' } diff --git a/sample/src/main/java/com/prolificinteractive/materialcalendarview/sample/CustomizeCodeActivity.java b/sample/src/main/java/com/prolificinteractive/materialcalendarview/sample/CustomizeCodeActivity.java index 3bb93ea3..62588674 100644 --- a/sample/src/main/java/com/prolificinteractive/materialcalendarview/sample/CustomizeCodeActivity.java +++ b/sample/src/main/java/com/prolificinteractive/materialcalendarview/sample/CustomizeCodeActivity.java @@ -4,6 +4,8 @@ import android.support.v7.app.AppCompatActivity; import android.util.TypedValue; +import com.prolificinteractive.materialcalendarview.CalendarDay; +import com.prolificinteractive.materialcalendarview.CalendarMode; import com.prolificinteractive.materialcalendarview.MaterialCalendarView; import com.prolificinteractive.materialcalendarview.format.ArrayWeekDayFormatter; import com.prolificinteractive.materialcalendarview.format.MonthArrayTitleFormatter; @@ -35,7 +37,15 @@ protected void onCreate(Bundle savedInstanceState) { widget.setTitleFormatter(new MonthArrayTitleFormatter(getResources().getTextArray(R.array.custom_months))); widget.setWeekDayFormatter(new ArrayWeekDayFormatter(getResources().getTextArray(R.array.custom_weekdays))); widget.setTileSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 36, getResources().getDisplayMetrics())); - widget.setFirstDayOfWeek(Calendar.THURSDAY); + + CalendarDay today = CalendarDay.from(2016, 5, 2); + widget.setCurrentDate(today); + widget.setSelectedDate(today); + + widget.setFirstDayOfWeek(Calendar.WEDNESDAY); + widget.setMinimumDate(CalendarDay.from(2016, 4, 3)); + widget.setMaximumDate(CalendarDay.from(2016, 5, 12)); + widget.setCalendarDisplayMode(CalendarMode.WEEKS); } } diff --git a/sample/src/main/java/com/prolificinteractive/materialcalendarview/sample/DynamicSettersActivity.java b/sample/src/main/java/com/prolificinteractive/materialcalendarview/sample/DynamicSettersActivity.java index 66b13bf2..06056825 100644 --- a/sample/src/main/java/com/prolificinteractive/materialcalendarview/sample/DynamicSettersActivity.java +++ b/sample/src/main/java/com/prolificinteractive/materialcalendarview/sample/DynamicSettersActivity.java @@ -9,9 +9,11 @@ import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.widget.DatePicker; +import android.widget.LinearLayout; import android.widget.NumberPicker; import com.prolificinteractive.materialcalendarview.CalendarDay; +import com.prolificinteractive.materialcalendarview.CalendarMode; import com.prolificinteractive.materialcalendarview.MaterialCalendarView; import java.util.Calendar; @@ -28,6 +30,8 @@ public class DynamicSettersActivity extends AppCompatActivity { MaterialCalendarView widget; private int currentTileSize; + private int currentTileWidth; + private int currentTileHeight; @Override protected void onCreate(Bundle savedInstanceState) { @@ -36,6 +40,8 @@ protected void onCreate(Bundle savedInstanceState) { ButterKnife.bind(this); currentTileSize = MaterialCalendarView.DEFAULT_TILE_SIZE_DP; + currentTileWidth = MaterialCalendarView.DEFAULT_TILE_SIZE_DP; + currentTileHeight = MaterialCalendarView.DEFAULT_TILE_SIZE_DP; } @OnClick(R.id.button_other_dates) @@ -43,7 +49,8 @@ void onOtherDatesClicked() { CharSequence[] items = { "Other Months", "Out Of Range", - "Decorated Disabled" + "Decorated Disabled", + "Select days outside month" }; final int[] itemValues = { MaterialCalendarView.SHOW_OTHER_MONTHS, @@ -51,25 +58,31 @@ void onOtherDatesClicked() { MaterialCalendarView.SHOW_DECORATED_DISABLED, }; int showOtherDates = widget.getShowOtherDates(); + boolean[] initSelections = { MaterialCalendarView.showOtherMonths(showOtherDates), MaterialCalendarView.showOutOfRange(showOtherDates), MaterialCalendarView.showDecoratedDisabled(showOtherDates), + widget.allowClickDaysOutsideCurrentMonth() }; new AlertDialog.Builder(this) .setTitle("Show Other Dates") .setMultiChoiceItems(items, initSelections, new DialogInterface.OnMultiChoiceClickListener() { @Override public void onClick(DialogInterface dialog, int which, boolean isChecked) { - int showOtherDates = widget.getShowOtherDates(); - if (isChecked) { - //Set flag - showOtherDates |= itemValues[which]; - } else { - //Unset flag - showOtherDates &= ~itemValues[which]; + if (which < 3) { + int showOtherDates = widget.getShowOtherDates(); + if (isChecked) { + //Set flag + showOtherDates |= itemValues[which]; + } else { + //Unset flag + showOtherDates &= ~itemValues[which]; + } + widget.setShowOtherDates(showOtherDates); + } else if (which == 3) { + widget.setAllowClickDaysOutsideCurrentMonth(isChecked); } - widget.setShowOtherDates(showOtherDates); } }) .setPositiveButton(android.R.string.ok, null) @@ -90,12 +103,22 @@ void onTextAppearanceChecked(boolean checked) { widget.setShowOtherDates(checked ? MaterialCalendarView.SHOW_ALL : MaterialCalendarView.SHOW_NONE); } - @OnCheckedChanged(R.id.check_page_enabled) + @OnCheckedChanged(R.id.check_page_enabled) void onPageEnabledChecked(boolean checked) { widget.setPagingEnabled(checked); } - @OnClick(R.id.button_min_date) + @OnClick(R.id.button_previous) + void onPreviousClicked() { + widget.goToPrevious(); + } + + @OnClick(R.id.button_next) + void onNextClicked() { + widget.goToNext(); + } + + @OnClick(R.id.button_min_date) void onMinClicked() { showDatePickerDialog(this, widget.getMinimumDate(), new DatePickerDialog.OnDateSetListener() { @Override @@ -162,6 +185,37 @@ public void onClick(@NonNull DialogInterface dialog, int which) { .show(); } + @OnClick(R.id.button_set_width_height) + void onTileWidthHeightClicked() { + final LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.HORIZONTAL); + final NumberPicker pickerWidth = new NumberPicker(this); + pickerWidth.setMinValue(24); + pickerWidth.setMaxValue(64); + pickerWidth.setWrapSelectorWheel(false); + pickerWidth.setValue(currentTileWidth); + final NumberPicker pickerHeight = new NumberPicker(this); + pickerHeight.setMinValue(24); + pickerHeight.setMaxValue(64); + pickerHeight.setWrapSelectorWheel(false); + pickerHeight.setValue(currentTileHeight); + layout.addView(pickerWidth); + layout.addView(pickerHeight); + new AlertDialog.Builder(this) + .setView(layout) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(@NonNull DialogInterface dialog, int which) { + currentTileWidth = pickerWidth.getValue(); + currentTileHeight = pickerHeight.getValue(); + widget.setTileSize(-1); + widget.setTileWidthDp(currentTileWidth); + widget.setTileHeightDp(currentTileHeight); + } + }) + .show(); + } + @OnClick(R.id.button_clear_selection) void onClearSelection() { widget.clearSelection(); @@ -202,6 +256,17 @@ void onFirstDayOfWeekClicked() { widget.setFirstDayOfWeek(DAYS_OF_WEEK[index]); } + @OnClick(R.id.button_weeks) + public void onSetWeekMode() { + widget.setCalendarDisplayMode(CalendarMode.WEEKS); + } + + @OnClick(R.id.button_months) + public void onSetMonthMode() { + widget.setCalendarDisplayMode(CalendarMode.MONTHS); + } + + public static void showDatePickerDialog(Context context, CalendarDay day, DatePickerDialog.OnDateSetListener callback) { if (day == null) { diff --git a/sample/src/main/res/layout/activity_customization.xml b/sample/src/main/res/layout/activity_customization.xml index 3e221fcd..c3461c01 100644 --- a/sample/src/main/res/layout/activity_customization.xml +++ b/sample/src/main/res/layout/activity_customization.xml @@ -27,6 +27,7 @@ app:mcv_monthLabels="@array/custom_months" app:mcv_tileSize="36dp" app:mcv_firstDayOfWeek="thursday" + app:mcv_calendarMode="week" /> diff --git a/sample/src/main/res/layout/activity_dynamic_setters.xml b/sample/src/main/res/layout/activity_dynamic_setters.xml index 40846fc9..ad3fb8bd 100644 --- a/sample/src/main/res/layout/activity_dynamic_setters.xml +++ b/sample/src/main/res/layout/activity_dynamic_setters.xml @@ -46,6 +46,27 @@ + +