com.kuloud.android.calendarview.CalendarView.java Source code

Java tutorial

Introduction

Here is the source code for com.kuloud.android.calendarview.CalendarView.java

Source

/*
 * Copyright (c) 2015 Kuloud
 *
 * 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.
 *
 */

package com.kuloud.android.calendarview;

import android.content.Context;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.ArrayRes;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.kuloud.android.calendarview.format.ArrayWeekDayFormatter;
import com.kuloud.android.calendarview.format.DateFormatTitleFormatter;
import com.kuloud.android.calendarview.format.DayFormatter;
import com.kuloud.android.calendarview.format.MonthArrayTitleFormatter;
import com.kuloud.android.calendarview.format.TitleFormatter;
import com.kuloud.android.calendarview.format.WeekDayFormatter;
import com.kuloud.android.common.util.CalendarUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import static com.kuloud.android.common.util.UIUtils.dp2px;
import static com.kuloud.android.common.util.UIUtils.getThemeAccentColor;

/**
 * <p>
 * This class is a calendar widget for displaying and selecting dates.
 * The range of dates supported by this calendar is configurable.
 * A user can select a date by taping on it and can page the calendar to a desired date.
 * </p>
 * <p>
 * By default, the range of dates shown is from 200 years in the past to 200 years in the future.
 * This can be extended or shortened by configuring the minimum and maximum dates.
 * </p>
 * <p>
 * When selecting a date out of range, or when the range changes so the selection becomes outside,
 * The date closest to the previous selection will become selected. This will also trigger the
 * {@linkplain OnDateChangedListener}
 * </p>
 */
public class CalendarView extends FrameLayout {

    /**
     * Default tile size in DIPs
     */
    public static final int DEFAULT_TILE_SIZE_DP = 44;

    private static final TitleFormatter DEFAULT_TITLE_FORMATTER = new DateFormatTitleFormatter();
    private final TitleChanger titleChanger;

    private View topbar;
    private TextView titleMonth;
    private TextView titlePercent;
    private final ViewPager pager;
    private final MonthPagerAdapter adapter;
    private final ArrayList<DayViewDecorator> dayViewDecorators = new ArrayList<>();
    private CalendarDay currentMonth;
    private CalendarDay minDate = null;
    private CalendarDay maxDate = null;
    private OnDateChangedListener listener;
    private final MonthView.Callbacks monthViewCallbacks = new MonthView.Callbacks() {
        @Override
        public void onDateChanged(CalendarDay date) {
            setSelectedDate(date);

            if (listener != null) {
                listener.onDateChanged(CalendarView.this, date);
            }
        }
    };
    private OnMonthChangedListener monthListener;
    private final ViewPager.OnPageChangeListener pageChangeListener = new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageSelected(int position) {
            titleChanger.setPreviousMonth(currentMonth);
            currentMonth = adapter.getItem(position);
            updateUi();

            if (monthListener != null) {
                monthListener.onMonthChanged(CalendarView.this, currentMonth);
            }
        }

        @Override
        public void onPageScrollStateChanged(int state) {
        }

        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        }
    };
    private int accentColor = 0;

    private LinearLayout root;

    public CalendarView(Context context) {
        this(context, null);
    }

    public CalendarView(Context context, AttributeSet attrs) {
        super(context, attrs);

        setClipChildren(false);
        setClipToPadding(false);

        pager = new ViewPager(getContext());

        setupChildren();

        titleChanger = new TitleChanger(topbar);
        titleChanger.setTitleFormatter(DEFAULT_TITLE_FORMATTER);
        adapter = new MonthPagerAdapter(this);
        pager.setAdapter(adapter);
        pager.setOnPageChangeListener(pageChangeListener);
        pager.setPageTransformer(false, new ViewPager.PageTransformer() {
            @Override
            public void transformPage(View page, float position) {
                position = (float) Math.sqrt(1 - Math.abs(position));
                page.setAlpha(position);
            }
        });

        adapter.setCallbacks(monthViewCallbacks);

        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CalendarView, 0, 0);
        try {

            int tileSize = a.getDimensionPixelSize(R.styleable.CalendarView_mcv_tileSize, -1);
            if (tileSize > 0) {
                setTileSize(tileSize);
            }

            setSelectionColor(
                    a.getColor(R.styleable.CalendarView_mcv_selectionColor, getThemeAccentColor(context)));

            CharSequence[] array = a.getTextArray(R.styleable.CalendarView_mcv_weekDayLabels);
            if (array != null) {
                setWeekDayFormatter(new ArrayWeekDayFormatter(array));
            }

            array = a.getTextArray(R.styleable.CalendarView_mcv_monthLabels);
            if (array != null) {
                setTitleFormatter(new MonthArrayTitleFormatter(array));
            }

            setShowOtherDates(a.getBoolean(R.styleable.CalendarView_mcv_showOtherDates, false));

            int firstDayOfWeek = a.getInt(R.styleable.CalendarView_mcv_firstDayOfWeek, -1);
            if (firstDayOfWeek < 0) {
                firstDayOfWeek = CalendarUtils.getInstance().getFirstDayOfWeek();
            }
            setFirstDayOfWeek(firstDayOfWeek);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (a != null) {
                a.recycle();
            }
        }

        currentMonth = CalendarDay.today();
        setCurrentDate(currentMonth);
    }

    private void setupChildren() {
        int tileSize = (int) dp2px(getContext(), DEFAULT_TILE_SIZE_DP);

        root = new LinearLayout(getContext());
        root.setOrientation(LinearLayout.VERTICAL);
        root.setClipChildren(false);
        root.setClipToPadding(false);
        LayoutParams p = new LayoutParams(tileSize * MonthView.DEFAULT_DAYS_IN_WEEK,
                tileSize * (MonthView.DEFAULT_MONTH_TILE_HEIGHT + 1));
        p.gravity = Gravity.CENTER;
        addView(root, p);

        topbar = LayoutInflater.from(getContext()).inflate(R.layout.calendar_toolbar, root, false);
        root.addView(topbar, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0, 1));

        titleMonth = (TextView) topbar.findViewById(R.id.tv_calendar_toolbar_month);
        titlePercent = (TextView) topbar.findViewById(R.id.tv_calendar_toolbar_percent);

        pager.setId(R.id.mcv_pager);
        pager.setOffscreenPageLimit(1);
        root.addView(pager,
                new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0, MonthView.DEFAULT_MONTH_TILE_HEIGHT));
    }

    /**
     * Sets the listener to be notified upon selected date changes.
     *
     * @param listener thing to be notified
     */
    public void setOnDateChangedListener(OnDateChangedListener listener) {
        this.listener = listener;
    }

    /**
     * Sets the listener to be notified upon month changes.
     *
     * @param listener thing to be notified
     */
    public void setOnMonthChangedListener(OnMonthChangedListener listener) {
        this.monthListener = listener;
    }

    private void updateUi() {
        titleChanger.change(currentMonth);
    }

    /**
     * @return the size of tiles in pixels
     */
    public int getTileSize() {
        return root.getLayoutParams().width / MonthView.DEFAULT_DAYS_IN_WEEK;
    }

    /**
     * Set the size of each tile that makes up the calendar.
     * Each day is 1 tile, so the widget is 7 tiles wide and 7 or 8 tiles tall
     * depending on the visibility of the {@link #topbar}.
     *
     * @param size the new size for each tile in pixels
     */
    public void setTileSize(int size) {
        LayoutParams p = new LayoutParams(size * MonthView.DEFAULT_DAYS_IN_WEEK,
                size * (MonthView.DEFAULT_MONTH_TILE_HEIGHT + 1));
        p.gravity = Gravity.CENTER;
        root.setLayoutParams(p);
    }

    /**
     * @param tileSizeDp the new size for each tile in dips
     * @see #setTileSize(int)
     */
    public void setTileSizeDp(int tileSizeDp) {
        setTileSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, tileSizeDp,
                getResources().getDisplayMetrics()));
    }

    /**
     * TODO should this be public?
     *
     * @return true if there is a future month that can be shown
     */
    private boolean canGoForward() {
        return pager.getCurrentItem() < (adapter.getCount() - 1);
    }

    /**
     * TODO should this be public?
     *
     * @return true if there is a previous month that can be shown
     */
    private boolean canGoBack() {
        return pager.getCurrentItem() > 0;
    }

    /**
     * @return the color used for the selection
     */
    public int getSelectionColor() {
        return accentColor;
    }

    /**
     * @param color The selection color
     */
    public void setSelectionColor(int color) {
        if (color == 0) {
            return;
        }
        accentColor = color;
        adapter.setSelectionColor(color);
        invalidate();
    }

    /**
     * @return the currently selected day, or null if no selection
     */
    public CalendarDay getSelectedDate() {
        return adapter.getSelectedDate();
    }

    /**
     * @param day a CalendarDay to set as selected. Null to clear selection
     */
    public void setSelectedDate(@Nullable CalendarDay day) {
        adapter.setSelectedDate(day);
        setCurrentDate(day);
    }

    /**
     * Clear the current selection
     */
    public void clearSelection() {
        setSelectedDate((CalendarDay) null);
    }

    /**
     * @param calendar a Calendar set to a day to select. Null to clear selection
     */
    public void setSelectedDate(@Nullable Calendar calendar) {
        setSelectedDate(CalendarDay.from(calendar));
    }

    /**
     * @param date a Date to set as selected. Null to clear selection
     */
    public void setSelectedDate(@Nullable Date date) {
        setSelectedDate(CalendarDay.from(date));
    }

    /**
     * @param calendar a Calendar set to a day to focus the calendar on. Null will do nothing
     */
    public void setCurrentDate(@Nullable Calendar calendar) {
        setCurrentDate(CalendarDay.from(calendar));
    }

    /**
     * @param date a Date to focus the calendar on. Null will do nothing
     */
    public void setCurrentDate(@Nullable Date date) {
        setCurrentDate(CalendarDay.from(date));
    }

    /**
     * @return The current month shown, will be set to first day of the month
     */
    public CalendarDay getCurrentDate() {
        return adapter.getItem(pager.getCurrentItem());
    }

    /**
     * @param day a CalendarDay to focus the calendar on. Null will do nothing
     */
    public void setCurrentDate(@Nullable CalendarDay day) {
        setCurrentDate(day, true);
    }

    /**
     * @param day             a CalendarDay to focus the calendar on. Null will do nothing
     * @param useSmoothScroll use smooth scroll when changing months.
     */
    public void setCurrentDate(@Nullable CalendarDay day, boolean useSmoothScroll) {
        if (day == null) {
            return;
        }
        int index = adapter.getIndexForDay(day);
        pager.setCurrentItem(index, useSmoothScroll);
        updateUi();
    }

    /**
     * @return the minimum selectable date for the calendar, if any
     */
    public CalendarDay getMinimumDate() {
        return minDate;
    }

    /**
     * @param calendar set the minimum selectable date, null for no minimum
     */
    public void setMinimumDate(@Nullable CalendarDay calendar) {
        minDate = calendar;
        setRangeDates(minDate, maxDate);
    }

    /**
     * @param calendar set the minimum selectable date, null for no minimum
     */
    public void setMinimumDate(@Nullable Calendar calendar) {
        setMinimumDate(CalendarDay.from(calendar));
    }

    /**
     * @param date set the minimum selectable date, null for no minimum
     */
    public void setMinimumDate(@Nullable Date date) {
        setMinimumDate(CalendarDay.from(date));
    }

    /**
     * @return the maximum selectable date for the calendar, if any
     */
    public CalendarDay getMaximumDate() {
        return maxDate;
    }

    /**
     * @param calendar set the maximum selectable date, null for no maximum
     */
    public void setMaximumDate(@Nullable CalendarDay calendar) {
        maxDate = calendar;
        setRangeDates(minDate, maxDate);
    }

    /**
     * @param calendar set the maximum selectable date, null for no maximum
     */
    public void setMaximumDate(@Nullable Calendar calendar) {
        setMaximumDate(CalendarDay.from(calendar));
    }

    /**
     * @param date set the maximum selectable date, null for no maximum
     */
    public void setMaximumDate(@Nullable Date date) {
        setMaximumDate(CalendarDay.from(date));
    }

    /**
     * Set a formatter for weekday labels.
     *
     * @param formatter the new formatter, null for default
     */
    public void setWeekDayFormatter(WeekDayFormatter formatter) {
        adapter.setWeekDayFormatter(formatter == null ? WeekDayFormatter.DEFAULT : formatter);
    }

    /**
     * Set a formatter for day labels.
     *
     * @param formatter the new formatter, null for default
     */
    public void setDayFormatter(DayFormatter formatter) {
        adapter.setDayFormatter(formatter == null ? DayFormatter.DEFAULT : formatter);
    }

    /**
     * Set a {@linkplain WeekDayFormatter}
     * with the provided week day labels
     *
     * @param weekDayLabels Labels to use for the days of the week
     * @see ArrayWeekDayFormatter
     * @see #setWeekDayFormatter(WeekDayFormatter)
     */
    public void setWeekDayLabels(CharSequence[] weekDayLabels) {
        setWeekDayFormatter(new ArrayWeekDayFormatter(weekDayLabels));
    }

    /**
     * @return true if days from previous or next months are shown, otherwise false.
     */
    public boolean getShowOtherDates() {
        return adapter.getShowOtherDates();
    }

    /**
     * By default, only days of one month are shown. If this is set true,
     * then days from the previous and next months are used to fill the empty space.
     * This also controls showing dates outside of the min-max range.
     *
     * @param showOtherDates show other days, default is false
     */
    public void setShowOtherDates(boolean showOtherDates) {
        adapter.setShowOtherDates(showOtherDates);
    }

    /**
     * Set a custom formatter for the month/year title
     *
     * @param titleFormatter new formatter to use, null to use default formatter
     */
    public void setTitleFormatter(TitleFormatter titleFormatter) {
        titleChanger.setTitleFormatter(titleFormatter == null ? DEFAULT_TITLE_FORMATTER : titleFormatter);
        updateUi();
    }

    /**
     * Set a {@linkplain TitleFormatter}
     * using the provided month labels
     *
     * @param monthLabels month labels to use
     * @see MonthArrayTitleFormatter
     * @see #setTitleFormatter(TitleFormatter)
     */
    public void setTitleMonths(CharSequence[] monthLabels) {
        setTitleFormatter(new MonthArrayTitleFormatter(monthLabels));
    }

    /**
     * Set a {@linkplain TitleFormatter}
     * using the provided month labels
     *
     * @param arrayRes String array resource of month labels to use
     * @see MonthArrayTitleFormatter
     * @see #setTitleFormatter(TitleFormatter)
     */
    public void setTitleMonths(@ArrayRes int arrayRes) {
        setTitleMonths(getResources().getTextArray(arrayRes));
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        SavedState ss = new SavedState(super.onSaveInstanceState());
        ss.color = getSelectionColor();
        ss.showOtherDates = getShowOtherDates();
        ss.minDate = getMinimumDate();
        ss.maxDate = getMaximumDate();
        ss.selectedDate = getSelectedDate();
        ss.firstDayOfWeek = getFirstDayOfWeek();
        ss.tileSizePx = getTileSize();
        return ss;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
        setSelectionColor(ss.color);
        setShowOtherDates(ss.showOtherDates);
        setRangeDates(ss.minDate, ss.maxDate);
        setSelectedDate(ss.selectedDate);
        setFirstDayOfWeek(ss.firstDayOfWeek);
        setTileSize(ss.tileSizePx);
    }

    @Override
    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
        //super.dispatchSaveInstanceState(container);
        super.dispatchFreezeSelfOnly(container);
    }

    @Override
    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
        //super.dispatchRestoreInstanceState(container);
        super.dispatchThawSelfOnly(container);
    }

    private void setRangeDates(CalendarDay min, CalendarDay max) {
        CalendarDay c = currentMonth;
        adapter.setRangeDates(min, max);
        currentMonth = c;
        int position = adapter.getIndexForDay(c);
        pager.setCurrentItem(position, false);
    }

    /**
     * @return The first day of the week as a {@linkplain Calendar} day constant.
     */
    public int getFirstDayOfWeek() {
        return adapter.getFirstDayOfWeek();
    }

    /**
     * Sets the first day of the week.
     * <p/>
     * Uses the java.util.Calendar day constants.
     *
     * @param day The first day of the week as a java.util.Calendar day constant.
     * @see java.util.Calendar
     */
    public void setFirstDayOfWeek(int day) {
        adapter.setFirstDayOfWeek(day);
    }

    /**
     * Add a collection of day decorators
     *
     * @param decorators decorators to add
     */
    public void addDecorators(Collection<? extends DayViewDecorator> decorators) {
        if (decorators == null) {
            return;
        }

        dayViewDecorators.addAll(decorators);
        adapter.setDecorators(dayViewDecorators);
    }

    /**
     * Add several day decorators
     *
     * @param decorators decorators to add
     */
    public void addDecorators(DayViewDecorator... decorators) {
        addDecorators(Arrays.asList(decorators));
    }

    /**
     * Add a day decorator
     *
     * @param decorator decorator to add
     */
    public void addDecorator(DayViewDecorator decorator) {
        if (decorator == null) {
            return;
        }
        dayViewDecorators.add(decorator);
        adapter.setDecorators(dayViewDecorators);
    }

    /**
     * Remove all decorators
     */
    public void removeDecorators() {
        dayViewDecorators.clear();
        adapter.setDecorators(dayViewDecorators);
    }

    /**
     * Remove a specific decorator instance. Same rules as {@linkplain List#remove(Object)}
     *
     * @param decorator decorator to remove
     */
    public void removeDecorator(DayViewDecorator decorator) {
        dayViewDecorators.remove(decorator);
        adapter.setDecorators(dayViewDecorators);
    }

    /**
     * Invalidate decorators after one has changed internally. That is, if a decorator mutates, you
     * should call this method to update the widget.
     */
    public void invalidateDecorators() {
        adapter.invalidateDecorators();
    }

    public static class SavedState extends BaseSavedState {

        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
        int color = 0;
        boolean showOtherDates = false;
        CalendarDay minDate = null;
        CalendarDay maxDate = null;
        CalendarDay selectedDate = null;
        int firstDayOfWeek = Calendar.SUNDAY;
        int tileSizePx = 0;

        SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            color = in.readInt();
            showOtherDates = in.readInt() == 1;
            ClassLoader loader = CalendarDay.class.getClassLoader();
            minDate = in.readParcelable(loader);
            maxDate = in.readParcelable(loader);
            selectedDate = in.readParcelable(loader);
            firstDayOfWeek = in.readInt();
            tileSizePx = in.readInt();
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeInt(color);
            out.writeInt(showOtherDates ? 1 : 0);
            out.writeParcelable(minDate, 0);
            out.writeParcelable(maxDate, 0);
            out.writeParcelable(selectedDate, 0);
            out.writeInt(firstDayOfWeek);
            out.writeInt(tileSizePx);
        }
    }

    private static class MonthPagerAdapter extends PagerAdapter {

        private final CalendarView view;
        private final LinkedList<MonthView> currentViews;
        private final ArrayList<CalendarDay> months;

        private MonthView.Callbacks callbacks = null;
        private Integer color = null;
        private Boolean showOtherDates = null;
        private CalendarDay minDate = null;
        private CalendarDay maxDate = null;
        private CalendarDay selectedDate = null;
        private WeekDayFormatter weekDayFormatter = WeekDayFormatter.DEFAULT;
        private DayFormatter dayFormatter = DayFormatter.DEFAULT;
        private List<DayViewDecorator> decorators = new ArrayList<>();
        private List<DecoratorResult> decoratorResults = null;
        private int firstDayOfTheWeek = Calendar.SUNDAY;

        private MonthPagerAdapter(CalendarView view) {
            this.view = view;
            currentViews = new LinkedList<>();
            months = new ArrayList<>();
            setRangeDates(null, null);
        }

        public void setDecorators(List<DayViewDecorator> decorators) {
            this.decorators = decorators;
            invalidateDecorators();
        }

        public void invalidateDecorators() {
            decoratorResults = new ArrayList<>();
            for (DayViewDecorator decorator : decorators) {
                DayViewFacade facade = new DayViewFacade();
                decorator.decorate(facade);
                if (facade.isDecorated()) {
                    decoratorResults.add(new DecoratorResult(decorator, facade));
                }
            }
            for (MonthView monthView : currentViews) {
                monthView.setDayViewDecorators(decoratorResults);
            }
        }

        @Override
        public int getCount() {
            return months.size();
        }

        public int getIndexForDay(CalendarDay day) {
            if (day == null) {
                return getCount() / 2;
            }
            if (minDate != null && day.isBefore(minDate)) {
                return 0;
            }
            if (maxDate != null && day.isAfter(maxDate)) {
                return getCount() - 1;
            }
            for (int i = 0; i < months.size(); i++) {
                CalendarDay month = months.get(i);
                if (day.getYear() == month.getYear() && day.getMonth() == month.getMonth()) {
                    return i;
                }
            }
            return getCount() / 2;
        }

        @Override
        public int getItemPosition(Object object) {
            if (!(object instanceof MonthView)) {
                return POSITION_NONE;
            }
            MonthView monthView = (MonthView) object;
            CalendarDay month = monthView.getMonth();
            if (month == null) {
                return POSITION_NONE;
            }
            int index = months.indexOf(month);
            if (index < 0) {
                return POSITION_NONE;
            }
            return index;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            CalendarDay month = months.get(position);
            MonthView monthView = new MonthView(container.getContext(), month, firstDayOfTheWeek);

            monthView.setWeekDayFormatter(weekDayFormatter);
            monthView.setDayFormatter(dayFormatter);
            monthView.setCallbacks(callbacks);
            if (color != null) {
                monthView.setSelectionColor(color);
            }
            if (showOtherDates != null) {
                monthView.setShowOtherDates(showOtherDates);
            }
            monthView.setMinimumDate(minDate);
            monthView.setMaximumDate(maxDate);
            monthView.setSelectedDate(selectedDate);

            container.addView(monthView);
            currentViews.add(monthView);

            monthView.setDayViewDecorators(decoratorResults);

            return monthView;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            MonthView monthView = (MonthView) object;
            currentViews.remove(monthView);
            container.removeView(monthView);
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        public void setCallbacks(MonthView.Callbacks callbacks) {
            this.callbacks = callbacks;
            for (MonthView monthView : currentViews) {
                monthView.setCallbacks(callbacks);
            }
        }

        public void setSelectionColor(int color) {
            this.color = color;
            for (MonthView monthView : currentViews) {
                monthView.setSelectionColor(color);
            }
        }

        public void setWeekDayFormatter(WeekDayFormatter formatter) {
            this.weekDayFormatter = formatter;
            for (MonthView monthView : currentViews) {
                monthView.setWeekDayFormatter(formatter);
            }
        }

        public void setDayFormatter(DayFormatter formatter) {
            this.dayFormatter = formatter;
            for (MonthView monthView : currentViews) {
                monthView.setDayFormatter(formatter);
            }
        }

        public boolean getShowOtherDates() {
            return showOtherDates;
        }

        public void setShowOtherDates(boolean show) {
            this.showOtherDates = show;
            for (MonthView monthView : currentViews) {
                monthView.setShowOtherDates(show);
            }
        }

        public void setRangeDates(CalendarDay min, CalendarDay max) {
            this.minDate = min;
            this.maxDate = max;
            for (MonthView monthView : currentViews) {
                monthView.setMinimumDate(min);
                monthView.setMaximumDate(max);
            }

            if (min == null) {
                Calendar worker = CalendarUtils.getInstance();
                worker.add(Calendar.YEAR, -200);
                min = CalendarDay.from(worker);
            }

            if (max == null) {
                Calendar worker = CalendarUtils.getInstance();
                worker.add(Calendar.YEAR, 200);
                max = CalendarDay.from(worker);
            }

            months.clear();

            Calendar worker = CalendarUtils.getInstance();
            min.copyToMonthOnly(worker);
            CalendarDay workingMonth = CalendarDay.from(worker);
            while (!max.isBefore(workingMonth)) {
                months.add(CalendarDay.from(worker));
                worker.add(Calendar.MONTH, 1);
                worker.set(Calendar.DAY_OF_MONTH, 1);
                workingMonth = CalendarDay.from(worker);
            }

            CalendarDay prevDate = selectedDate;
            notifyDataSetChanged();
            setSelectedDate(prevDate);
            if (prevDate != null) {
                if (!prevDate.equals(selectedDate)) {
                    callbacks.onDateChanged(selectedDate);
                }
            }
        }

        private CalendarDay getValidSelectedDate(CalendarDay date) {
            if (date == null) {
                return null;
            }
            if (minDate != null && minDate.isAfter(date)) {
                return minDate;
            }
            if (maxDate != null && maxDate.isBefore(date)) {
                return maxDate;
            }
            return date;
        }

        public CalendarDay getItem(int position) {
            return months.get(position);
        }

        public CalendarDay getSelectedDate() {
            return selectedDate;
        }

        public void setSelectedDate(@Nullable CalendarDay date) {
            CalendarDay prevDate = selectedDate;
            this.selectedDate = getValidSelectedDate(date);
            for (MonthView monthView : currentViews) {
                monthView.setSelectedDate(selectedDate);
            }

            if (date == null && prevDate != null) {
                callbacks.onDateChanged(null);
            }
        }

        public int getFirstDayOfWeek() {
            return firstDayOfTheWeek;
        }

        public void setFirstDayOfWeek(int day) {
            firstDayOfTheWeek = day;
            for (MonthView monthView : currentViews) {
                monthView.setFirstDayOfWeek(firstDayOfTheWeek);
            }
        }
    }
}