package com.prolificinteractive.materialcalendarview; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.StateListDrawable; import android.graphics.drawable.shapes.OvalShape; import android.os.Build; import android.support.annotation.NonNull; import android.text.SpannableString; import android.text.Spanned; import android.view.Gravity; import android.view.View; import android.widget.CheckedTextView; import com.prolificinteractive.materialcalendarview.MaterialCalendarView.ShowOtherDates; import com.prolificinteractive.materialcalendarview.format.DayFormatter; import java.util.List; import static com.prolificinteractive.materialcalendarview.MaterialCalendarView.showDecoratedDisabled; import static com.prolificinteractive.materialcalendarview.MaterialCalendarView.showOtherMonths; import static com.prolificinteractive.materialcalendarview.MaterialCalendarView.showOutOfRange; /** * Display one day of a {@linkplain MaterialCalendarView} */ @SuppressLint("ViewConstructor") class DayView extends CheckedTextView { private CalendarDay date; private int selectionColor = Color.GRAY; private final int fadeTime; private Drawable customBackground = null; private Drawable selectionDrawable; private Drawable mCircleDrawable; private DayFormatter formatter = DayFormatter.DEFAULT; private boolean isInRange = true; private boolean isInMonth = true; private boolean isDecoratedDisabled = false; @ShowOtherDates private int showOtherDates = MaterialCalendarView.SHOW_DEFAULTS; public DayView(Context context, CalendarDay day) { super(context); fadeTime = getResources().getInteger(android.R.integer.config_shortAnimTime); setSelectionColor(this.selectionColor); setGravity(Gravity.CENTER); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { setTextAlignment(TEXT_ALIGNMENT_CENTER); } setDay(day); } public void setDay(CalendarDay date) { this.date = date; setText(getLabel()); } /** * Set the new label formatter and reformat the current label. This preserves current spans. * * @param formatter new label formatter */ public void setDayFormatter(DayFormatter formatter) { this.formatter = formatter == null ? DayFormatter.DEFAULT : formatter; CharSequence currentLabel = getText(); Object[] spans = null; if (currentLabel instanceof Spanned) { spans = ((Spanned) currentLabel).getSpans(0, currentLabel.length(), Object.class); } SpannableString newLabel = new SpannableString(getLabel()); if (spans != null) { for (Object span : spans) { newLabel.setSpan(span, 0, newLabel.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } setText(newLabel); } @NonNull public String getLabel() { return formatter.format(date); } public void setSelectionColor(int color) { this.selectionColor = color; regenerateBackground(); } /** * @param drawable custom selection drawable */ public void setSelectionDrawable(Drawable drawable) { if (drawable == null) { this.selectionDrawable = null; } else { this.selectionDrawable = drawable.getConstantState().newDrawable(getResources()); } regenerateBackground(); } /** * @param drawable background to draw behind everything else */ public void setCustomBackground(Drawable drawable) { if (drawable == null) { this.customBackground = null; } else { this.customBackground = drawable.getConstantState().newDrawable(getResources()); } invalidate(); } public CalendarDay getDate() { return date; } private void setEnabled() { boolean enabled = isInMonth && isInRange && !isDecoratedDisabled; super.setEnabled(isInRange && !isDecoratedDisabled); boolean showOtherMonths = showOtherMonths(showOtherDates); boolean showOutOfRange = showOutOfRange(showOtherDates) || showOtherMonths; boolean showDecoratedDisabled = showDecoratedDisabled(showOtherDates); boolean shouldBeVisible = enabled; if (!isInMonth && showOtherMonths) { shouldBeVisible = true; } if (!isInRange && showOutOfRange) { shouldBeVisible |= isInMonth; } if (isDecoratedDisabled && showDecoratedDisabled) { shouldBeVisible |= isInMonth && isInRange; } if (!isInMonth && shouldBeVisible) { setTextColor(getTextColors().getColorForState( new int[]{-android.R.attr.state_enabled}, Color.GRAY)); } setVisibility(shouldBeVisible ? View.VISIBLE : View.INVISIBLE); } protected void setupSelection(@ShowOtherDates int showOtherDates, boolean inRange, boolean inMonth) { this.showOtherDates = showOtherDates; this.isInMonth = inMonth; this.isInRange = inRange; setEnabled(); } private final Rect tempRect = new Rect(); private final Rect circleDrawableRect = new Rect(); @Override protected void onDraw(@NonNull Canvas canvas) { if (customBackground != null) { customBackground.setBounds(tempRect); customBackground.setState(getDrawableState()); customBackground.draw(canvas); } mCircleDrawable.setBounds(circleDrawableRect); super.onDraw(canvas); } private void regenerateBackground() { if (selectionDrawable != null) { setBackgroundDrawable(selectionDrawable); } else { mCircleDrawable = generateBackground(selectionColor, fadeTime, circleDrawableRect); setBackgroundDrawable(mCircleDrawable); } } 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, bounds)); } else { drawable.addState(new int[]{android.R.attr.state_pressed}, generateCircleDrawable(color)); } drawable.addState(new int[]{}, generateCircleDrawable(Color.TRANSPARENT)); return drawable; } private static Drawable generateCircleDrawable(final int color) { ShapeDrawable drawable = new ShapeDrawable(new OvalShape()); drawable.getPaint().setColor(color); return drawable; } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private static Drawable generateRippleDrawable(final int color, Rect bounds) { ColorStateList list = ColorStateList.valueOf(color); Drawable mask = generateCircleDrawable(Color.WHITE); 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; } /** * @param facade apply the facade to us */ void applyFacade(DayViewFacade facade) { this.isDecoratedDisabled = facade.areDaysDisabled(); setEnabled(); setCustomBackground(facade.getBackgroundDrawable()); setSelectionDrawable(facade.getSelectionDrawable()); // Facade has spans List<DayViewFacade.Span> spans = facade.getSpans(); if (!spans.isEmpty()) { String label = getLabel(); SpannableString formattedLabel = new SpannableString(getLabel()); for (DayViewFacade.Span span : spans) { formattedLabel.setSpan(span.span, 0, label.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } setText(formattedLabel); } // Reset in case it was customized previously else { 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); final int offset = Math.abs(height - width) / 2; // Lollipop platform bug. Circle drawable offset needs to be half of normal offset final int circleOffset = Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP ? offset / 2 : offset; if (width >= height) { tempRect.set(offset, 0, radius + offset, height); circleDrawableRect.set(circleOffset, 0, radius + circleOffset, height); } else { tempRect.set(0, offset, width, radius + offset); circleDrawableRect.set(0, circleOffset, width, radius + circleOffset); } } }