com.fourmob.datetimepicker.date.DatePicker.java Source code

Java tutorial

Introduction

Here is the source code for com.fourmob.datetimepicker.date.DatePicker.java

Source

/*******************************************************************************
 * Mirakel is an Android App for managing your ToDo-Lists
 *
 *   Copyright (c) 2013-2015 Anatolij Zelenin, Georg Semmler.
 *
 *       This program is free software: you can redistribute it and/or modify
 *       it under the terms of the GNU General Public License as published by
 *       the Free Software Foundation, either version 3 of the License, or
 *       any later version.
 *
 *       This program is distributed in the hope that it will be useful,
 *       but WITHOUT ANY WARRANTY; without even the implied warranty of
 *       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *       GNU General Public License for more details.
 *
 *       You should have received a copy of the GNU General Public License
 *       along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/

package com.fourmob.datetimepicker.date;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.ViewAnimator;

import com.fourmob.datetimepicker.Utils;
import com.google.common.base.Optional;
import com.nineoldandroids.animation.ObjectAnimator;

import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;

import java.util.HashSet;

import de.azapps.material_elements.utils.ThemeManager;
import de.azapps.mirakel.date_time.R;
import de.azapps.mirakel.helper.Helpers;

import static com.google.common.base.Optional.of;

public class DatePicker extends LinearLayout implements View.OnClickListener, DatePickerController {
    private static final int MAX_YEAR = 2050;
    private static final int MIN_YEAR = 1902;
    private static final int VIEW_DATE_PICKER_YEAR = 1;
    private static final int VIEW_DATE_PICKER_MONTH_DAY = 0;
    private static final String TAG = "DatePicker";
    private static DateTimeFormatter DAY_FORMAT = DateTimeFormat.forPattern("dd");
    private static DateTimeFormatter YEAR_FORMAT = DateTimeFormat.forPattern("yyyy");

    private int mMaxYear;
    private int mMinYear;
    private View layout;
    private TextView mSelectedMonthTextView;
    private TextView mDayOfWeekView;
    private LinearLayout mMonthAndDayView;
    private TextView mSelectedDayTextView;
    private TextView mYearView;
    private Context ctx;
    private String mDayPickerDescription;
    private ViewAnimator mAnimator;
    protected DayPickerView mDayPickerView;
    protected YearPickerView mYearPickerView;
    private String mYearPickerDescription;
    private Button mDoneButton;
    private Button mNoDateButton;
    private boolean yearSelected;
    protected LocalDate mCalendar;
    private boolean mDelayAnimation;
    private int mCurrentView;
    private int mWeekStart;
    private final HashSet<OnDateChangedListener> mListeners = new HashSet<>();
    protected OnDateSetListener mCallBack;

    public DatePicker(final Context context, final AttributeSet attrs) {
        super(context, attrs);
        final TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DatePicker, 0, 0);
        this.mCalendar = new LocalDate();
        try {
            mCalendar = mCalendar.withYear(a.getInt(R.styleable.DatePicker_initialYear, this.mCalendar.getYear()));
            mCalendar = mCalendar.withMonthOfYear(
                    a.getInt(R.styleable.DatePicker_initialMonth, this.mCalendar.getMonthOfYear()));
            mCalendar = mCalendar
                    .withDayOfMonth(a.getInt(R.styleable.DatePicker_initialDay, this.mCalendar.getDayOfMonth()));
            this.mMaxYear = a.getInt(R.styleable.DatePicker_maxYear, MAX_YEAR);
            this.mMinYear = a.getInt(R.styleable.DatePicker_minYear, MIN_YEAR);
        } finally {
            a.recycle();
        }
        setupView(context);
    }

    private void setupView(final Context context) {
        this.ctx = context;
        this.layout = View.inflate(context, R.layout.date_picker_view, this);
        initLayout();
        updateYearRange();
    }

    void setMaxYear(final int max) {
        if ((max >= this.mMinYear) && (max < MAX_YEAR)) {
            this.mMaxYear = max;
            updateYearRange();
        }
    }

    void setMinYear(final int min) {
        if ((min <= this.mMaxYear) && (min > MIN_YEAR)) {
            this.mMinYear = min;
            updateYearRange();
        }
    }

    private void updateYearRange() {
        if (this.mMaxYear <= this.mMinYear) {
            throw new IllegalArgumentException("Year end must be larger than year start");
        }
        if (this.mMaxYear > MAX_YEAR) {
            throw new IllegalArgumentException("max year end must < " + MAX_YEAR);
        }
        if (this.mMinYear < MIN_YEAR) {
            throw new IllegalArgumentException("min year end must > " + MIN_YEAR);
        }
        if (this.mDayPickerView != null) {
            this.mDayPickerView.onChange();
        }
    }

    @SuppressLint("NewApi")
    @Override
    public void onScreenStateChanged(final int screenState) {
        if (Build.VERSION.SDK_INT >= 16) {
            super.onScreenStateChanged(screenState);
        }
    }

    @Override
    public void onClick(final View v) {
        if (v.getId() == R.id.date_picker_year) {
            setCurrentView(VIEW_DATE_PICKER_YEAR);
            this.yearSelected = true;
        } else if (v.getId() == R.id.date_picker_month_and_day) {
            setCurrentView(VIEW_DATE_PICKER_MONTH_DAY);
            this.yearSelected = false;
        }
    }

    private void setCurrentView(final int currentView) {
        setCurrentView(currentView, false);
    }

    private void setCurrentView(final int currentView, final boolean forceRefresh) {
        final long timeInMillis = this.mCalendar.toDateTimeAtStartOfDay().getMillis();
        switch (currentView) {
        case VIEW_DATE_PICKER_MONTH_DAY:
            final ObjectAnimator monthDayAnim = Utils.getPulseAnimator(this.mMonthAndDayView, 0.9F, 1.05F);
            if (this.mDelayAnimation) {
                monthDayAnim.setStartDelay(500L);
                this.mDelayAnimation = false;
            }
            this.mDayPickerView.onDateChanged();
            if ((this.mCurrentView != currentView) || forceRefresh) {
                this.mMonthAndDayView.setSelected(true);
                this.mYearView.setSelected(false);
                this.mAnimator.setDisplayedChild(VIEW_DATE_PICKER_MONTH_DAY);
                this.mCurrentView = currentView;
            }
            monthDayAnim.start();
            final String monthDayDesc = DateUtils.formatDateTime(this.ctx, timeInMillis,
                    DateUtils.FORMAT_SHOW_DATE);
            this.mAnimator.setContentDescription(this.mDayPickerDescription + ": " + monthDayDesc);
            return;
        case VIEW_DATE_PICKER_YEAR:
            final ObjectAnimator yearAnim = Utils.getPulseAnimator(this.mYearView, 0.85F, 1.1F);
            if (this.mDelayAnimation) {
                yearAnim.setStartDelay(500L);
                this.mDelayAnimation = false;
            }
            this.mYearPickerView.onDateChanged();
            if ((this.mCurrentView != currentView) || forceRefresh) {
                this.mMonthAndDayView.setSelected(false);
                this.mYearView.setSelected(true);
                this.mAnimator.setDisplayedChild(VIEW_DATE_PICKER_YEAR);
                this.mCurrentView = currentView;
            }
            yearAnim.start();
            final String dayDesc = YEAR_FORMAT.print(timeInMillis);// format(Long.valueOf(timeInMillis));
            this.mAnimator.setContentDescription(this.mYearPickerDescription + ": " + dayDesc);
            return;
        default:
        }
    }

    private void initLayout() {
        final View datepicker_dialog = this.layout.findViewById(R.id.datepicker_dialog);
        this.mDayOfWeekView = (TextView) this.layout.findViewById(R.id.date_picker_header);
        this.mMonthAndDayView = (LinearLayout) this.layout.findViewById(R.id.date_picker_month_and_day);
        this.mMonthAndDayView.setOnClickListener(this);
        this.mSelectedMonthTextView = (TextView) this.layout.findViewById(R.id.date_picker_month);

        final ColorStateList selectorColorStates = getSelectorColorStates();

        this.mSelectedMonthTextView.setTextColor(selectorColorStates);
        this.mSelectedDayTextView = (TextView) this.layout.findViewById(R.id.date_picker_day);
        this.mSelectedDayTextView.setTextColor(selectorColorStates);
        this.mYearView = (TextView) this.layout.findViewById(R.id.date_picker_year);
        this.mYearView.setTextColor(selectorColorStates);
        this.mYearView.setOnClickListener(this);

        this.mDayPickerView = new DayPickerView(this.ctx, this);
        this.mYearPickerView = new YearPickerView(this.ctx, this);
        this.mDayPickerDescription = getResources().getString(R.string.day_picker_description);
        this.mYearPickerDescription = getResources().getString(R.string.year_picker_description);
        this.mAnimator = (ViewAnimator) this.layout.findViewById(R.id.animator);
        this.mAnimator.addView(this.mDayPickerView);
        this.mAnimator.addView(this.mYearPickerView);
        final AlphaAnimation inAlphaAnimation = new AlphaAnimation(0.0F, 1.0F);
        inAlphaAnimation.setDuration(300L);
        this.mAnimator.setInAnimation(inAlphaAnimation);
        final AlphaAnimation outAlphaAnimation = new AlphaAnimation(1.0F, 0.0F);
        outAlphaAnimation.setDuration(300L);
        this.mAnimator.setOutAnimation(outAlphaAnimation);
        this.mDoneButton = (Button) this.layout.findViewById(R.id.done);
        this.mDoneButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View view) {
                if (DatePicker.this.mCallBack != null) {
                    DatePicker.this.mCallBack.onDateSet(DatePicker.this, of(mCalendar));
                }
            }
        });
        this.mNoDateButton = (Button) this.layout.findViewById(R.id.dismiss);
        this.mNoDateButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View view) {
                if (DatePicker.this.mCallBack != null) {
                    DatePicker.this.mCallBack.onDateSet(DatePicker.this, Optional.<LocalDate>absent());
                }
            }
        });
        updateDisplay();
        final int currentView = VIEW_DATE_PICKER_MONTH_DAY;
        setCurrentView(currentView, true);
        final int listPosition = -1;
        final int listPositionOffset = 0;
        setScroll(listPosition, currentView, listPositionOffset);
        datepicker_dialog.setBackgroundColor(ThemeManager.getColor(R.attr.colorBackground));
        final View header = this.layout.findViewById(R.id.datepicker_header);
        header.setBackgroundColor(ThemeManager.getPrimaryThemeColor());
        if (this.mDayOfWeekView != null) {
            this.mDayOfWeekView.setBackgroundColor(ThemeManager.getPrimaryDarkThemeColor());
            this.mDayOfWeekView.setTextColor(ThemeManager.getColor(R.attr.colorControlNormal));
        }
        this.mNoDateButton.setTextColor(ThemeManager.getColor(R.attr.colorTextGrey));
        this.mDoneButton.setTextColor(ThemeManager.getColor(R.attr.colorTextGrey));
    }

    public static ColorStateList getSelectorColorStates() {
        final int[][] states = new int[][] { new int[] { android.R.attr.state_pressed },
                new int[] { android.R.attr.state_selected }, new int[] {} };

        final int[] colors = new int[] { ThemeManager.getPrimaryDarkThemeColor(),
                ThemeManager.getColor(R.attr.colorTextWhite), ThemeManager.getColor(R.attr.colorControlNormal) };

        return new ColorStateList(states, colors);
    }

    private void setScroll(final int listPosition, final int currentView, final int listPositionOffset) {
        if (listPosition != -1) {
            if (currentView == VIEW_DATE_PICKER_MONTH_DAY) {
                this.mDayPickerView.postSetSelection(listPosition);
            }
            if (currentView == VIEW_DATE_PICKER_YEAR) {
                this.mYearPickerView.postSetSelectionFromTop(listPosition, listPositionOffset);
            }
        }
    }

    private void updateDisplay() {
        if (this.mDayOfWeekView != null) {
            mSelectedMonthTextView.setText(new DateTimeFormatterBuilder().appendDayOfWeekShortText().toFormatter()
                    .print(mCalendar).toUpperCase(Helpers.getLocale(getContext())));
        }
        mSelectedMonthTextView.setText(new DateTimeFormatterBuilder().appendMonthOfYearShortText().toFormatter()
                .print(mCalendar).toUpperCase(Helpers.getLocale(getContext())));
        mSelectedDayTextView
                .setText(new DateTimeFormatterBuilder().appendDayOfMonth(0).toFormatter().print(mCalendar));
        mYearView.setText(new DateTimeFormatterBuilder().appendYear(4, 4).toFormatter().print(mCalendar));
        final long timeInMillis = this.mCalendar.toDateTimeAtStartOfDay().getMillis();
        final String desc = DateUtils.formatDateTime(this.ctx, timeInMillis, 24);
        this.mMonthAndDayView.setContentDescription(desc);
    }

    public LocalDate getDate() {
        return mCalendar;
    }

    public void setDate(final @NonNull LocalDate date) {
        this.mCalendar = date;
        updateDisplay();
        updatePickers();
        updateYearRange();
    }

    interface OnDateChangedListener {
        void onDateChanged();
    }

    public static class OnDateSetListener implements Parcelable {
        public void onDateSet(final @NonNull DatePicker picker, final @NonNull Optional<LocalDate> newDate) {
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(final Parcel dest, final int flags) {

        }

        public static final Parcelable.Creator<OnDateSetListener> CREATOR = new Parcelable.Creator<OnDateSetListener>() {
            @Override
            public OnDateSetListener createFromParcel(final Parcel source) {
                return new OnDateSetListener();
            }

            @Override
            public OnDateSetListener[] newArray(final int size) {
                return new OnDateSetListener[size];
            }
        };
    }

    @Override
    public void registerOnDateChangedListener(final OnDateChangedListener onDateChangedListener) {
        this.mListeners.add(onDateChangedListener);
    }

    @Override
    public int getFirstDayOfWeek() {
        return this.mWeekStart;
    }

    @Override
    public int getMaxYear() {
        return this.mMaxYear;
    }

    @Override
    public int getMinYear() {
        return this.mMinYear;
    }

    public void setOnDateSetListener(final OnDateSetListener dt) {
        this.mCallBack = dt;
    }

    @Override
    public SimpleMonthAdapter.CalendarDay getSelectedDay() {
        return new SimpleMonthAdapter.CalendarDay(this.mCalendar);
    }

    @Override
    public void onDayOfMonthSelected(final int year, final int month, final int day) {
        mCalendar = mCalendar.withYear(year).withMonthOfYear(month).withDayOfMonth(day);
        updatePickers();
        updateDisplay();
    }

    @Override
    public void onYearSelected(final int year) {
        adjustDayInMonthIfNeeded(this.mCalendar.getMonthOfYear(), year);
        this.mCalendar = mCalendar.withYear(year);
        updatePickers();
        setCurrentView(0);
        updateDisplay();
    }

    private void adjustDayInMonthIfNeeded(final int month, final int year) {
        final int currentDay = this.mCalendar.getDayOfMonth();
        final int day = Utils.getDaysInMonth(month, year);
        if (currentDay > day) {
            this.mCalendar = mCalendar.withDayOfMonth(day);
        }
    }

    private void updatePickers() {
        for (OnDateChangedListener mListener : this.mListeners) {
            mListener.onDateChanged();
        }
    }

    @Override
    protected void onConfigurationChanged(final Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        // on display rotate reload dialog
        final Parcelable yearState = this.mYearPickerView.onSaveInstanceState();
        final Parcelable monthState = this.mDayPickerView.onSaveInstanceState();
        initLayout();
        if (this.yearSelected) {
            setCurrentView(VIEW_DATE_PICKER_YEAR);
        }
        mYearPickerView.onRestoreInstanceState(yearState);
        mDayPickerView.onRestoreInstanceState(monthState);
    }

    private static final String CALENDAR_KEY = "calendar";
    private static final String WEEK_START_KEY = "week_start";
    private static final String YEAR_START_KEY = "minYear";
    private static final String YEAR_END_KEY = "maxYear";
    private static final String CURRENT_VIEW_KEY = "currentView";
    private static final String MOST_VISIBLE_POSITION_KEY = "mostVisiblePosition";
    private static final String LIST_OFFSET_KEY = "listOffset";
    private static final String PARENT_KEY = "Parent";
    private static final String CALLBACK_KEY = "callback";

    @Override
    public Parcelable onSaveInstanceState() {
        final Bundle b = new Bundle();
        b.putParcelable(PARENT_KEY, super.onSaveInstanceState());
        b.putSerializable(CALENDAR_KEY, mCalendar);
        b.putInt(WEEK_START_KEY, this.mWeekStart);
        b.putInt(YEAR_START_KEY, this.mMinYear);
        b.putInt(YEAR_END_KEY, this.mMaxYear);
        b.putInt(CURRENT_VIEW_KEY, this.mCurrentView);
        b.putParcelable(CALLBACK_KEY, this.mCallBack);

        int mostVisiblePosition = -1;
        if (this.mCurrentView == 0) {
            mostVisiblePosition = this.mDayPickerView.getMostVisiblePosition();
        }
        if (this.mCurrentView == 1) {
            mostVisiblePosition = this.mYearPickerView.getFirstVisiblePosition();
            b.putInt(LIST_OFFSET_KEY, this.mYearPickerView.getFirstPositionOffset());
        }
        b.putInt(MOST_VISIBLE_POSITION_KEY, mostVisiblePosition);
        return b;
    }

    @Override
    public void onRestoreInstanceState(final @Nullable Parcelable state) {
        if (state == null) {
            return;
        }
        // begin boilerplate code so parent classes can restore state
        final Bundle b = (Bundle) state;
        super.onRestoreInstanceState(b.getParcelable(PARENT_KEY));
        this.mWeekStart = b.getInt(WEEK_START_KEY);
        setMinYear(b.getInt(YEAR_START_KEY));
        setMaxYear(b.getInt(YEAR_END_KEY));
        final int currentView = b.getInt(CURRENT_VIEW_KEY);
        setCurrentView(currentView);
        setScroll(b.getInt(MOST_VISIBLE_POSITION_KEY, -1), currentView, b.getInt(LIST_OFFSET_KEY, 0));
        setDate((LocalDate) b.getSerializable(CALENDAR_KEY));
        mCallBack = b.getParcelable(CALLBACK_KEY);
    }

    public int getYear() {
        return this.mCalendar.getYear();
    }

    public int getMonth() {
        return this.mCalendar.getMonthOfYear();
    }

    public int getDay() {
        return this.mCalendar.getDayOfMonth();
    }

    public void hideNoDate() {
        this.mNoDateButton.setVisibility(View.GONE);
    }

}