add more than one dot indicator below date in android prolific material calendar view

Viewed 4447

I have implemented calendarview using android prolific material calendarview external library in my android application. Now I have to add multiple dot indicators below some dates to indicate number of events in each date. I tried but only got single dot below date. Please help

Here are my calendar fragment and decorator classes:

public class CalendarFragment extends Fragment {
    private View v;
    private List<Schedule> schedules = new ArrayList<Schedule>( );
    private MaterialCalendarView calendarView;

    public CalendarFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        try {
        v = inflater.inflate(R.layout.fragment_calendar, container, false);
        initViews();
        schedules = ScheduleDAO.getInstance().getScheduleListWithId();
            highlightDates(schedules);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return v;
    }

    private void highlightDates(List<Schedule>scheduleList) throws ParseException {
        for (int position = 0;position<scheduleList.size();position++)
        {
            Schedule schedule = scheduleList.get(position);
            if (schedule.getSessionStatus().equals("Incomplete")){
                int color = R.color.colorPrimaryDark;
                CurrentDayDecorator decorator = new CurrentDayDecorator(new Date(schedule.getScheduledDate()),color);
                calendarView.addDecorator(decorator);

            }else {
                int color = R.color.lightgray;
                CurrentDayDecorator decorator = new CurrentDayDecorator(new Date(schedule.getScheduledDate()),color);
                calendarView.addDecorator(decorator);
            }
        }
    }

    private void initViews() {
        calendarView = v.findViewById(R.id.calendarView);
    }
}

Decorator class
public class CurrentDayDecorator implements DayViewDecorator {
    private final int color;
    private final CalendarDay day;

    public CurrentDayDecorator(Date date,int color) {
        this.color = color;
        this.day = CalendarDay.from(date);
    }
    @Override
    public boolean shouldDecorate(CalendarDay day) {
        if (this.day.equals(day)){
            return true;
        }
        return false;
    }

    @Override
    public void decorate(DayViewFacade view) {
        view.addSpan(new DotSpan(3,color));
    }
}
2 Answers

I had a similar issue and found this post very helpful.

What you want to do is not supported through the MaterialViewCalendar API so you need to add some extra helper classes. Note that one decorator maps to one dot. So if you want two dots under a given date, then that date must be bound to two decorators etc.

One way to solve the issue is to do the following:

Change the decorate-method in your CurrentDayDecorator class to the following:

@Override
    public void decorate(DayViewFacade view) {
        LineBackgroundSpan span = new CustomSpan(color, xOffsets[spanType]);
        view.addSpan(span);
    }

As you can see we will need to add a new class (CustomSpan) and two fields (xOffsets and spanType) to your CurrentDayDecorator class. LineBackgroundSpan is an interface contained in the package.

private static class CustomSpan extends DotSpan{
    private int color;
    private int xOffset;
    private float radius = 3;
    CustomSpan(int color, int xOffset){
        this.color = color;
        this.xOffset = xOffset;
    }

        /*Note! The following code is more or less copy-pasted from the DotSpan class. I have commented the changes below.*/
    @Override
    public void drawBackground(Canvas canvas, Paint paint, int left, int right, int top, int baseline,
                               int bottom, CharSequence text, int start, int end, int lnum) {
        int oldColor = paint.getColor();
        if (color != 0) {
            paint.setColor(color);
        }
        int x = ((left + right) / 2); /*This is the x-coordinate right 
    below the date. If we add to x, we will draw the 
    circle to the right of the date and vice versa if we subtract from x.*/
        canvas.drawCircle(x + xOffset, bottom + radius, radius, paint);
        paint.setColor(oldColor);
    }
}

So all that is left is to feed an offset to our CustomSpan. In my own Decorator-method I have an array of offsets to pick from. Which one I pick depends on which dot I am creating. In my application I can put at most 4 dots below a date. I need to tell my CurrentDayDecorator which one of the 4 dots it is "representing", before I draw it.

This is how my class looks like (for you, just change "EventDecorator" to "CurrentDayDecorator")

 private static class EventDecorator implements DayViewDecorator {

    private static final float DEFAULT_DOT_RADIUS = 4;
    //Note that negative values indicate a relative offset to the LEFT
    private static final int[] xOffsets = new int[]{0,-10,10,-20};
    private int color;
    private HashSet<CalendarDay> dates;
    private float dotRadius;
    private int spanType;

    private EventDecorator(int color, float dotRadius, int spanType) {
        this.color = color;
        this.dotRadius = dotRadius;
        this.dates = new HashSet<>();
        this.spanType = spanType;
    }
    /*Note! I added this method so that I can add dates after object creation!*/
    public boolean addDate(CalendarDay day){
        return dates.add(day);
    }

    @Override
    public boolean shouldDecorate(CalendarDay day) {
        return dates.contains(day);
    }

    @Override
    public void decorate(DayViewFacade view) {
        LineBackgroundSpan span = new CustomSpan(color, xOffsets[spanType],DEFAULT_DOT_RADIUS);
        view.addSpan(span);
    }
}

Now the tricky part becomes knowing when to create a DayViewDecorator with the correct corresponding spanType. If a day should have two dots, its going to need two DayViewDecorators (with different values for spanType). What I did was that I kept track of all days with "events" and associated each "eventful day" with a counter. In other words, a HashMap mapping a CalendarDay to an Integer. Then you go through each entry in the Map and add the given day to X amounts of decorators, where X is the total count of events. In code it would look something like this:

EventDecorator[] decoratorArray = new EventDecorator[4]; //Max 4 dots
for(int i = 0; i<decoratorArray.length; i++)
        decoratorArray[i] = new EventDecorator(myColor,myRadius,i);

/*dayInstanceMap contains all the mappings.*/
for(Map.Entry<CalendarDay,Integer> entry : dayInstanceMap.entrySet()){
      CalendarDay currDay = entry.getKey();
      Integer currDayCount = entry.getValue(); //If you have max amount of dots, check here if currDay is too large.
      for(int i = 0; i<currDayCount; i++)
          decoratorArray[i].addDate(currDay);
}

After that, just add the decorators to the MaterialCalendarView and you should be good to go.

  1. Create a custom LineBackgroundSpan that draws the dots from left to right
class MultiDotSpan(
    private val radius: Float = 3f,
    private val colors: List<Int>
) : LineBackgroundSpan {

    override fun drawBackground(
        canvas: Canvas, paint: Paint,
        left: Int, right: Int, top: Int, baseline: Int, bottom: Int,
        charSequence: CharSequence,
        start: Int, end: Int, lineNum: Int
    ) {

        var position = (left + right) / 2 - (colors.size - 1) * radius

        if (colors.size > 1) {
            position - 2
        }

        colors.forEach { color ->
            val oldColor = paint.color
            if (color != 0) {
                paint.color = color
            }
            canvas.drawCircle(position, bottom + radius, radius, paint)
            paint.color = oldColor

            position += radius * 2 + 2
        }
    }
}
  1. Add it to your decorator
class EventDecorator(
    private val colors: List<Int>,
    private var dates: List<CalendarDay> = emptyList()
) : DayViewDecorator {

    override fun shouldDecorate(day: CalendarDay) = dates.contains(day)

    override fun decorate(view: DayViewFacade) = with(view) {
        addSpan(MultiDotSpan(8f, colors))
    }
}
  1. Add the decorator to your calendar view
val eventDecorator = MCVEventDecorator(colors, events)
calendarView.addDecorator(eventDecorator)

Note: You will need to use a different decorator for different amounts of dots (e.g. single, double, triple etc)

Related