diff --git a/library/src/main/java/com/alamkanak/weekview/WeekView.java b/library/src/main/java/com/alamkanak/weekview/WeekView.java index 5121ccb55..3d5198717 100755 --- a/library/src/main/java/com/alamkanak/weekview/WeekView.java +++ b/library/src/main/java/com/alamkanak/weekview/WeekView.java @@ -2,6 +2,7 @@ import android.content.Context; import android.content.res.TypedArray; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; @@ -10,6 +11,8 @@ import android.graphics.RectF; import android.graphics.Region; import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.os.Build; import android.support.annotation.Nullable; import android.support.v4.view.GestureDetectorCompat; @@ -81,12 +84,14 @@ private enum Direction { private Paint mNowLinePaint; private Paint mTodayHeaderTextPaint; private Paint mEventBackgroundPaint; + private Paint mNewEventBackgroundPaint; private float mHeaderColumnWidth; private List mEventRects; private List mPreviousPeriodEvents; private List mCurrentPeriodEvents; private List mNextPeriodEvents; private TextPaint mEventTextPaint; + private TextPaint mNewEventTextPaint; private Paint mHeaderColumnBackgroundPaint; private int mFetchedPeriod = -1; // the middle period the calendar has fetched. private boolean mRefreshEvents = false; @@ -95,9 +100,10 @@ private enum Direction { private boolean mIsZooming; private Calendar mFirstVisibleDay; private Calendar mLastVisibleDay; - private int mDefaultEventColor; private int mMinimumFlingVelocity = 0; private int mScaledTouchSlop = 0; + private EventRect mNewEventRect; + // Attributes and their default values. private int mHourHeight = 50; private int mNewHourHeight = -1; @@ -127,13 +133,22 @@ private enum Direction { private int mEventTextColor = Color.BLACK; private int mEventPadding = 8; private int mHeaderColumnBackgroundColor = Color.WHITE; + private int mDefaultEventColor; + private int mNewEventColor; + private int mNewEventId = -100; + private Drawable mNewEventIconDrawable; + private int mNewEventLengthInMinutes = 60; + private int mNewEventTimeResolutionInMinutes = 15; + private boolean mIsFirstDraw = true; private boolean mAreDimensionsInvalid = true; - @Deprecated private int mDayNameLength = LENGTH_LONG; + @Deprecated + private int mDayNameLength = LENGTH_LONG; private int mOverlappingEventGap = 0; private int mEventMarginVertical = 0; private float mXScrollingSpeed = 1f; private Calendar mScrollToDay = null; + private Calendar mCacheEmptyEventDay; private double mScrollToHour = -1; private int mEventCornerRadius = 0; private boolean mShowDistinctWeekendColor = false; @@ -151,6 +166,7 @@ private enum Direction { private EmptyViewLongPressListener mEmptyViewLongPressListener; private DateTimeInterpreter mDateTimeInterpreter; private ScrollListener mScrollListener; + private AddEventClickListener mAddEventClickListener; private final GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() { @@ -242,28 +258,76 @@ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float ve @Override public boolean onSingleTapConfirmed(MotionEvent e) { + // If the tap was on an event then trigger the callback. if (mEventRects != null && mEventClickListener != null) { List reversedEventRects = mEventRects; Collections.reverse(reversedEventRects); - for (EventRect event : reversedEventRects) { - if (event.rectF != null && e.getX() > event.rectF.left && e.getX() < event.rectF.right && e.getY() > event.rectF.top && e.getY() < event.rectF.bottom) { - mEventClickListener.onEventClick(event.originalEvent, event.rectF); + for (EventRect eventRect : reversedEventRects) { + if (eventRect.event.getId() != mNewEventId &&eventRect.rectF != null && e.getX() > eventRect.rectF.left && e.getX() < eventRect.rectF.right && e.getY() > eventRect.rectF.top && e.getY() < eventRect.rectF.bottom) { + mEventClickListener.onEventClick(eventRect.originalEvent, eventRect.rectF); playSoundEffect(SoundEffectConstants.CLICK); return super.onSingleTapConfirmed(e); } } } - // If the tap was on in an empty space, then trigger the callback. - if (mEmptyViewClickListener != null && e.getX() > mHeaderColumnWidth && e.getY() > (mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom)) { + // If the tap was on add new Event space, then trigger the callback + if (mAddEventClickListener != null && mNewEventRect != null && mNewEventRect.rectF != null && e.getX() > mNewEventRect.rectF.left && e.getX() < mNewEventRect.rectF.right && e.getY() > mNewEventRect.rectF.top && e.getY() < mNewEventRect.rectF.bottom) { + mAddEventClickListener.onAddEventClicked(mNewEventRect.event.getStartTime(), mNewEventRect.event.getEndTime()); + return super.onSingleTapConfirmed(e); + } + + // If the tap was on an empty space, then trigger the callback. + if ((mEmptyViewClickListener != null || mAddEventClickListener != null) && e.getX() > mHeaderColumnWidth && e.getY() > (mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom)) { Calendar selectedTime = getTimeFromPoint(e.getX(), e.getY()); + List tempEventRects = mEventRects; + mEventRects = new ArrayList(); if (selectedTime != null) { + if(mNewEventRect != null) { + tempEventRects.remove(mNewEventRect); + mNewEventRect = null; + } + playSoundEffect(SoundEffectConstants.CLICK); - mEmptyViewClickListener.onEmptyViewClicked(selectedTime); + if(mEmptyViewClickListener != null) + mEmptyViewClickListener.onEmptyViewClicked(selectedTime, mCacheEmptyEventDay, isSameDayAndHour(selectedTime, mCacheEmptyEventDay)); + + if(mAddEventClickListener != null) { + //round selectedTime to resolution + int unroundedMinutes = selectedTime.get(Calendar.MINUTE); + int mod = unroundedMinutes % mNewEventTimeResolutionInMinutes; + selectedTime.add(Calendar.MINUTE, mod < Math.ceil(mNewEventTimeResolutionInMinutes / 2) ? -mod : (mNewEventTimeResolutionInMinutes - mod)); + + Calendar endTime = (Calendar) selectedTime.clone(); + endTime.add(Calendar.MINUTE, Math.min(mNewEventLengthInMinutes, (24-selectedTime.get(Calendar.HOUR_OF_DAY))*60 - selectedTime.get(Calendar.MINUTE))); + WeekViewEvent newEvent = new WeekViewEvent(mNewEventId, "", null, selectedTime, endTime); + + float top = selectedTime.get(Calendar.HOUR_OF_DAY) * 60; + top = mHourHeight * 24 * top / 1440 + mCurrentOrigin.y + mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight / 2 + mEventMarginVertical; + float bottom = endTime.get(Calendar.HOUR_OF_DAY) * 60; + bottom = mHourHeight * 24 * bottom / 1440 + mCurrentOrigin.y + mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight / 2 - mEventMarginVertical; + + // Calculate left and right. + float left = 0; + float right = left + mWidthPerDay; + // Draw the event and the event name on top of it. + if (left < right && + left < getWidth() && + top < getHeight() && + right > mHeaderColumnWidth && + bottom > 0 + ) { + RectF dayRectF = new RectF(left, top, right, bottom); + newEvent.setColor(mNewEventColor); + mNewEventRect = new EventRect(newEvent, newEvent, dayRectF); + tempEventRects.add(mNewEventRect); + invalidate(); + computePositionOfEvents(tempEventRects); + } + } } } - return super.onSingleTapConfirmed(e); } @@ -336,7 +400,12 @@ public WeekView(Context context, AttributeSet attrs, int defStyleAttr) { mTodayHeaderTextColor = a.getColor(R.styleable.WeekView_todayHeaderTextColor, mTodayHeaderTextColor); mEventTextSize = a.getDimensionPixelSize(R.styleable.WeekView_eventTextSize, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, mEventTextSize, context.getResources().getDisplayMetrics())); mEventTextColor = a.getColor(R.styleable.WeekView_eventTextColor, mEventTextColor); - mEventPadding = a.getDimensionPixelSize(R.styleable.WeekView_eventPadding, mEventPadding); + mNewEventColor = a.getColor(R.styleable.WeekView_newEventColor, mNewEventColor); + mNewEventIconDrawable = a.getDrawable(R.styleable.WeekView_newEventIconResource); + mNewEventId = a.getInt(R.styleable.WeekView_newEventId, mNewEventId); + mNewEventLengthInMinutes = a.getInt(R.styleable.WeekView_newEventLengthInMinutes, mNewEventLengthInMinutes); + mNewEventTimeResolutionInMinutes = a.getInt(R.styleable.WeekView_newEventTimeResolutionInMinutes, mNewEventTimeResolutionInMinutes); + mEventPadding = a.getDimensionPixelSize(R.styleable.WeekView_hourSeparatorHeight, mEventPadding); mHeaderColumnBackgroundColor = a.getColor(R.styleable.WeekView_headerColumnBackground, mHeaderColumnBackgroundColor); mDayNameLength = a.getInteger(R.styleable.WeekView_dayNameLength, mDayNameLength); mOverlappingEventGap = a.getDimensionPixelSize(R.styleable.WeekView_overlappingEventGap, mOverlappingEventGap); @@ -425,6 +494,9 @@ private void init() { // Prepare event background color. mEventBackgroundPaint = new Paint(); mEventBackgroundPaint.setColor(Color.rgb(174, 208, 238)); + // Prepare empty event background color. + mNewEventBackgroundPaint = new Paint(); + mNewEventBackgroundPaint.setColor(Color.rgb(60, 147, 217)); // Prepare header column background color. mHeaderColumnBackgroundPaint = new Paint(); @@ -436,8 +508,13 @@ private void init() { mEventTextPaint.setColor(mEventTextColor); mEventTextPaint.setTextSize(mEventTextSize); + + //mStartDate = (Calendar) mFirstVisibleDay.clone(); + // Set default event color. mDefaultEventColor = Color.parseColor("#9fc6e7"); + // Set default empty event color. + mNewEventColor = Color.parseColor("#3c93d9"); mScaleDetector = new ScaleGestureDetector(mContext, new ScaleGestureDetector.OnScaleGestureListener() { @Override @@ -803,7 +880,10 @@ top < getHeight() && mEventRects.get(i).rectF = new RectF(left, top, right, bottom); mEventBackgroundPaint.setColor(mEventRects.get(i).event.getColor() == 0 ? mDefaultEventColor : mEventRects.get(i).event.getColor()); canvas.drawRoundRect(mEventRects.get(i).rectF, mEventCornerRadius, mEventCornerRadius, mEventBackgroundPaint); - drawEventTitle(mEventRects.get(i).event, mEventRects.get(i).rectF, canvas, top, left); + if(mEventRects.get(i).event.getId() != mNewEventId) + drawEventTitle(mEventRects.get(i).event, mEventRects.get(i).rectF, canvas, top, left); + else + drawEmptyImage(mEventRects.get(i).event, mEventRects.get(i).rectF, canvas, top, left); } else mEventRects.get(i).rectF = null; @@ -856,7 +936,6 @@ top < getHeight() && } } - /** * Draw the name of the event on top of the event rectangle. * @param event The event of which the title (and location) should be drawn. @@ -887,30 +966,46 @@ private void drawEventTitle(WeekViewEvent event, RectF rect, Canvas canvas, floa // Get text dimensions. StaticLayout textLayout = new StaticLayout(bob, mEventTextPaint, availableWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - - int lineHeight = textLayout.getHeight() / textLayout.getLineCount(); - - if (availableHeight >= lineHeight) { - // Calculate available number of line counts. - int availableLineCount = availableHeight / lineHeight; - do { - // Ellipsize text to fit into event rect. - textLayout = new StaticLayout(TextUtils.ellipsize(bob, mEventTextPaint, availableLineCount * availableWidth, TextUtils.TruncateAt.END), mEventTextPaint, (int) (rect.right - originalLeft - mEventPadding * 2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - - // Reduce line count. - availableLineCount--; - - // Repeat until text is short enough. - } while (textLayout.getHeight() > availableHeight); - - // Draw text. - canvas.save(); - canvas.translate(originalLeft + mEventPadding, originalTop + mEventPadding); - textLayout.draw(canvas); - canvas.restore(); + if(textLayout.getLineCount() > 0) { + int lineHeight = textLayout.getHeight() / textLayout.getLineCount(); + + if (availableHeight >= lineHeight) { + // Calculate available number of line counts. + int availableLineCount = availableHeight / lineHeight; + do { + // Ellipsize text to fit into event rect. + if (event.getId() != mNewEventId) + textLayout = new StaticLayout(TextUtils.ellipsize(bob, mEventTextPaint, availableLineCount * availableWidth, TextUtils.TruncateAt.END), mEventTextPaint, (int) (rect.right - originalLeft - mEventPadding * 2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + + // Reduce line count. + availableLineCount--; + + // Repeat until text is short enough. + } while (textLayout.getHeight() > availableHeight); + + // Draw text. + canvas.save(); + canvas.translate(originalLeft + mEventPadding, originalTop + mEventPadding); + textLayout.draw(canvas); + canvas.restore(); + } } } + /** + * Draw the text on top of the rectangle in the empty event. + * + * + */ + private void drawEmptyImage(WeekViewEvent event, RectF rect, Canvas canvas, float originalTop, float originalLeft) { + int size = Math.max(1,(int)Math.floor(Math.min(0.8 * rect.height(), 0.8 * rect.width()))); + if(mNewEventIconDrawable == null) + mNewEventIconDrawable = getResources().getDrawable(android.R.drawable.ic_input_add); + Bitmap icon = ((BitmapDrawable) mNewEventIconDrawable).getBitmap(); + icon = Bitmap.createScaledBitmap(icon, size, size, false); + canvas.drawBitmap(icon, originalLeft + (rect.width() - icon.getWidth())/ 2, originalTop + (rect.height() - icon.getHeight()) / 2, new Paint()); + + } /** * A class to hold reference to the events and their visual representation. An EventRect is @@ -1288,6 +1383,14 @@ public ScrollListener getScrollListener(){ return mScrollListener; } + public void setAddEventClickListener(AddEventClickListener addEventClickListener){ + this.mAddEventClickListener = addEventClickListener; + } + + public AddEventClickListener getAddEventClickListener(){ + return mAddEventClickListener; + } + /** * Get the interpreter which provides the text to show in the header column and the header row. * @return The date, time interpreter. @@ -1544,6 +1647,39 @@ public void setDefaultEventColor(int defaultEventColor) { invalidate(); } + public int getNewEventColor() { + return mNewEventColor; + } + + public void setNewEventColor(int defaultNewEventColor) { + mNewEventColor = defaultNewEventColor; + invalidate(); + } + + public int getNewEventId(){ + return mNewEventId; + } + + public void setNewEventId(int newEventId){ + this.mNewEventId = newEventId; + } + + public int getNewEventLengthInMinutes(){ + return mNewEventLengthInMinutes; + } + + public void setNewEventLengthInMinutes(int newEventLengthInMinutes) { + this.mNewEventLengthInMinutes = newEventLengthInMinutes; + } + + public int getNewEventTimeResolutionInMinutes(){ + return mNewEventTimeResolutionInMinutes; + } + + public void setNewEventTimeResolutionInMinutes(int newEventTimeResolutionInMinutes){ + this.mNewEventTimeResolutionInMinutes = newEventTimeResolutionInMinutes; + } + /** * Note: Use {@link #setDateTimeInterpreter(DateTimeInterpreter)} and * {@link #getDateTimeInterpreter()} instead. @@ -1882,6 +2018,7 @@ private boolean forceFinishScroll() { public void goToToday() { Calendar today = Calendar.getInstance(); goToDate(today); + mCacheEmptyEventDay = null; } /** @@ -1988,7 +2125,9 @@ public interface EmptyViewClickListener { * Triggered when the users clicks on a empty space of the calendar. * @param time: {@link Calendar} object set with the date and time of the clicked position on the view. */ - void onEmptyViewClicked(Calendar time); + //void onEmptyViewClicked(Calendar time); + void onEmptyViewClicked(Calendar time, Calendar tempTime, boolean clickedTwice); + } public interface EmptyViewLongPressListener { @@ -2009,4 +2148,14 @@ public interface ScrollListener { */ void onFirstVisibleDayChanged(Calendar newFirstVisibleDay, Calendar oldFirstVisibleDay); } + + public interface AddEventClickListener { + /** + * Triggered when the users clicks to create a new event. + * @param startTime The startTime of a new event + * @param endTime The endTime of a new event + */ + void onAddEventClicked(Calendar startTime, Calendar endTime); + + } } diff --git a/library/src/main/java/com/alamkanak/weekview/WeekViewUtil.java b/library/src/main/java/com/alamkanak/weekview/WeekViewUtil.java index 24264669d..fb5cf84b9 100644 --- a/library/src/main/java/com/alamkanak/weekview/WeekViewUtil.java +++ b/library/src/main/java/com/alamkanak/weekview/WeekViewUtil.java @@ -36,4 +36,19 @@ public static Calendar today(){ today.set(Calendar.MILLISECOND, 0); return today; } + + /** + * Checks if two times are on the same day and hour. + * + * @param dayOne The first day. + * @param dayTwo The second day. + * @return Whether the times are on the same day and hour. + */ + public static boolean isSameDayAndHour(Calendar dayOne, Calendar dayTwo) { + + if (dayTwo != null) { + return dayOne.get(Calendar.YEAR) == dayTwo.get(Calendar.YEAR) && dayOne.get(Calendar.DAY_OF_YEAR) == dayTwo.get(Calendar.DAY_OF_YEAR) && dayOne.get(Calendar.HOUR_OF_DAY) == dayTwo.get(Calendar.HOUR_OF_DAY); + } + return false; + } } diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml index 820b22730..1831ca319 100644 --- a/library/src/main/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -27,6 +27,11 @@ + + + + + diff --git a/sample/src/main/java/com/alamkanak/weekview/sample/BaseActivity.java b/sample/src/main/java/com/alamkanak/weekview/sample/BaseActivity.java index e311d1943..711b1b5f5 100644 --- a/sample/src/main/java/com/alamkanak/weekview/sample/BaseActivity.java +++ b/sample/src/main/java/com/alamkanak/weekview/sample/BaseActivity.java @@ -15,6 +15,7 @@ import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.List; import java.util.Locale; /** @@ -23,7 +24,7 @@ * Created by Raquib-ul-Alam Kanak on 1/3/2014. * Website: http://alamkanak.github.io */ -public abstract class BaseActivity extends AppCompatActivity implements WeekView.EventClickListener, MonthLoader.MonthChangeListener, WeekView.EventLongPressListener, WeekView.EmptyViewLongPressListener { +public abstract class BaseActivity extends AppCompatActivity implements WeekView.EventClickListener, MonthLoader.MonthChangeListener, WeekView.EventLongPressListener, WeekView.EmptyViewLongPressListener, WeekView.EmptyViewClickListener, WeekView.AddEventClickListener { private static final int TYPE_DAY_VIEW = 1; private static final int TYPE_THREE_DAY_VIEW = 2; private static final int TYPE_WEEK_VIEW = 3; @@ -52,6 +53,12 @@ protected void onCreate(Bundle savedInstanceState) { // Set long press listener for empty view mWeekView.setEmptyViewLongPressListener(this); + // Set EmptyView Click Listener + mWeekView.setEmptyViewClickListener(this); + + // Set AddEvent Click Listener + mWeekView.setAddEventClickListener(this); + // Set up a date time interpreter to interpret how the date and time will be formatted in // the week view. This is optional. setupDateTimeInterpreter(false); @@ -163,4 +170,19 @@ public void onEmptyViewLongPress(Calendar time) { public WeekView getWeekView() { return mWeekView; } + + @Override + public void onEmptyViewClicked(Calendar time, Calendar tempTime, boolean clickedTwice) { + Toast.makeText(this, "Empty view clicked: " + getEventTitle(time), Toast.LENGTH_SHORT).show(); + } + + @Override + public List onMonthChange(int newYear, int newMonth) { + return null; + } + + @Override + public void onAddEventClicked(Calendar startTime, Calendar endTime) { + Toast.makeText(this, "Add event clicked.", Toast.LENGTH_SHORT).show(); + } } diff --git a/sample/src/main/res/layout/activity_base.xml b/sample/src/main/res/layout/activity_base.xml index 5f68c319d..035cd75a6 100644 --- a/sample/src/main/res/layout/activity_base.xml +++ b/sample/src/main/res/layout/activity_base.xml @@ -22,6 +22,7 @@ app:dayBackgroundColor="#05000000" app:todayBackgroundColor="#1848adff" app:headerColumnBackground="#ffffffff" - app:todayHeaderTextColor="@color/accent" /> + app:todayHeaderTextColor="@color/accent" + app:newEventTimeResolutionInMinutes="15"/>