Skip to content

Commit 7e38535

Browse files
committed
Added support for overlapping events
1 parent 574fe9e commit 7e38535

File tree

4 files changed

+183
-13
lines changed

4 files changed

+183
-13
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@ Usage
3232
<dependency>
3333
<groupId>com.github.alamkanak</groupId>
3434
<artifactId>android-week-view</artifactId>
35-
<version>1.0.3</version>
35+
<version>1.1.0</version>
3636
<type>aar</type>
3737
</dependency>
3838
```
3939
* Grab via gradle
4040

4141
```groovy
42-
compile 'com.github.alamkanak:android-week-view:1.0.3'
42+
compile 'com.github.alamkanak:android-week-view:1.1.0'
4343
```
4444
2. Add WeekView in your xml layout.
4545

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
VERSION_NAME=1.0.3
1+
VERSION_NAME=1.1.0
22
GROUP=com.github.alamkanak
33

44
POM_DESCRIPTION=Dissect layout traversals on Android.

library/src/main/java/com/alamkanak/weekview/WeekView.java

Lines changed: 166 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.ArrayList;
2929
import java.util.Calendar;
3030
import java.util.Collections;
31+
import java.util.Comparator;
3132
import java.util.List;
3233

3334
/**
@@ -448,18 +449,19 @@ private void drawEvents(Calendar date, float startFromPixel, Canvas canvas) {
448449
if (isSameDay(mEventRects.get(i).event.getStartTime(), date)) {
449450

450451
// Calculate top.
451-
float top = mEventRects.get(i).event.getStartTime().get(Calendar.HOUR_OF_DAY) * 60 + mEventRects.get(i).event.getStartTime().get(Calendar.MINUTE);
452-
top = mHourHeight * 24 * top / 1440 + mCurrentOrigin.y + mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight/2;
452+
float top = mHourHeight * 24 * mEventRects.get(i).top / 1440 + mCurrentOrigin.y + mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight/2;
453453
float originalTop = top;
454-
if (top < mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight/2) top = mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight/2;
454+
if (top < mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight/2)
455+
top = mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight/2;
455456

456457
// Calculate bottom.
457-
float bottom = mEventRects.get(i).event.getEndTime().get(Calendar.HOUR_OF_DAY) * 60 + mEventRects.get(i).event.getEndTime().get(Calendar.MINUTE);
458+
float bottom = mEventRects.get(i).bottom;
458459
bottom = mHourHeight * 24 * bottom / 1440 + mCurrentOrigin.y + mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight/2;
459460

460461
// Calculate left and right.
461-
float left = startFromPixel;
462-
float right = startFromPixel + mWidthPerDay;
462+
float left = startFromPixel + mEventRects.get(i).left * mWidthPerDay;
463+
float originalLeft = left;
464+
float right = left + mEventRects.get(i).width * mWidthPerDay;
463465
if (left < mHeaderColumnWidth) left = mHeaderColumnWidth;
464466

465467
// Draw the event and the event name on top of it.
@@ -474,7 +476,7 @@ eventRectF.top < getHeight() &&
474476
mEventRects.get(i).rectF = eventRectF;
475477
mEventBackgroundPaint.setColor(mEventRects.get(i).event.getColor() == 0 ? mDefaultEventColor : mEventRects.get(i).event.getColor());
476478
canvas.drawRect(mEventRects.get(i).rectF, mEventBackgroundPaint);
477-
drawText(mEventRects.get(i).event.getName(), mEventRects.get(i).rectF, canvas, originalTop, startFromPixel);
479+
drawText(mEventRects.get(i).event.getName(), mEventRects.get(i).rectF, canvas, originalTop, originalLeft);
478480
}
479481
else
480482
mEventRects.get(i).rectF = null;
@@ -483,6 +485,7 @@ eventRectF.top < getHeight() &&
483485
}
484486
}
485487

488+
486489
/**
487490
* Draw the name of the event on top of the event rectangle.
488491
* @param text The text to draw.
@@ -516,7 +519,6 @@ else if (lineHeight >= availableHeight) {
516519
canvas.translate(originalLeft + mEventPadding, originalTop + mEventPadding);
517520
mTextLayout.draw(canvas);
518521
canvas.restore();
519-
520522
}
521523

522524

@@ -526,6 +528,10 @@ else if (lineHeight >= availableHeight) {
526528
private class EventRect {
527529
public WeekViewEvent event;
528530
public RectF rectF;
531+
public float left;
532+
public float width;
533+
public float top;
534+
public float bottom;
529535

530536
public EventRect(WeekViewEvent event, RectF rectF) {
531537
this.event = event;
@@ -564,6 +570,7 @@ private void getMoreEvents(Calendar day) {
564570
if (mFetchedMonths[0] < 1 || mFetchedMonths[0] != previousMonth || mRefreshEvents) {
565571
if (!containsValue(lastFetchedMonth, previousMonth) && !isInEditMode()){
566572
List<WeekViewEvent> events = mMonthChangeListener.onMonthChange((previousMonth==12)?day.get(Calendar.YEAR)-1:day.get(Calendar.YEAR), previousMonth);
573+
sortEvents(events);
567574
for (WeekViewEvent event: events) {
568575
mEventRects.add(new EventRect(event, null));
569576
}
@@ -575,6 +582,7 @@ private void getMoreEvents(Calendar day) {
575582
if (mFetchedMonths[1] < 1 || mFetchedMonths[1] != day.get(Calendar.MONTH)+1 || mRefreshEvents) {
576583
if (!containsValue(lastFetchedMonth, day.get(Calendar.MONTH)+1) && !isInEditMode()) {
577584
List<WeekViewEvent> events = mMonthChangeListener.onMonthChange(day.get(Calendar.YEAR), day.get(Calendar.MONTH) + 1);
585+
sortEvents(events);
578586
for (WeekViewEvent event : events) {
579587
mEventRects.add(new EventRect(event, null));
580588
}
@@ -586,15 +594,165 @@ private void getMoreEvents(Calendar day) {
586594
if (mFetchedMonths[2] < 1 || mFetchedMonths[2] != nextMonth || mRefreshEvents) {
587595
if (!containsValue(lastFetchedMonth, nextMonth) && !isInEditMode()) {
588596
List<WeekViewEvent> events = mMonthChangeListener.onMonthChange(nextMonth == 1 ? day.get(Calendar.YEAR) + 1 : day.get(Calendar.YEAR), nextMonth);
597+
sortEvents(events);
589598
for (WeekViewEvent event : events) {
590599
mEventRects.add(new EventRect(event, null));
591600
}
592601
}
593602
mFetchedMonths[2] = nextMonth;
594603
}
604+
605+
// Prepare to calculate positions of each events.
606+
ArrayList<EventRect> tempEvents = new ArrayList<EventRect>(mEventRects);
607+
mEventRects = new ArrayList<EventRect>();
608+
Calendar dayCounter = (Calendar) day.clone();
609+
dayCounter.add(Calendar.MONTH, -1);
610+
dayCounter.set(Calendar.DAY_OF_MONTH, 1);
611+
Calendar maxDay = (Calendar) day.clone();
612+
maxDay.add(Calendar.MONTH, 1);
613+
maxDay.set(Calendar.DAY_OF_MONTH, maxDay.getActualMaximum(Calendar.DAY_OF_MONTH));
614+
615+
// Iterate through each day to calculate the position of the events.
616+
while (dayCounter.getTimeInMillis() <= maxDay.getTimeInMillis()) {
617+
ArrayList<EventRect> eventRects = new ArrayList<EventRect>();
618+
for (EventRect eventRect : tempEvents) {
619+
if (isSameDay(eventRect.event.getStartTime(), dayCounter))
620+
eventRects.add(eventRect);
621+
}
622+
computePositionOfEvents(eventRects);
623+
dayCounter.add(Calendar.DATE, 1);
624+
}
625+
}
626+
627+
/**
628+
* Sorts the events in ascending order.
629+
* @param events The events to be sorted.
630+
*/
631+
private void sortEvents(List<WeekViewEvent> events) {
632+
Collections.sort(events, new Comparator<WeekViewEvent>() {
633+
@Override
634+
public int compare(WeekViewEvent event1, WeekViewEvent event2) {
635+
long start1 = event1.getStartTime().getTimeInMillis();
636+
long start2 = event2.getStartTime().getTimeInMillis();
637+
int comparator = start1 > start2 ? 1 : (start1 < start2 ? -1 : 0);
638+
if (comparator == 0) {
639+
long end1 = event1.getEndTime().getTimeInMillis();
640+
long end2 = event2.getEndTime().getTimeInMillis();
641+
comparator = end1 > end2 ? 1 : (end1 < end2 ? -1 : 0);
642+
}
643+
return comparator;
644+
}
645+
});
646+
}
647+
648+
/**
649+
* Calculates the left and right positions of each events. This comes handy specially if events
650+
* are overlapping.
651+
* @param eventRects The events along with their wrapper class.
652+
*/
653+
private void computePositionOfEvents(List<EventRect> eventRects) {
654+
// Make "collision groups" for all events that collide with others.
655+
List<List<EventRect>> collisionGroups = new ArrayList<List<EventRect>>();
656+
for (EventRect eventRect : eventRects) {
657+
boolean isPlaced = false;
658+
outerLoop:
659+
for (List<EventRect> collisionGroup : collisionGroups) {
660+
for (EventRect groupEvent : collisionGroup) {
661+
if (isEventsCollide(groupEvent.event, eventRect.event)) {
662+
collisionGroup.add(eventRect);
663+
isPlaced = true;
664+
break outerLoop;
665+
}
666+
}
667+
}
668+
if (!isPlaced) {
669+
List<EventRect> newGroup = new ArrayList<EventRect>();
670+
newGroup.add(eventRect);
671+
collisionGroups.add(newGroup);
672+
}
673+
}
674+
675+
for (List<EventRect> collisionGroup : collisionGroups) {
676+
expandEventsToMaxWidth(collisionGroup);
677+
}
678+
679+
}
680+
681+
/**
682+
* Expands all the events to maximum possible width. The events will try to occupy maximum
683+
* space available horizontally.
684+
* @param collisionGroup The group of events which overlap with each other.
685+
*/
686+
private void expandEventsToMaxWidth(List<EventRect> collisionGroup) {
687+
// Expand the events to maximum possible width.
688+
List<List<EventRect>> columns = new ArrayList<List<EventRect>>();
689+
columns.add(new ArrayList<EventRect>());
690+
for (EventRect eventRect : collisionGroup) {
691+
boolean isPlaced = false;
692+
for (List<EventRect> column : columns) {
693+
if (column.size() == 0) {
694+
column.add(eventRect);
695+
isPlaced = true;
696+
}
697+
else if (!isEventsCollide(eventRect.event, column.get(column.size()-1).event)) {
698+
column.add(eventRect);
699+
isPlaced = true;
700+
break;
701+
}
702+
}
703+
if (!isPlaced) {
704+
List<EventRect> newColumn = new ArrayList<EventRect>();
705+
newColumn.add(eventRect);
706+
columns.add(newColumn);
707+
}
708+
}
709+
710+
711+
// Calculate left and right position for all the events.
712+
int maxRowCount = columns.get(0).size();
713+
for (int i = 0; i < maxRowCount; i++) {
714+
// Set the left and right values of the event.
715+
float j = 0;
716+
for (List<EventRect> column : columns) {
717+
if (column.size() >= i+1) {
718+
EventRect eventRect = column.get(i);
719+
eventRect.width = 1f / columns.size();
720+
eventRect.left = j / columns.size();
721+
eventRect.top = eventRect.event.getStartTime().get(Calendar.HOUR_OF_DAY) * 60 + eventRect.event.getStartTime().get(Calendar.MINUTE);
722+
eventRect.bottom = eventRect.event.getEndTime().get(Calendar.HOUR_OF_DAY) * 60 + eventRect.event.getEndTime().get(Calendar.MINUTE);;
723+
mEventRects.add(eventRect);
724+
}
725+
j++;
726+
}
727+
}
595728
}
596729

597730

731+
/**
732+
* Checks if two events overlap.
733+
* @param event1 The first event.
734+
* @param event2 The second event.
735+
* @return true if the events overlap.
736+
*/
737+
private boolean isEventsCollide(WeekViewEvent event1, WeekViewEvent event2) {
738+
long start1 = event1.getStartTime().getTimeInMillis();
739+
long end1 = event1.getEndTime().getTimeInMillis();
740+
long start2 = event2.getStartTime().getTimeInMillis();
741+
long end2 = event2.getEndTime().getTimeInMillis();
742+
return (start1 > start2 && start1 < end2) || (end1 > start2 && end1 < end2);
743+
}
744+
745+
746+
/**
747+
* Checks if time1 occurs after (or at the same time) time2.
748+
* @param time1 The time to check.
749+
* @param time2 The time to check against.
750+
* @return true if time1 and time2 are equal or if time1 is after time2. Otherwise false.
751+
*/
752+
private boolean isTimeAfterOrEquals(Calendar time1, Calendar time2) {
753+
return !(time1 == null || time2 == null) && time1.getTimeInMillis() >= time2.getTimeInMillis();
754+
}
755+
598756
/**
599757
* Deletes the events of the months that are too far away from the current month.
600758
* @param currentDay The current day.

sample/src/main/java/com/alamkanak/weekview/sample/MainActivity.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,20 @@ public List<WeekViewEvent> onMonthChange(int newYear, int newMonth) {
135135
events.add(event);
136136

137137
startTime = Calendar.getInstance();
138-
startTime.set(Calendar.HOUR_OF_DAY, 05);
139-
startTime.set(Calendar.MINUTE, 0);
138+
startTime.set(Calendar.HOUR_OF_DAY, 4);
139+
startTime.set(Calendar.MINUTE, 20);
140+
startTime.set(Calendar.MONTH, newMonth-1);
141+
startTime.set(Calendar.YEAR, newYear);
142+
endTime = (Calendar) startTime.clone();
143+
endTime.set(Calendar.HOUR, 5);
144+
endTime.set(Calendar.MINUTE, 0);
145+
event = new WeekViewEvent(10, getEventTitle(startTime), startTime, endTime);
146+
event.setColor(getResources().getColor(R.color.event_color_03));
147+
events.add(event);
148+
149+
startTime = Calendar.getInstance();
150+
startTime.set(Calendar.HOUR_OF_DAY, 5);
151+
startTime.set(Calendar.MINUTE, 30);
140152
startTime.set(Calendar.MONTH, newMonth-1);
141153
startTime.set(Calendar.YEAR, newYear);
142154
endTime = (Calendar) startTime.clone();

0 commit comments

Comments
 (0)