Draw And Build A Customized Calendar Widget With Flutter

In regard of building a calendar widget which allows BSS’s staffs to submit time periods for their leave applications, I used CustomPainter and GestureDetector to make it into reality.

This is my CustomCalendar class, we need to pass a callback to receive the time periods which user had selected, and a list of Offset to save the selected positions, it need to be passed from outside in order to keep its state when the CustomCalendar itself re-rendered.

  • nameOfEndDay: the last day of the current month, in int format. Achieved via a very useful dart package (https://pub.dartlang.org/packages/date_utils)
  • startDayIndex: index of the first day appeared in the calendar widget, has been used to put this day at the right position in the first line.
  • painter: the CustomPainter object to help you draw the widget UI.
  • touch: the GestureDetector object to identify user’s gesture such as tap, pinch (actually this widget only supports tap gesture at the time of this article ^_^)

The Painter class plays an important role in the CustomCalendar widget

In the constructor method of this class, we’ll initialize some Paint objects to draw borders and shapes in the paint method with will be elaborate later in this article.

  • To draw a line, we need to pass 2 parameters strokeWidth and strokeCap to describe how wide to make edges drawn and the kind of finish to place on the end of lines, respectively.
  • To draw a shape, a rectangle in this case, we need to define the style as PaintingStyle.fill, because this object will fill a specific area with the given color
Painter({this.nameOfEndDay, this.startDayIndex, this.onSelectDay, this.selectedPositions}) {
_paint = Paint()
..color = Colors.black
..strokeWidth = 1.0
..strokeCap = StrokeCap.round;

_paintFill = Paint()
..color = Colors.green.withOpacity(0.45)
..style = PaintingStyle.fill;

_paintFillWeekend = Paint()
..color = Colors.red.withOpacity(0.45)
..style = PaintingStyle.fill;

_paintFillSelection = Paint()
..color = Colors.purpleAccent
..style = PaintingStyle.fill;
}

We’ll need to override the paint method

Each time user tap on a time period, an object will be create and add to a list to save those offsets and draw a selected indicator on the correlative location. So I created this class, we’ll need to compare this kind of object later, so I overloaded the ‘==’ operator, the hashCode method is needed as well according to Dart language

class SelectedDateInfo {
int day;
sessionOfDay session;

SelectedDateInfo({this.day, this.session});

bool operator ==(o) => o is SelectedDateInfo && day == o.day && session == o.session;
int get hashCode => hash2(day.hashCode, session.hashCode);
}

enum dayOfWeek { Mon, Tue, Wed, Thu, Fri, Sat, Sun }

enum sessionOfDay { Morning, Afternoon }

Override the hitTest method to handle user’s interactions with the canvas object.

@override
bool hitTest(Offset position) {
getSelectedDayInfo(position);
onSelectDay(selectedDayInfo);
selectedDayInfo = new SelectedDateInfo();
return null;
}
  • getSelectedDayInfo(position): transfer the hitTest’s position to an actual SelectedDayInfo object which save the day and the session had already been chosen. today is the current day in int format.
getSelectedDayInfo(Offset l) {
int x, y;
double t = h / (4 + 1);
x = l.dx ~/ (w / 7);
y = l.dy ~/ t;

switch (y) {
case 1:
if (x - startDayIndex + 1 > 0) {
selectedDayInfo.day = x - startDayIndex + today;
}
break;
case 2:
selectedDayInfo.day = x - startDayIndex + today + 7;
break;
case 3:
selectedDayInfo.day = x - startDayIndex + today + 14;
break;
case 4:
selectedDayInfo.day = x - startDayIndex + today + 21;
break;
}

if (y != 0 && x < 5) {
if (l.dy - y * t < t / 2) {
selectedDayInfo.session = sessionOfDay.Morning;
} else {
selectedDayInfo.session = sessionOfDay.Afternoon;
}
}
}
  • onSelectDay: a callback let the parent receive the selected day and handle it.

Usage of the CustomCalendar widget, we need a List<Offset> as selectedPositions to save all selected locations and help the painter repaint them when the shouldRepaint method returned true.

CustomCalendar(
selectedPositions: selectedPositions,
onSelectDay: (SelectedDateInfo info) {
if (info.session != null && info.day != null) {
if (selectedDatesInfo.indexOf(info) == -1) {
selectedDatesInfo.add(info);
} else {
selectedDatesInfo.remove(info);
}
print(selectedDatesInfo.length);
}
})
Like what you read? Give Nguyễn Lê Gia Phụng a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.