Skip to content

Commit 3e0a88d

Browse files
ldjcmudsn5ft
authored andcommitted
Create range highlight Grid for MaterialCalendar
PiperOrigin-RevId: 250312954
1 parent 23aed17 commit 3e0a88d

File tree

12 files changed

+259
-57
lines changed

12 files changed

+259
-57
lines changed

lib/java/com/google/android/material/picker/CalendarGridSelectors.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ static void colorCell(TextView cell, @StyleRes int style) {
4848
return;
4949
}
5050
MaterialShapeDrawable backgroundDrawable = new MaterialShapeDrawable();
51+
MaterialShapeDrawable shapeMask = new MaterialShapeDrawable();
5152

5253
TypedArray styleableArray =
5354
context.obtainStyledAttributes(style, R.styleable.MaterialCalendarDay);
@@ -62,20 +63,22 @@ static void colorCell(TextView cell, @StyleRes int style) {
6263
context, styleableArray, R.styleable.MaterialCalendarDay_itemStrokeColor);
6364
int strokeWidth =
6465
styleableArray.getDimensionPixelSize(R.styleable.MaterialCalendarDay_itemStrokeWidth, 0);
65-
backgroundDrawable.setShapeAppearanceModel(
66+
ShapeAppearanceModel cellShape =
6667
new ShapeAppearanceModel(
6768
context,
6869
styleableArray.getResourceId(R.styleable.MaterialCalendarDay_itemShapeAppearance, 0),
6970
styleableArray.getResourceId(
70-
R.styleable.MaterialCalendarDay_itemShapeAppearanceOverlay, 0)));
71+
R.styleable.MaterialCalendarDay_itemShapeAppearanceOverlay, 0));
72+
backgroundDrawable.setShapeAppearanceModel(cellShape);
73+
shapeMask.setShapeAppearanceModel(cellShape);
7174
styleableArray.recycle();
7275

7376
cell.setTextColor(textColor);
7477
backgroundDrawable.setFillColor(backgroundColor);
7578
backgroundDrawable.setStroke(strokeWidth, strokeColor);
7679
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
7780
ViewCompat.setBackground(
78-
cell, new RippleDrawable(textColor.withAlpha(30), backgroundDrawable, null));
81+
cell, new RippleDrawable(textColor.withAlpha(30), backgroundDrawable, shapeMask));
7982
} else {
8083
ViewCompat.setBackground(cell, backgroundDrawable);
8184
}

lib/java/com/google/android/material/picker/DateGridSelector.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import android.content.Context;
2121
import android.content.res.TypedArray;
22+
import android.graphics.Canvas;
2223
import android.os.Parcel;
2324
import android.os.Parcelable;
2425
import androidx.annotation.Nullable;
@@ -72,6 +73,11 @@ public Calendar getSelection() {
7273
return selectedItem;
7374
}
7475

76+
@Override
77+
public void onCalendarMonthDraw(Canvas canvas, MaterialCalendarGridView gridView) {
78+
// do nothing
79+
}
80+
7581
/* Parcelable interface */
7682

7783
/** {@link Parcelable.Creator} */

lib/java/com/google/android/material/picker/DateRangeGridSelector.java

Lines changed: 132 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,22 @@
1818
import com.google.android.material.R;
1919

2020
import android.content.Context;
21+
import android.content.res.ColorStateList;
2122
import android.content.res.TypedArray;
23+
import android.graphics.Canvas;
24+
import android.graphics.Paint;
2225
import android.os.Parcel;
2326
import android.os.Parcelable;
27+
import androidx.annotation.ColorInt;
2428
import androidx.annotation.Nullable;
2529
import androidx.annotation.RestrictTo;
2630
import androidx.annotation.RestrictTo.Scope;
31+
import androidx.annotation.StyleRes;
2732
import com.google.android.material.resources.MaterialAttributes;
33+
import com.google.android.material.resources.MaterialResources;
2834
import androidx.core.util.Pair;
2935
import android.text.format.DateUtils;
36+
import android.view.View;
3037
import android.widget.TextView;
3138
import java.util.Calendar;
3239

@@ -41,6 +48,38 @@ public class DateRangeGridSelector implements GridSelector<Pair<Calendar, Calend
4148

4249
private Calendar selectedStartItem = null;
4350
private Calendar selectedEndItem = null;
51+
private boolean stylesInitialized = false;
52+
private Paint rangeFillPaint;
53+
@ColorInt private int rangeFillColor;
54+
@StyleRes private int dayStyle;
55+
@StyleRes private int selectedStyle;
56+
@StyleRes private int todayStyle;
57+
58+
// The context is not available on construction, so we lazily initialize styles.
59+
private void initializeStyles(Context context) {
60+
if (stylesInitialized) {
61+
return;
62+
}
63+
stylesInitialized = true;
64+
65+
int rangeCalendarStyle =
66+
MaterialAttributes.resolveOrThrow(
67+
context,
68+
R.attr.materialDateRangePickerStyle,
69+
MaterialCalendar.class.getCanonicalName());
70+
71+
TypedArray calendarAttributes =
72+
context.obtainStyledAttributes(rangeCalendarStyle, R.styleable.MaterialCalendar);
73+
ColorStateList rangeFillColorList =
74+
MaterialResources.getColorStateList(
75+
context, calendarAttributes, R.styleable.MaterialCalendar_rangeFillColor);
76+
dayStyle = calendarAttributes.getResourceId(R.styleable.MaterialCalendar_dayStyle, 0);
77+
selectedStyle =
78+
calendarAttributes.getResourceId(R.styleable.MaterialCalendar_daySelectedStyle, 0);
79+
todayStyle = calendarAttributes.getResourceId(R.styleable.MaterialCalendar_dayTodayStyle, 0);
80+
rangeFillColor = rangeFillColorList.getDefaultColor();
81+
calendarAttributes.recycle();
82+
}
4483

4584
@Override
4685
public void select(Calendar selection) {
@@ -57,50 +96,114 @@ public void select(Calendar selection) {
5796
@Override
5897
public void drawCell(TextView cell, Calendar item) {
5998
Context context = cell.getContext();
60-
int rangeCalendarStyle =
61-
MaterialAttributes.resolveOrThrow(
62-
context,
63-
R.attr.materialDateRangePickerStyle,
64-
MaterialCalendar.class.getCanonicalName());
65-
99+
initializeStyles(context);
66100
int style;
67-
TypedArray stylesList =
68-
context.obtainStyledAttributes(rangeCalendarStyle, R.styleable.MaterialCalendar);
69101
if (item.equals(selectedStartItem) || item.equals(selectedEndItem)) {
70-
style = stylesList.getResourceId(R.styleable.MaterialCalendar_daySelectedStyle, 0);
102+
style = selectedStyle;
71103
} else if (DateUtils.isToday(item.getTimeInMillis())) {
72-
style = stylesList.getResourceId(R.styleable.MaterialCalendar_dayTodayStyle, 0);
104+
style = todayStyle;
73105
} else {
74-
style = stylesList.getResourceId(R.styleable.MaterialCalendar_dayStyle, 0);
106+
style = dayStyle;
75107
}
76-
stylesList.recycle();
77-
78108
CalendarGridSelectors.colorCell(cell, style);
79109
}
80110

111+
@Override
112+
public void onCalendarMonthDraw(Canvas canvas, MaterialCalendarGridView gridView) {
113+
initializeStyles(gridView.getContext());
114+
if (rangeFillPaint == null) {
115+
rangeFillPaint = new Paint();
116+
rangeFillPaint.setColor(rangeFillColor);
117+
}
118+
MonthAdapter monthAdapter = gridView.getAdapter();
119+
Calendar firstOfMonth = monthAdapter.getItem(monthAdapter.firstPositionInMonth());
120+
Calendar lastOfMonth = monthAdapter.getItem(monthAdapter.lastPositionInMonth());
121+
if (skipMonth(firstOfMonth, lastOfMonth, selectedStartItem, selectedEndItem)) {
122+
return;
123+
}
124+
125+
int firstHighlightPosition;
126+
int rangeHighlightStart;
127+
if (selectedStartItem.before(firstOfMonth)) {
128+
firstHighlightPosition = monthAdapter.firstPositionInMonth();
129+
rangeHighlightStart =
130+
firstHighlightPosition == 0
131+
? 0
132+
: gridView.getChildAt(firstHighlightPosition - 1).getRight();
133+
} else {
134+
firstHighlightPosition =
135+
monthAdapter.dayToPosition(selectedStartItem.get(Calendar.DAY_OF_MONTH));
136+
rangeHighlightStart = horizontalMidPoint(gridView.getChildAt(firstHighlightPosition));
137+
}
138+
139+
int lastHighlightPosition;
140+
int rangeHighlightEnd;
141+
if (selectedEndItem.after(lastOfMonth)) {
142+
lastHighlightPosition = monthAdapter.lastPositionInMonth();
143+
rangeHighlightEnd =
144+
lastHighlightPosition == gridView.getCount() - 1
145+
? gridView.getWidth()
146+
: gridView.getChildAt(lastHighlightPosition + 1).getLeft();
147+
} else {
148+
lastHighlightPosition =
149+
monthAdapter.dayToPosition(selectedEndItem.get(Calendar.DAY_OF_MONTH));
150+
rangeHighlightEnd = horizontalMidPoint(gridView.getChildAt(lastHighlightPosition));
151+
}
152+
153+
int firstRow = (int) monthAdapter.getItemId(firstHighlightPosition);
154+
int lastRow = (int) monthAdapter.getItemId(lastHighlightPosition);
155+
for (int row = firstRow; row <= lastRow; row++) {
156+
int firstPositionInRow = row * gridView.getNumColumns();
157+
int lastPositionInRow = firstPositionInRow + gridView.getNumColumns() - 1;
158+
View firstView = gridView.getChildAt(firstPositionInRow);
159+
int top = firstView.getTop();
160+
int bottom = firstView.getBottom();
161+
int left = firstPositionInRow > firstHighlightPosition ? 0 : rangeHighlightStart;
162+
int right =
163+
lastHighlightPosition > lastPositionInRow ? gridView.getWidth() : rangeHighlightEnd;
164+
canvas.drawRect(left, top, right, bottom, rangeFillPaint);
165+
}
166+
}
167+
81168
@Override
82169
@Nullable
83170
public Pair<Calendar, Calendar> getSelection() {
84-
Calendar start = getStart();
85-
Calendar end = getEnd();
86-
if (start == null || end == null) {
171+
if (selectedStartItem == null || selectedEndItem == null) {
87172
return null;
88173
}
89-
return new Pair<>(getStart(), getEnd());
174+
return new Pair<>(selectedStartItem, selectedEndItem);
90175
}
91176

92-
/** Returns a {@link java.util.Calendar} representing the start of the range */
177+
/** Returns a {@link java.util.Calendar} representing the start of the range. */
93178
@Nullable
94179
public Calendar getStart() {
180+
if (selectedStartItem == null || selectedEndItem == null) {
181+
return null;
182+
}
95183
return selectedStartItem;
96184
}
97185

98-
/** Returns a {@link java.util.Calendar} representing the end of the range */
186+
/** Returns a {@link java.util.Calendar} representing the end of the range. */
99187
@Nullable
100188
public Calendar getEnd() {
189+
if (selectedStartItem == null || selectedEndItem == null) {
190+
return null;
191+
}
101192
return selectedEndItem;
102193
}
103194

195+
private boolean skipMonth(
196+
Calendar firstOfMonth, Calendar lastOfMonth, Calendar startDay, Calendar endDay) {
197+
if (startDay == null || endDay == null) {
198+
return true;
199+
}
200+
return startDay.after(lastOfMonth) || endDay.before(firstOfMonth);
201+
}
202+
203+
private int horizontalMidPoint(View view) {
204+
return view.getLeft() + view.getWidth() / 2;
205+
}
206+
104207
/* Parcelable interface */
105208

106209
/** {@link Parcelable.Creator} */
@@ -111,6 +214,11 @@ public DateRangeGridSelector createFromParcel(Parcel source) {
111214
DateRangeGridSelector dateRangeGridSelector = new DateRangeGridSelector();
112215
dateRangeGridSelector.selectedStartItem = (Calendar) source.readSerializable();
113216
dateRangeGridSelector.selectedEndItem = (Calendar) source.readSerializable();
217+
dateRangeGridSelector.stylesInitialized = (Boolean) source.readValue(null);
218+
dateRangeGridSelector.rangeFillColor = source.readInt();
219+
dateRangeGridSelector.dayStyle = source.readInt();
220+
dateRangeGridSelector.selectedStyle = source.readInt();
221+
dateRangeGridSelector.todayStyle = source.readInt();
114222
return dateRangeGridSelector;
115223
}
116224

@@ -129,5 +237,10 @@ public int describeContents() {
129237
public void writeToParcel(Parcel dest, int flags) {
130238
dest.writeSerializable(selectedStartItem);
131239
dest.writeSerializable(selectedEndItem);
240+
dest.writeValue(stylesInitialized);
241+
dest.writeInt(rangeFillColor);
242+
dest.writeInt(dayStyle);
243+
dest.writeInt(selectedStyle);
244+
dest.writeInt(todayStyle);
132245
}
133246
}

lib/java/com/google/android/material/picker/GridSelector.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.google.android.material.picker;
1717

18+
import android.graphics.Canvas;
1819
import android.os.Parcelable;
1920
import androidx.annotation.Nullable;
2021
import androidx.annotation.RestrictTo;
@@ -60,4 +61,11 @@ public interface GridSelector<S> extends Parcelable {
6061
* @param item The {@link Calendar} returned from {@link MonthAdapter#getItem(int)}.
6162
*/
6263
void drawCell(TextView cell, Calendar item);
64+
65+
/**
66+
* Called after {@link
67+
* com.google.android.material.picker.MaterialCalendarGridView#onDraw(android.graphics.Canvas)} for
68+
* each month so selectors can draw on the canvas.
69+
*/
70+
void onCalendarMonthDraw(Canvas canvas, MaterialCalendarGridView gridView);
6371
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2019 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+
package com.google.android.material.picker;
17+
18+
import android.content.Context;
19+
import android.graphics.Canvas;
20+
import android.util.AttributeSet;
21+
import android.widget.GridView;
22+
import android.widget.ListAdapter;
23+
24+
final class MaterialCalendarGridView extends GridView {
25+
26+
public MaterialCalendarGridView(Context context) {
27+
this(context, null);
28+
}
29+
30+
public MaterialCalendarGridView(Context context, AttributeSet attrs) {
31+
this(context, attrs, 0);
32+
}
33+
34+
public MaterialCalendarGridView(Context context, AttributeSet attrs, int defStyleAttr) {
35+
super(context, attrs, defStyleAttr);
36+
}
37+
38+
@Override
39+
public MonthAdapter getAdapter() {
40+
return (MonthAdapter) super.getAdapter();
41+
}
42+
43+
@Override
44+
public final void setAdapter(ListAdapter adapter) {
45+
if (!(adapter instanceof MonthAdapter)) {
46+
throw new IllegalArgumentException(
47+
String.format(
48+
"%1$s must have its Adapter set to a %2$s",
49+
MaterialCalendarGridView.class.getCanonicalName(),
50+
MonthAdapter.class.getCanonicalName()));
51+
}
52+
super.setAdapter(adapter);
53+
}
54+
55+
@Override
56+
protected final void onDraw(Canvas canvas) {
57+
super.onDraw(canvas);
58+
getAdapter().gridSelector.onCalendarMonthDraw(canvas, this);
59+
}
60+
}

lib/java/com/google/android/material/picker/Month.java

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,14 @@
1717

1818
import android.os.Parcel;
1919
import android.os.Parcelable;
20-
import androidx.annotation.RestrictTo;
21-
import androidx.annotation.RestrictTo.Scope;
22-
import androidx.annotation.VisibleForTesting;
2320
import java.text.SimpleDateFormat;
2421
import java.util.Arrays;
2522
import java.util.Calendar;
2623
import java.util.GregorianCalendar;
2724
import java.util.Locale;
2825

29-
/**
30-
* Contains convenience operations for a month within a specific year.
31-
*
32-
* @hide
33-
*/
34-
@RestrictTo(Scope.LIBRARY_GROUP)
35-
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
36-
public final class Month implements Comparable<Month>, Parcelable {
26+
/** Contains convenience operations for a month within a specific year. */
27+
final class Month implements Comparable<Month>, Parcelable {
3728

3829
private final Calendar calendar;
3930
private static final SimpleDateFormat longNameFormat =
@@ -62,8 +53,7 @@ private Month(Calendar calendar) {
6253
* Calendar#JANUARY}
6354
* @return A Month object backed by a new {@link Calendar} instance
6455
*/
65-
@VisibleForTesting
66-
public static Month create(int year, int month) {
56+
static Month create(int year, int month) {
6757
Calendar calendar = Calendar.getInstance();
6858
calendar.clear();
6959
calendar.set(Calendar.YEAR, year);
@@ -72,7 +62,7 @@ public static Month create(int year, int month) {
7262
}
7363

7464
/** Returns the {@link Month} that contains today (as per {@link Calendar#getInstance()}. */
75-
public static Month today() {
65+
static Month today() {
7666
Calendar calendar = Calendar.getInstance();
7767
return Month.create(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH));
7868
}

0 commit comments

Comments
 (0)