Java tutorial
/* * Copyright (C) 2015 Jonatan Ezequiel Salas * * 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.samsistemas.calendarview.widget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.PorterDuff; import android.graphics.Typeface; import android.os.Build; import android.support.annotation.ColorRes; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewConfigurationCompat; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Scroller; import android.widget.TextView; import com.samsistemas.calendarview.R; import com.samsistemas.calendarview.decor.DayDecorator; import com.samsistemas.calendarview.utility.CalendarUtility; import java.text.DateFormatSymbols; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Locale; /*** * Custom CalendarView class. * * @author jonatan.salas */ public class CalendarView extends LinearLayout { /** * Indicates that the CalendarView is in an idle, settled state. The current page * is fully in view and no animation is in progress. */ int SCROLL_STATE_IDLE = 0; /** * Indicates that the CalendarView is currently being dragged by the user. */ int SCROLL_STATE_DRAGGING = 1; /** * Indicates that the CalendarView is in the process of settling to a final position. */ int SCROLL_STATE_SETTLING = 2; boolean USE_CACHE = false; int MIN_DISTANCE_FOR_FLING = 25; // dips int DEFAULT_GUTTER_SIZE = 16; // dips int MIN_FLING_VELOCITY = 400; // dips /** * Sentinel value for no current active pointer. */ int INVALID_POINTER = -1; // If the CalendarView is at least this close to its final position, complete the scroll // on touch down and let the user interact with the content inside instead of // "catching" the flinging Calendar. int CLOSE_ENOUGH = 2; // dp private boolean mScrollingCacheEnabled; private boolean mIsBeingDragged; private boolean mIsUnableToDrag; private int mDefaultGutterSize; private int mTouchSlop; /** * Position of the last motion event. */ private float mLastMotionX; private float mLastMotionY; private float mInitialMotionX; private float mInitialMotionY; private Scroller mScroller; /** * ID of the active pointer. This is used to retain consistency during * drags/flings if multiple pointers are used. */ private int mActivePointerId = INVALID_POINTER; /** * Determines speed during touch scrolling */ private VelocityTracker mVelocityTracker; private int mMinimumVelocity; private int mMaximumVelocity; private int mFlingDistance; private int mCloseEnough; private int mScrollState = SCROLL_STATE_IDLE; private final Runnable mEndScrollRunnable = new Runnable() { public void run() { setScrollState(SCROLL_STATE_IDLE); } }; // Gesture Detector used to handle Swipe gestures. private GestureDetectorCompat mGestureDetector; private Context mContext; private View mView; private ImageView mNextButton; private ImageView mBackButton; //Listeners used by the Calendar... private OnDateClickListener mOnDateClickListener; private OnDateLongClickListener mOnDateLongClickListener; private OnMonthChangedListener mOnMonthChangedListener; private Calendar mCalendar; private Date mLastSelectedDay; //Customizable variables... private Typeface mTypeface; private int mDisabledDayBackgroundColor; private int mDisabledDayTextColor; private int mCalendarBackgroundColor; private int mSelectedDayBackground; private int mWeekLayoutBackgroundColor; private int mCalendarTitleBackgroundColor; private int mSelectedDayTextColor; private int mCalendarTitleTextColor; private int mDayOfWeekTextColor; private int mCurrentDayOfMonth; private int mWeekendColor; private int mWeekend; private List<DayDecorator> mDecoratorsList = null; private boolean mIsOverflowDateVisible = true; private int mFirstDayOfWeek = Calendar.SUNDAY; private int mCurrentMonthIndex = 0; // Day of weekend private int[] mTotalDayOfWeekend; // true for ordinary day, false for a weekend. private boolean mIsCommonDay; /** * Constructor with arguments. It receives a * Context used to get the resources. * * @param context - the context used to get the resources. */ public CalendarView(Context context) { this(context, null); //Initialize the gesture listener needed.. mGestureDetector = new GestureDetectorCompat(context, new CalendarGestureDetector()); } /** * Constructor with arguments. It receives a * Context used to get the resources. * * @param context - the context used to get the resources. * @param attrs - attribute set with custom styles. */ public CalendarView(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; //Initialize the gesture listener needed.. mGestureDetector = new GestureDetectorCompat(context, new CalendarGestureDetector()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) { if (isInEditMode()) { return; } } getAttributes(attrs); init(); } /*** * Method that gets and set the attributes of the CalendarView class. * * @param attrs - Attribute set object with custom values to be setted */ private void getAttributes(AttributeSet attrs) { final TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.MaterialCalendarView, 0, 0); final int white = ContextCompat.getColor(mContext, android.R.color.white); final int black = ContextCompat.getColor(mContext, android.R.color.black); final int dayDisableBackground = ContextCompat.getColor(mContext, R.color.day_disabled_background_color); final int dayDisableTextColor = ContextCompat.getColor(mContext, R.color.day_disabled_text_color); final int daySelectedBackground = ContextCompat.getColor(mContext, R.color.selected_day_background); final int dayCurrent = ContextCompat.getColor(mContext, R.color.current_day_of_month); final int weekendColor = ContextCompat.getColor(mContext, R.color.weekend_color); try { mCalendarBackgroundColor = a.getColor(R.styleable.MaterialCalendarView_calendarBackgroundColor, white); mCalendarTitleBackgroundColor = a.getColor(R.styleable.MaterialCalendarView_titleLayoutBackgroundColor, white); mCalendarTitleTextColor = a.getColor(R.styleable.MaterialCalendarView_calendarTitleTextColor, black); mWeekLayoutBackgroundColor = a.getColor(R.styleable.MaterialCalendarView_weekLayoutBackgroundColor, white); mDayOfWeekTextColor = a.getColor(R.styleable.MaterialCalendarView_dayOfWeekTextColor, black); mDisabledDayBackgroundColor = a.getColor(R.styleable.MaterialCalendarView_disabledDayBackgroundColor, dayDisableBackground); mDisabledDayTextColor = a.getColor(R.styleable.MaterialCalendarView_disabledDayTextColor, dayDisableTextColor); mSelectedDayBackground = a.getColor(R.styleable.MaterialCalendarView_selectedDayBackgroundColor, daySelectedBackground); mSelectedDayTextColor = a.getColor(R.styleable.MaterialCalendarView_selectedDayTextColor, white); mCurrentDayOfMonth = a.getColor(R.styleable.MaterialCalendarView_currentDayOfMonthColor, dayCurrent); mWeekendColor = a.getColor(R.styleable.MaterialCalendarView_weekendColor, weekendColor); mWeekend = a.getInteger(R.styleable.MaterialCalendarView_weekend, 0); } finally { if (null != a) { a.recycle(); } } } /** * This method init all necessary variables and Views that our Calendar is going to use. */ private void init() { mScroller = new Scroller(mContext, null); //Variables associated to handle touch events.. final ViewConfiguration configuration = ViewConfiguration.get(mContext); final float density = mContext.getResources().getDisplayMetrics().density; //Variables associated to Swipe.. mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density); mCloseEnough = (int) (CLOSE_ENOUGH * density); mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density); //Inflate current view.. mView = LayoutInflater.from(mContext).inflate(R.layout.material_calendar_with_title, this, true); //Get buttons for Calendar and set its listeners.. mBackButton = (ImageView) mView.findViewById(R.id.left_button); mNextButton = (ImageView) mView.findViewById(R.id.right_button); mBackButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mCurrentMonthIndex--; mCalendar = Calendar.getInstance(Locale.getDefault()); mCalendar.add(Calendar.MONTH, mCurrentMonthIndex); refreshCalendar(mCalendar); if (mOnMonthChangedListener != null) { mOnMonthChangedListener.onMonthChanged(mCalendar.getTime()); } } }); mNextButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mCurrentMonthIndex++; mCalendar = Calendar.getInstance(Locale.getDefault()); mCalendar.add(Calendar.MONTH, mCurrentMonthIndex); refreshCalendar(mCalendar); if (mOnMonthChangedListener != null) { mOnMonthChangedListener.onMonthChanged(mCalendar.getTime()); } } }); setFirstDayOfWeek(Calendar.SUNDAY); refreshCalendar(Calendar.getInstance(getLocale())); } /** * Display calendar title with next previous month button */ private void initTitleLayout() { View titleLayout = mView.findViewById(R.id.title_layout); titleLayout.setBackgroundColor(mCalendarTitleBackgroundColor); TextView dateTitle = (TextView) mView.findViewById(R.id.dateTitle); String dateText = CalendarUtility.getCurrentMonth(mCurrentMonthIndex).toUpperCase(Locale.getDefault()) + " " + getCurrentYear(); dateTitle.setText(dateText); dateTitle.setTextColor(mCalendarTitleTextColor); if (null != getTypeface()) { dateTitle.setTypeface(getTypeface(), Typeface.BOLD); } } /** * Initialize the calendar week layout, considers start day */ private void initWeekLayout() { TextView dayOfWeek; String dayOfTheWeekString; //Setting background color white View titleLayout = mView.findViewById(R.id.week_layout); titleLayout.setBackgroundColor(mWeekLayoutBackgroundColor); final String[] weekDaysArray = new DateFormatSymbols(getLocale()).getShortWeekdays(); for (int i = 1; i < weekDaysArray.length; i++) { dayOfTheWeekString = weekDaysArray[i]; int length = dayOfTheWeekString.length() < 3 ? dayOfTheWeekString.length() : 3; dayOfTheWeekString = dayOfTheWeekString.substring(0, length).toUpperCase(); dayOfWeek = (TextView) mView.findViewWithTag( mContext.getString(R.string.day_of_week) + CalendarUtility.getWeekIndex(i, mCalendar)); dayOfWeek.setText(dayOfTheWeekString); mIsCommonDay = true; if (totalDayOfWeekend().length != 0) { for (int weekend : totalDayOfWeekend()) { if (i == weekend) { dayOfWeek.setTextColor(mWeekendColor); mIsCommonDay = false; } } } if (mIsCommonDay) { dayOfWeek.setTextColor(mDayOfWeekTextColor); } if (null != getTypeface()) { dayOfWeek.setTypeface(getTypeface()); } } } /** * This method prepare and populate the days in the CalendarView */ private void setDaysInCalendar() { Calendar calendar = Calendar.getInstance(getLocale()); calendar.setTime(mCalendar.getTime()); calendar.set(Calendar.DAY_OF_MONTH, 1); calendar.setFirstDayOfWeek(mFirstDayOfWeek); int firstDayOfMonth = calendar.get(Calendar.DAY_OF_WEEK); // Calculate dayOfMonthIndex int dayOfMonthIndex = CalendarUtility.getWeekIndex(firstDayOfMonth, calendar); int actualMaximum = calendar.getActualMaximum(Calendar.DAY_OF_MONTH); final Calendar startCalendar = (Calendar) calendar.clone(); //Add required number of days startCalendar.add(Calendar.DATE, -(dayOfMonthIndex - 1)); int monthEndIndex = 42 - (actualMaximum + dayOfMonthIndex - 1); DayView dayView; ViewGroup dayOfMonthContainer; for (int i = 1; i < 43; i++) { dayOfMonthContainer = (ViewGroup) mView .findViewWithTag(mContext.getString(R.string.day_of_month_container) + i); dayView = (DayView) mView.findViewWithTag(mContext.getString(R.string.day_of_month_text) + i); if (dayView == null) continue; //Apply the default styles dayOfMonthContainer.setOnClickListener(null); dayView.bind(startCalendar.getTime(), getDecoratorsList()); dayView.setVisibility(View.VISIBLE); if (null != getTypeface()) { dayView.setTypeface(getTypeface()); } if (CalendarUtility.isSameMonth(calendar, startCalendar)) { dayOfMonthContainer.setOnClickListener(onDayOfMonthClickListener); dayOfMonthContainer.setOnLongClickListener(onDayOfMonthLongClickListener); dayView.setBackgroundColor(mCalendarBackgroundColor); mIsCommonDay = true; if (totalDayOfWeekend().length != 0) { for (int weekend : totalDayOfWeekend()) { if (startCalendar.get(Calendar.DAY_OF_WEEK) == weekend) { dayView.setTextColor(mWeekendColor); mIsCommonDay = false; } } } if (mIsCommonDay) { dayView.setTextColor(mDayOfWeekTextColor); } } else { dayView.setBackgroundColor(mDisabledDayBackgroundColor); dayView.setTextColor(mDisabledDayTextColor); if (!isOverflowDateVisible()) dayView.setVisibility(View.GONE); else if (i >= 36 && ((float) monthEndIndex / 7.0f) >= 1) { dayView.setVisibility(View.GONE); } } dayView.decorate(); //Set the current day color if (mCalendar.get(Calendar.MONTH) == startCalendar.get(Calendar.MONTH)) setCurrentDay(mCalendar.getTime()); startCalendar.add(Calendar.DATE, 1); dayOfMonthIndex++; } // If the last week row has no visible days, hide it or show it in case ViewGroup weekRow = (ViewGroup) mView.findViewWithTag("weekRow6"); dayView = (DayView) mView.findViewWithTag("dayOfMonthText36"); if (dayView.getVisibility() != VISIBLE) { weekRow.setVisibility(GONE); } else { weekRow.setVisibility(VISIBLE); } } private void clearDayOfTheMonthStyle(Date currentDate) { if (currentDate != null) { final Calendar calendar = CalendarUtility.getTodayCalendar(mContext, mFirstDayOfWeek); calendar.setFirstDayOfWeek(mFirstDayOfWeek); calendar.setTime(currentDate); final DayView dayView = findViewByCalendar(calendar); dayView.setBackgroundColor(mCalendarBackgroundColor); mIsCommonDay = true; if (totalDayOfWeekend().length != 0) { for (int weekend : totalDayOfWeekend()) { if (calendar.get(Calendar.DAY_OF_WEEK) == weekend) { dayView.setTextColor(mWeekendColor); mIsCommonDay = false; } } } if (mIsCommonDay) { dayView.setTextColor(mDayOfWeekTextColor); } } } public DayView findViewByDate(@NonNull Date dateToFind) { final Calendar calendar = Calendar.getInstance(getLocale()); calendar.setTime(dateToFind); return (DayView) getView(mContext.getString(R.string.day_of_month_text), calendar); } private DayView findViewByCalendar(@NonNull Calendar calendarToFind) { return (DayView) getView(mContext.getString(R.string.day_of_month_text), calendarToFind); } private int getDayIndexByDate(Calendar calendar) { int monthOffset = CalendarUtility.getMonthOffset(calendar, mFirstDayOfWeek); int currentDay = calendar.get(Calendar.DAY_OF_MONTH); return currentDay + monthOffset; } private View getView(String key, Calendar currentCalendar) { final int index = getDayIndexByDate(currentCalendar); return mView.findViewWithTag(key + index); } public void refreshCalendar(Calendar calendar) { mCalendar = calendar; mCalendar.setFirstDayOfWeek(mFirstDayOfWeek); initTitleLayout(); setTotalDayOfWeekend(); initWeekLayout(); setDaysInCalendar(); } private void setTotalDayOfWeekend() { int[] weekendDay = new int[Integer.bitCount(mWeekend)]; char days[] = Integer.toBinaryString(mWeekend).toCharArray(); int day = 1; int index = 0; for (int i = days.length - 1; i >= 0; i--) { if (days[i] == '1') { weekendDay[index] = day; index++; } day++; } mTotalDayOfWeekend = weekendDay; } private int[] totalDayOfWeekend() { return mTotalDayOfWeekend; } public void setCurrentDay(@NonNull Date todayDate) { final Calendar calendar = Calendar.getInstance(getLocale()); calendar.setTime(todayDate); if (CalendarUtility.isToday(calendar)) { final DayView dayOfMonth = findViewByCalendar(calendar); dayOfMonth.setTextColor(mCurrentDayOfMonth); dayOfMonth.setBackgroundColor(mSelectedDayBackground); } } public void setDateAsSelected(Date currentDate) { final Calendar currentCalendar = CalendarUtility.getTodayCalendar(mContext, mFirstDayOfWeek); currentCalendar.setFirstDayOfWeek(mFirstDayOfWeek); currentCalendar.setTime(currentDate); // Clear previous marks clearDayOfTheMonthStyle(mLastSelectedDay); // Store current values as last values setLastSelectedDay(currentDate); // Mark current day as selected DayView view = findViewByCalendar(currentCalendar); view.setBackgroundColor(mSelectedDayBackground); view.setTextColor(mSelectedDayTextColor); } private OnLongClickListener onDayOfMonthLongClickListener = new OnLongClickListener() { @Override public boolean onLongClick(View view) { // Extract day selected ViewGroup dayOfMonthContainer = (ViewGroup) view; String tagId = (String) dayOfMonthContainer.getTag(); tagId = tagId.substring(mContext.getString(R.string.day_of_month_container).length(), tagId.length()); final TextView dayOfMonthText = (TextView) view .findViewWithTag(mContext.getString(R.string.day_of_month_text) + tagId); // Fire event final Calendar calendar = Calendar.getInstance(); calendar.setFirstDayOfWeek(mFirstDayOfWeek); calendar.setTime(mCalendar.getTime()); calendar.set(Calendar.DAY_OF_MONTH, Integer.valueOf(dayOfMonthText.getText().toString())); setDateAsSelected(calendar.getTime()); //Set the current day color setCurrentDay(mCalendar.getTime()); if (mOnDateLongClickListener != null) { mOnDateLongClickListener.onDateLongClick(calendar.getTime()); } return false; } }; private OnClickListener onDayOfMonthClickListener = new OnClickListener() { @Override public void onClick(View view) { // Extract day selected ViewGroup dayOfMonthContainer = (ViewGroup) view; String tagId = (String) dayOfMonthContainer.getTag(); tagId = tagId.substring(mContext.getString(R.string.day_of_month_container).length(), tagId.length()); final TextView dayOfMonthText = (TextView) view .findViewWithTag(mContext.getString(R.string.day_of_month_text) + tagId); // Fire event final Calendar calendar = Calendar.getInstance(); calendar.setFirstDayOfWeek(mFirstDayOfWeek); calendar.setTime(mCalendar.getTime()); calendar.set(Calendar.DAY_OF_MONTH, Integer.valueOf(dayOfMonthText.getText().toString())); setDateAsSelected(calendar.getTime()); //Set the current day color setCurrentDay(mCalendar.getTime()); if (mOnDateClickListener != null) { mOnDateClickListener.onDateClick(calendar.getTime()); } } }; private boolean isGutterDrag(float x, float dx) { return (x < mDefaultGutterSize && dx > 0) || (x > getWidth() - mDefaultGutterSize && dx < 0); } private void setScrollingCacheEnabled(boolean enabled) { if (mScrollingCacheEnabled != enabled) { mScrollingCacheEnabled = enabled; if (USE_CACHE) { final int size = getChildCount(); for (int i = 0; i < size; ++i) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { child.setDrawingCacheEnabled(enabled); } } } } } private void onSecondaryPointerUp(MotionEvent ev) { final int pointerIndex = MotionEventCompat.getActionIndex(ev); final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); if (pointerId == mActivePointerId) { // This was our active pointer going up. Choose a new // active pointer and adjust accordingly. final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex); mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); if (mVelocityTracker != null) { mVelocityTracker.clear(); } } } private void setScrollState(int newState) { if (mScrollState == newState) { return; } mScrollState = newState; } private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(disallowIntercept); } } /** * Tests scroll ability within child views of v given a delta of dx. * * @param v View to test for horizontal scroll ability * @param checkV Whether the view v passed should itself be checked for scrollability (true), * or just its children (false). * @param dx Delta scrolled in pixels * @param x X coordinate of the active touch point * @param y Y coordinate of the active touch point * @return true if child views of v can be scrolled by delta of dx. */ protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { if (v instanceof ViewGroup) { final ViewGroup group = (ViewGroup) v; final int scrollX = v.getScrollX(); final int scrollY = v.getScrollY(); final int count = group.getChildCount(); // Count backwards - let topmost views consume scroll distance first. for (int i = count - 1; i >= 0; i--) { // This will not work for transformed views in Honeycomb+ final View child = group.getChildAt(i); if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && canScroll(child, true, dx, x + scrollX - child.getLeft(), y + scrollY - child.getTop())) { return true; } } } return checkV && ViewCompat.canScrollHorizontally(v, -dx); } private void completeScroll(boolean postEvents) { boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING; if (needPopulate) { // Done with scroll, no longer want to cache view drawing. setScrollingCacheEnabled(false); mScroller.abortAnimation(); int oldX = getScrollX(); int oldY = getScrollY(); int x = mScroller.getCurrX(); int y = mScroller.getCurrY(); if (oldX != x || oldY != y) { scrollTo(x, y); } } if (needPopulate) { if (postEvents) { ViewCompat.postOnAnimation(this, mEndScrollRunnable); } else { mEndScrollRunnable.run(); } } } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (null != mGestureDetector) { mGestureDetector.onTouchEvent(ev); super.dispatchTouchEvent(ev); return true; } return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { /* * This method JUST determines whether we want to intercept the motion. * If we return true, onMotionEvent will be called and we do the actual * scrolling there. */ final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; // Always take care of the touch gesture being complete. if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { // Release the drag. mIsBeingDragged = false; mIsUnableToDrag = false; mActivePointerId = INVALID_POINTER; if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } return false; } // Nothing more to do here if we have decided whether or not we // are dragging. if (action != MotionEvent.ACTION_DOWN) { if (mIsBeingDragged) { return true; } if (mIsUnableToDrag) { return false; } } switch (action) { case MotionEvent.ACTION_MOVE: { /* * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check * whether the user has moved far enough from his original down touch. */ /* * Locally do absolute value. mLastMotionY is set to the y value * of the down event. */ final int activePointerId = mActivePointerId; if (activePointerId == INVALID_POINTER) { // If we don't have a valid id, the touch down wasn't on content. break; } final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId); final float x = MotionEventCompat.getX(ev, pointerIndex); final float dx = x - mLastMotionX; final float xDiff = Math.abs(dx); final float y = MotionEventCompat.getY(ev, pointerIndex); final float yDiff = Math.abs(y - mInitialMotionY); if (dx != 0 && !isGutterDrag(mLastMotionX, dx) && canScroll(this, false, (int) dx, (int) x, (int) y)) { // Nested view has scrollable area under this point. Let it be handled there. mLastMotionX = x; mLastMotionY = y; mIsUnableToDrag = true; return false; } if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) { mIsBeingDragged = true; requestParentDisallowInterceptTouchEvent(true); setScrollState(SCROLL_STATE_DRAGGING); mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop : mInitialMotionX - mTouchSlop; mLastMotionY = y; setScrollingCacheEnabled(true); } else if (yDiff > mTouchSlop) { // The finger has moved enough in the vertical // direction to be counted as a drag... abort // any attempt to drag horizontally, to work correctly // with children that have scrolling containers. mIsUnableToDrag = true; } break; } case MotionEvent.ACTION_DOWN: { /* * Remember location of down touch. * ACTION_DOWN always refers to pointer index 0. */ mLastMotionX = mInitialMotionX = ev.getX(); mLastMotionY = mInitialMotionY = ev.getY(); mActivePointerId = MotionEventCompat.getPointerId(ev, 0); mIsUnableToDrag = false; mScroller.computeScrollOffset(); if (mScrollState == SCROLL_STATE_SETTLING && Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) { mIsBeingDragged = true; requestParentDisallowInterceptTouchEvent(true); setScrollState(SCROLL_STATE_DRAGGING); } else { completeScroll(false); mIsBeingDragged = false; } break; } case MotionEventCompat.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; } if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); /* * The only time we want to intercept motion events is if we are in the * drag mode. */ return mIsBeingDragged; } @Override public boolean onTouchEvent(MotionEvent event) { return mGestureDetector.onTouchEvent(event); } /** * CalendarGestureDetector class used to detect Swipes gestures. * * @author jonatan.salas */ public class CalendarGestureDetector extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent e) { return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { try { float diffY = e2.getY() - e1.getY(); float diffX = e2.getX() - e1.getX(); if (Math.abs(diffX) > Math.abs(diffY)) { if (Math.abs(diffX) > mTouchSlop && Math.abs(velocityX) > mMinimumVelocity && Math.abs(velocityX) < mMaximumVelocity) { if (e2.getX() - e1.getX() > mFlingDistance) { mCurrentMonthIndex--; mCalendar = Calendar.getInstance(Locale.getDefault()); mCalendar.add(Calendar.MONTH, mCurrentMonthIndex); refreshCalendar(mCalendar); if (mOnMonthChangedListener != null) { mOnMonthChangedListener.onMonthChanged(mCalendar.getTime()); } } else if (e1.getX() - e2.getX() > mFlingDistance) { mCurrentMonthIndex++; mCalendar = Calendar.getInstance(Locale.getDefault()); mCalendar.add(Calendar.MONTH, mCurrentMonthIndex); refreshCalendar(mCalendar); if (mOnMonthChangedListener != null) { mOnMonthChangedListener.onMonthChanged(mCalendar.getTime()); } } } } return true; } catch (Exception ex) { ex.printStackTrace(); } return super.onFling(e1, e2, velocityX, velocityY); } } /** * Interface that define a method to * implement to handle a selected date event, * * @author jonatan.salas */ public interface OnDateClickListener { /** * Method that lets you handle * when a user touches a particular date. * * @param selectedDate - the date selected by the user. */ void onDateClick(@NonNull Date selectedDate); } /** * Interface that define a method to * implement to handle a selected date event, * * @author jonatan.salas */ public interface OnDateLongClickListener { /** * Method that lets you handle * when a user touches a particular date. * * @param selectedDate - the date selected by the user. */ void onDateLongClick(@NonNull Date selectedDate); } /** * Interface that define a method to implement to handle * a month changed event. * * @author jonatan.salas */ public interface OnMonthChangedListener { /** * Method that lets you handle when a goes to back or next * month. * * @param monthDate - the date with the current month */ void onMonthChanged(@NonNull Date monthDate); } /** * Attributes setters and getters. */ public void setOnDateClickListener(OnDateClickListener onDateClickListener) { this.mOnDateClickListener = onDateClickListener; } public void setOnDateLongClickListener(OnDateLongClickListener onDateLongClickListener) { this.mOnDateLongClickListener = onDateLongClickListener; } public void setOnMonthChangedListener(OnMonthChangedListener onMonthChangedListener) { this.mOnMonthChangedListener = onMonthChangedListener; } private void setLastSelectedDay(Date lastSelectedDay) { this.mLastSelectedDay = lastSelectedDay; } public void setTypeface(Typeface typeface) { this.mTypeface = typeface; } public void setDecoratorsList(List<DayDecorator> decoratorsList) { this.mDecoratorsList = decoratorsList; } public void setIsOverflowDateVisible(boolean isOverflowDateVisible) { this.mIsOverflowDateVisible = isOverflowDateVisible; } public void setFirstDayOfWeek(int firstDayOfWeek) { this.mFirstDayOfWeek = firstDayOfWeek; } public void setDisabledDayBackgroundColor(int disabledDayBackgroundColor) { this.mDisabledDayBackgroundColor = disabledDayBackgroundColor; } public void setDisabledDayTextColor(int disabledDayTextColor) { this.mDisabledDayTextColor = disabledDayTextColor; } public void setCalendarBackgroundColor(int calendarBackgroundColor) { this.mCalendarBackgroundColor = calendarBackgroundColor; } public void setSelectedDayBackground(int selectedDayBackground) { this.mSelectedDayBackground = selectedDayBackground; } public void setWeekLayoutBackgroundColor(int weekLayoutBackgroundColor) { this.mWeekLayoutBackgroundColor = weekLayoutBackgroundColor; } public void setCalendarTitleBackgroundColor(int calendarTitleBackgroundColor) { this.mCalendarTitleBackgroundColor = calendarTitleBackgroundColor; } public void setSelectedDayTextColor(int selectedDayTextColor) { this.mSelectedDayTextColor = selectedDayTextColor; } public void setCalendarTitleTextColor(int calendarTitleTextColor) { this.mCalendarTitleTextColor = calendarTitleTextColor; } public void setDayOfWeekTextColor(int dayOfWeekTextColor) { this.mDayOfWeekTextColor = dayOfWeekTextColor; } public void setCurrentDayOfMonth(int currentDayOfMonth) { this.mCurrentDayOfMonth = currentDayOfMonth; } public void setWeekendColor(int weekendColor) { this.mWeekendColor = weekendColor; } public void setWeekend(int weekend) { this.mWeekend = weekend; } public void setBackButtonColor(@ColorRes int colorId) { this.mBackButton.setColorFilter(ContextCompat.getColor(mContext, colorId), PorterDuff.Mode.SRC_ATOP); } public void setNextButtonColor(@ColorRes int colorId) { this.mNextButton.setColorFilter(ContextCompat.getColor(mContext, colorId), PorterDuff.Mode.SRC_ATOP); } public void setBackButtonDrawable(@DrawableRes int drawableId) { this.mBackButton.setImageDrawable(ContextCompat.getDrawable(mContext, drawableId)); } public void setNextButtonDrawable(@DrawableRes int drawableId) { this.mNextButton.setImageDrawable(ContextCompat.getDrawable(mContext, drawableId)); } public Typeface getTypeface() { return mTypeface; } public List<DayDecorator> getDecoratorsList() { return mDecoratorsList; } public Locale getLocale() { return mContext.getResources().getConfiguration().locale; } public String getCurrentMonth() { return CalendarUtility.getCurrentMonth(mCurrentMonthIndex); } public String getCurrentYear() { return String.valueOf(mCalendar.get(Calendar.YEAR)); } public boolean isOverflowDateVisible() { return mIsOverflowDateVisible; } }