Technical Documentation: kizitonwose/Calendar
โน๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference
Overview (`README.md`)
Calendar
A highly customizable calendar library for Android and Compose Multiplatform, backed by RecyclerView for the view system, and LazyRow/LazyColumn for compose.
[not(contains(text(),%27-%27))][last()])
[not(contains(text(),%27-%27))][last()])
With this library, your calendar will look however you want it to.
Features
- Week, month, or year modes - Show a week-based calendar, or the typical month calendar, or a year-based calendar.
- Single, multiple, or range selection - Total flexibility to implement the date selection
whichever way you like. - Disable desired dates - Prevent selection of some dates by disabling them.
- Boundary dates - Limit the calendar date range.
- Custom date view/composable - Make your day cells look however you want, with any
functionality you want. - Custom calendar view/composable - Make your calendar look however you want, with whatever
functionality you want. - Custom first day of the week - Use any day as the first day of the week.
- Horizontal or vertical scrolling calendar.
- HeatMap calendar - Suitable for showing how data changes over time, like GitHub's contribution
chart. - Year/Month/Week headers and footers - Add headers/footers of any kind on each year/month/week.
- Easily scroll to any date/week/month/year on the calendar via swipe actions or programmatically.
- Use all RecyclerView/LazyRow/LazyColumn customizations since the calendar extends from
RecyclerView for the view system and uses LazyRow/LazyColumn for compose. - Design your calendar however you want. The
library provides the logic, you provide the views/composables.
Sample project
It's important to check out the sample app. There are lots of examples provided for both view and compose implementations.
Most techniques that you would want to implement are already done in the examples.
Download the Android sample app here
View the Android sample app's source code here
View the multiplatform sample project online at https://calendar.kizitonwose.dev
View the multiplatform sample project's source code here
Setup
The library provides the following artifacts:
com.kizitonwose.calendar:compose: The compose artifact for Android projects. This uses the java.time APIs.
com.kizitonwose.calendar:compose-multiplatform: The compose artifact for Compose Multiplatform projects. This uses the kotlinx-datetime library and supports Android, iOS, js, WasmJs and Desktop platforms.
com.kizitonwose.calendar:view: The view artifact for Android projects. This uses the java.time APIs and can exist alongside the Android compose artifact in an Android project if needed.
Step 1
This step is required ONLY if your Android app's minSdkVersion is below 26. Jump to step 2 if this does not apply to you.
Android apps with minSdkVersion below 26 have to enable Java 8+ API desugaring for backward compatibility since java.time classes were added in Java 8 which is supported natively starting from Android SDK 26. To set up your project for desugaring, you need to first ensure that you are using Android Gradle plugin 4.0.0 or higher.
Then include the following in your app's build.gradle file:
android {
defaultConfig {
// Required ONLY if your minSdkVersion is below 21
multiDexEnabled true
}
compileOptions {
// Enable support for the new language APIs
coreLibraryDesugaringEnabled true
// Set Java compatibility (version can be higher if desired)
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
// Also add this for Kotlin projects (version can be higher if desired)
jvmTarget = "1.8"
}
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:<latest-version>'
}You can find the latest version of desugar_jdk_libs here.
Step 2A - For pure Android projects without multiplatform setup
Add the desired calendar library (view or compose) to your app's build.gradle.kts:
dependencies {
// The view calendar library for Android
implementation("com.kizitonwose.calendar:view:<latest-version>")
// The compose calendar library for Android
implementation("com.kizitonwose.calendar:compose:<latest-version>")
}Step 2B - For Compose Multiplatform projects
Add the multiplatform calendar library to your project's build.gradle.kts:
commonMain.dependencies {
// The calendar library for compose multiplatform projects
// Supports Android, iOS, js, WasmJs and Desktop platforms
implementation("com.kizitonwose.calendar:compose-multiplatform:<latest-version>")
}You can find the latest version of the library on the maven central badge above.
Snapshots of the development version are available in Central Portal Snapshots repository.
Compose UI version compatibility
For the compose calendar library, ensure that you are using the library version that matches the Compose UI version in your project. If you use a version of the library that has a higher version of Compose UI than the one in your project, gradle will upgrade the Compose UI version in your project via transitive dependency.
| Compose UI | Android Calendar Library | Multiplatform Calendar Library |
|---|---|---|
| 1.2.x | 2.0.x | - |
| 1.3.x | 2.1.x - 2.2.x | - |
| 1.4.x | 2.3.x | - |
| 1.5.x | 2.4.x | - |
| 1.6.x | 2.5.x | 2.5.x |
| 1.7.x | 2.6.x | 2.6.x |
| 1.8.x | 2.7.x - 2.8.x | 2.7.x - 2.8.x |
| 1.9.x | 2.9.x | 2.9.x |
| 1.10.x | 2.10.x | 2.10.x |
| 1.11.x | 2.10.x | 2.10.x |
Usage
You can find the relevant documentation for the library in the links below.
| View-based documentation | Compose documentation |
|---|
Migration
If you're upgrading from calendar library version 1.x.x to 2.x.x, see the migration guide.
Share your creations
Made a cool calendar with this library? Share an image here.
Contributing
Found a bug? feel free to fix it and send a pull request or open an issue.
License
Calendar library is distributed under the MIT license.
See LICENSE for details.
Compose (`docs/Compose.md`)
Calendar Compose Documentation
Table of Contents
- Quick links
- Compose Multiplatform Information
- Compose UI version compatibility
- Calendar Composables
- Usage
- Week calendar
- HeatMap calendar
- Year calendar
Quick links
Check out the sample app if you have not yet done so. Most techniques that you would want to implement are already done in the examples.
Download the Android sample app here
Read the Android sample app's source code here
View the multiplatform sample project online at https://calendar.kizitonwose.dev
Read the multiplatform sample project's source code here
Add the library to your project here
If you are looking for the view-based documentation, you can find it here
Compose Multiplatform Information
The APIs for the compose libraries for Android and Multiplatform projects have been designed such that you can copy examples across both projects and they would work without code changes as the classes have the same names and package declarations. The only difference in some cases would be that the code for the Android calendar library needs to import classes such as LocalDate, YearMonth and Year from the java.time package while the multiplaform calendar library needs to import such classes from the kotlinx-datetime library.
Note that the Year class does not yet exist in the kotlinx-datetime library, therefore the multiplatform calendar library includes a minimal Year class implementation to bridge this gap.
Compose UI version compatibility
Ensure that you are using the library version that matches the Compose UI version in your project. If you use a version of the library that has a higher version of Compose UI than the one in your project, gradle will upgrade the Compose UI version in your project via transitive dependency. See the compatibility table here.
Calendar Composables
The library can be used via six composables:
HorizontalCalendar(): Horizontally scrolling month-based calendar.
VerticalCalendar(): Vertically scrolling month-based calendar.
WeekCalendar(): Horizontally scrolling week-based calendar.
HeatMapCalendar(): Horizontally scrolling heatmap calendar, useful for showing how data changes over time. A popular example is the user contribution chart on GitHub.
HorizontalYearCalendar(): Horizontally scrolling year-based calendar.
VerticalYearCalendar(): Vertically scrolling year-based calendar.
All composables are based on LazyRow/LazyColumn for efficiency.
In the examples below, we will mostly use the month-based HorizontalCalendar
and VerticalCalendar composables since all the calendar composables share the same basic concept.
If you need a week-based calendar, use the WeekCalendar composable instead. If you need a
year-based calendar, use the HorizontalYearCalendar and VerticalYearCalendar composables.
Most state properties/methods with the name prefix/suffix month (e.g firstVisibleMonth) in the
month-base calendar will have an equivalent with the name prefix/suffix week (
e.g firstVisibleWeek) in the week-based calendar and year (e.g firstVisibleYear) in the
year-based calendar.
Usage
HorizontalCalendar and VerticalCalendar:
@Composable
fun MainScreen() {
val currentMonth = remember { YearMonth.now() }
val startMonth = remember { currentMonth.minusMonths(100) } // Adjust as needed
val endMonth = remember { currentMonth.plusMonths(100) } // Adjust as needed
val firstDayOfWeek = remember { firstDayOfWeekFromLocale() } // Available from the library
val state = rememberCalendarState(
startMonth = startMonth,
endMonth = endMonth,
firstVisibleMonth = currentMonth,
firstDayOfWeek = firstDayOfWeek
)
HorizontalCalendar(
state = state,
dayContent = { Day(it) }
)
// If you need a vertical calendar.
// VerticalCalendar(
// state = state,
// dayContent = { Day(it) }
// )
}Note: There is an additional parameter that can be provided when creating the state: outDateStyle. This determines how the out-dates are generated. See the properties section to understand this parameter.
WeekCalendar:
@Composable
fun MainScreen() {
val currentDate = remember { LocalDate.now() }
val currentMonth = remember { YearMonth.now() }
val startDate = remember { currentMonth.minusMonths(100).atStartOfMonth() } // Adjust as needed
val endDate = remember { currentMonth.plusMonths(100).atEndOfMonth() } // Adjust as needed
val firstDayOfWeek = remember { firstDayOfWeekFromLocale() } // Available from the library
val state = rememberWeekCalendarState(
startDate = startDate,
endDate = endDate,
firstVisibleWeekDate = currentDate,
firstDayOfWeek = firstDayOfWeek
)
WeekCalendar(
state = state,
dayContent = { Day(it) }
)
}HorizontalYearCalendar and VerticalYearCalendar:
@Composable
fun MainScreen() {
val currentYear = remember { Year.now() }
val startYear = remember { currentYear.minusYears(100) } // Adjust as needed
val endYear = remember { currentYear.plusYears(100) } // Adjust as needed
val firstDayOfWeek = remember { firstDayOfWeekFromLocale() } // Available from the library
val state = rememberYearCalendarState(
startYear = startYear,
endYear = endYear,
firstVisibleYear = currentYear,
firstDayOfWeek = firstDayOfWeek,
)
HorizontalYearCalendar(
state = state,
dayContent = { Day(it) },
)
// If you need a vertical year calendar.
// VerticalYearCalendar(
// state = state,
// dayContent = { Day(it) }
// )
}Your Day composable in its simplest form would be:
@Composable
fun Day(day: CalendarDay) {
Box(
modifier = Modifier
.aspectRatio(1f), // This is important for square sizing!
contentAlignment = Alignment.Center
) {
Text(text = day.date.dayOfMonth.toString())
}
}In the above example, we use Modifier.aspectRatio(1f), this is because the calendar assigns the width of the month divided by 7 as the width of each day cell. No height is set, so you have the flexibility to decide what works best for you.
To get the typical square look on a calendar, you use Modifier.aspectRatio(1f) which tells the box to make its height the same size as the assigned width.
You can choose to set a specific height if you want. For example: Modifier.height(70.dp)
And that's all you need for simple usage! But keep reading, there's more!
First day of the week and Day of week titles.
Of course, you want to show the day of week titles on the appropriate days on the calendar.
Sun | Mon | Tue | Wed | Thu | Fri | Sat
Here's a method that generates the weekdays from the user's current Locale.
val daysOfWeek = daysOfWeek() // Available in the libraryThe function takes a firstDayOfWeek parameter in case you want to generate the days of week such that the desired day is at the first position.
For example:
val daysOfWeek = daysOfWeek(firstDayOfWeek = DayOfWeek.THURSDAY)
// Will produce => Thu | Fri | Sat | Sun | Mon | Tue | WedUsing the daysOfWeek list, you can set up the calendar so the first day of the week is what the user would expect. This could be Sunday, Monday, etc. It is good practice to use what the Locale returns as that's what the user would expect.
To set up the calendar state using the provided daysOfWeek list:
- val firstDayOfWeek = remember { firstDayOfWeekFromLocale() }
+ val daysOfWeek = remember { daysOfWeek() }
val state = rememberCalendarState(
startMonth = startMonth,
endMonth = endMonth,
firstVisibleMonth = currentMonth,
- firstDayOfWeek = firstDayOfWeek
+ firstDayOfWeek = daysOfWeek.first()
)You should also use the daysOfWeek list values to set up the weekday titles, this way it matches what is shown on the calendar.
To set up the day of week titles, you can either use the month header which would show the titles on every month and allow the titles to scroll with the month, or you can show the title on a static composable above the calendar. Both ways are covered below:
Setup days of week using a static title composable:
@Composable
fun DaysOfWeekTitle(daysOfWeek: List<DayOfWeek>) {
Row(modifier = Modifier.fillMaxWidth()) {
for (dayOfWeek in daysOfWeek) {
Text(
modifier = Modifier.weight(1f),
textAlign = TextAlign.Center,
text = dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.getDefault()),
)
}
}
}Now you can use the title composable with the calendar in a column:
@Composable
fun MainScreen() {
Column {
DaysOfWeekTitle(daysOfWeek = daysOfWeek) // Use the title here
HorizontalCalendar(
state = state,
dayContent = { Day(it) }
)
}
}To use the titles as a month header so it scrolls with each month, continue to the month headers and footers section below!
Month headers and footers.
To add a header or footer to each month, the procedure is the same as we did for the day using the dayContent calendar parameter, but instead of the dayContent, you have to provide the monthHeader or monthFooter composable parameter.
To add the days of week titles as the month header, we can set the same DaysOfWeekTitle composable discussed above as the monthHeader parameter:
@Composable
fun MainScreen() {
HorizontalCalendar(
state = state,
dayContent = { Day(it) },
monthHeader = {
DaysOfWeekTitle(daysOfWeek = daysOfWeek) // Use the title as month header
}
)
}In the code above, we use the same daysOfWeek list that was created when we initialized the calendar state. However, we can also get the daysOfWeek list from the month data passed into the monthHeader parameter:
@Composable
fun MainScreen() {
HorizontalCalendar(
state = state,
dayContent = { Day(it) },
monthHeader = { month ->
// You may want to use `remember {}` here so the mapping is not done
// every time as the days of week order will never change unless
// you set a new value for `firstDayOfWeek` in the state.
val daysOfWeek = month.weekDays.first().map { it.date.dayOfWeek }
MonthHeader(daysOfWeek = daysOfWeek)
}
)
}With the day of week titles added, you can have a calendar that looks like this:
You can do more than just use the day titles as the header. For example, you can also show the month name if it is not already shown somewhere outside the calendar. Feel free to get creative with your month headers and footers! For complex usages, please see the sample project.
Calendar containers
Two interesting parameters of the calendar composable are the monthBody and monthContainer. You will typically not need these. But if you want to do some customizations before the calendar is rendered, then this is the place for it.
For example, if you want to draw a gradient behind the container where all the days are rendered and add rounded corners/borders to the entire month container and also shrink the entire month container so it does not fit the screen width, the monthBody and monthContainer will be:
@Composable
fun MainScreen() {
HorizontalCalendar(
// Draw the day content gradient.
monthBody = { _, content ->
Box(
modifier = Modifier.background(
brush = Brush.verticalGradient(
colors = listOf(
Color(0xFFB2EBF2),
Color(0xFFB2B8F2)
)
)
)
) {
content() // Render the provided content!
}
},
// Add the corners/borders and month width.
monthContainer = { _, container ->
val configuration = LocalConfiguration.current
val screenWidth = configuration.screenWidthDp.dp
Box(
modifier = Modifier
.width(screenWidth * 0.73f)
.padding(8.dp)
.clip(shape = RoundedCornerShape(8.dp))
.border(
color = Color.Black,
width = 1.dp,
shape = RoundedCornerShape(8.dp)
)
) {
container() // Render the provided container!
}
}
)
}With the above monthBody and monthContainer configurations, we will have this calendar:
Other composable parameters.
calendarScrollPaged: The scrolling behavior of the calendar. When
true, the calendar will snap to the nearest month after a scroll or swipe action. Whenfalse, the calendar scrolls normally.userScrollEnabled: Whether scrolling via the user gestures or accessibility actions is allowed. You can still scroll programmatically using the state even when it is disabled. Inherited from LazyRow/LazyColumn.
reverseLayout: reverse the direction of scrolling and layout. When
true, months will be composed from the end to the start andstartMonthwill be located at the end. Inherited from LazyRow/LazyColumn.contentPadding: A padding around the whole calendar. This will add padding for the content after it has been clipped, which is not possible via
modifierparameter. Inherited from LazyRow/LazyColumn.
State properties
All properties set when creating the state via rememberCalendarState() or rememberWeekCalendarState() can be updated in the future via the appropriate property in the state object. There are also other interesting properties in the state objects worth mentioning.
CalendarState properties for HorizontalCalendar and VerticalCalendar:
firstVisibleMonth: The first month that is visible on the calendar.
lastVisibleMonth: The last month that is visible on the calendar.
layoutInfo: A subclass of
LazyListLayoutInfocalculated during the last layout pass. For example, you can use it to calculate what items are currently visible.isScrollInProgress: Whether this calendar is currently scrolling by gesture, fling, or programmatically.
outDateStyle: This determines how outDates are generated for each month on the calendar. It can be one of two values:
- EndOfRow: The calendar will generate
outDatesuntil it reaches the end of the month row. This means that if a month has 5 rows, it will display 5 rows and if a month has 6 rows, it will display 6 rows. - EndOfGrid: The calendar will generate
outDatesuntil it reaches the end of a 6 x 7 grid on each month. This means that all months will have 6 rows.
This value can also be provided when the calendar state is initialized via
rememberCalendarState(outDateStyle = ...).- EndOfRow: The calendar will generate
If you are wondering what outDates and inDates mean, let's use the screenshot below as an example.
In the image, the dates within the green annotation are inDates, the ones within the red annotation are outDates while those without annotation are monthDates. You can check for this when your calendar day is rendered. To achieve the exact effect on the image, we update our Day composable:
@Composable
fun Day(day: CalendarDay) {
Box(
modifier = Modifier
.aspectRatio(1f),
contentAlignment = Alignment.Center
) {
Text(
text = day.date.dayOfMonth.toString(),
color = if (day.position == DayPosition.MonthDate) Color.White else Color.Gray
)
}
}inDates have their position property set to DayPosition.InDate
outDates have their position property set to DayPosition.OutDate
monthDates have their position property set to DayPosition.MonthDate as seen in the code snippet above.
WeekCalendarState properties for WeekCalendar:
firstVisibleWeek: The first week that is visible on the calendar.
lastVisibleWeek: The last week that is visible on the calendar.
layoutInfo: A subclass of
LazyListLayoutInfocalculated during the last layout pass. For example, you can use it to calculate what items are currently visible.isScrollInProgress: Whether this calendar is currently scrolling by gesture, fling, or programmatically.
YearCalendarState properties for HorizontalYearCalendar and VerticalYearCalendar:
firstVisibleYear: The first year that is visible on the calendar.
lastVisibleYear: The last year that is visible on the calendar.
layoutInfo: A subclass of
LazyListLayoutInfocalculated during the last layout pass. For example, you can use it to calculate what items are currently visible.isScrollInProgress: Whether this calendar is currently scrolling by gesture, fling, or programmatically.
State methods
CalendarState
scrollToDate(date: LocalDate): Instantly scroll to a date on the calendar without an animation.
animateScrollToDate(date: LocalDate): Scroll to a date on the calendar with smooth scrolling animation.
scrollToMonth(month: YearMonth): Instantly scroll to a month on the calendar without an animation.
animateScrollToMonth(month: YearMonth): Scroll to a month on the calendar with smooth scrolling animation.
WeekCalendarState
scrollToDate(date: LocalDate): Instantly scroll to a date on the calendar without an animation.
animateScrollToDate(date: LocalDate): Scroll to a date on the calendar with smooth scrolling animation.
scrollToWeek(date: LocalDate): Instantly scroll to the week containing the given date on the calendar without an animation.
animateScrollToWeek(date: LocalDate): Scroll to the week containing the given date on the calendar with smooth scrolling animation.
YearCalendarState
scrollToDate(date: LocalDate): Instantly scroll to a date on the calendar without an animation.
animateScrollToDate(date: LocalDate): Scroll to a date on the calendar with smooth scrolling animation.
scrollToMonth(month: YearMonth): Instantly scroll to a month on the calendar without an animation.
animateScrollToMonth(month: YearMonth): Scroll to a month on the calendar with smooth scrolling animation.
scrollToYear(year: Year): Instantly scroll to a year on the calendar without an animation.
animateScrollToYear(year: Year): Scroll to a year on the calendar with smooth scrolling animation.
There's no need to repeat the documentation here. Please see the relevant class for all properties and methods available with proper documentation.
Date clicks
You can handle clicks in your Day composable as you would for any other composable via the modifier:
@Composable
fun Day(day: CalendarDay, onClick: (CalendarDay) -> Unit) {
Box(
modifier = Modifier
.aspectRatio(1f)
.clickable(
enabled = day.position == DayPosition.MonthDate,
onClick = { onClick(day) }
),
contentAlignment = Alignment.Center
) {
Text(text = day.date.dayOfMonth.toString())
}
}Date Selection
The library has no inbuilt concept of selected/unselected dates, this gives you the freedom to choose how best you would like to implement this use case.
Implementing date selection is as simple as showing a background on a specific date in the Day composable.
For this example, I want only the last clicked date to be selected on the calendar.
Firstly, we update our Day composable to show a circle background if the date is selected:
@Composable
fun Day(day: CalendarDay, isSelected: Boolean, onClick: (CalendarDay) -> Unit) {
Box(
modifier = Modifier
.aspectRatio(1f)
.clip(CircleShape)
.background(color = if (isSelected) Color.Green else Color.Transparent)
.clickable(
enabled = day.position == DayPosition.MonthDate,
onClick = { onClick(day) }
),
contentAlignment = Alignment.Center
) {
Text(text = day.date.dayOfMonth.toString())
}
}Next, using the click logic already shown in the date click section above, we update the selected date state whenever a date is clicked:
@Composable
fun MainScreen() {
var selectedDate by remember { mutableStateOf<LocalDate?>(null) }
HorizontalCalendar(
state = state,
dayContent = { day ->
Day(day, isSelected = selectedDate == day.date) { day ->
selectedDate = if (selectedDate == day.date) null else day.date
}
}
)
}For more complex selection logic like range selection, please see the sample project. It's quite simple, the magic is all in your logic!
Disabling dates
As expected, the library does not provide this logic internally so you have complete flexibility.
To disable dates, you can simply set the texts on those dates to look disabled and disable clicks on those dates. For example, if we want to show in and out dates but disable them so that they cannot be selected, we can just set a different color on the texts.
We actually already did this with the example in the date click section, we already ignore clicks for in and out dates using this logic:
@Composable
fun Day(day: CalendarDay, onClick: (CalendarDay) -> Unit) {
Box(
modifier = Modifier
.aspectRatio(1f)
.clickable(
enabled = day.position == DayPosition.MonthDate, // Only month-dates are clickable.
onClick = { onClick(day) }
),
contentAlignment = Alignment.Center
) { // Change the color of in-dates and out-dates, you can also hide them completely!
Text(
text = day.date.dayOfMonth.toString(),
color = if (day.position == DayPosition.MonthDate) Color.White else Color.Gray
)
}
}And we've now covered the typical usage. The beauty of the library is in its limitless possibilities. You are not constrained on how to build your user interface, the library provides you with the needed calendar data logic and you provide the desired UI logic.
See the sample project for some complex implementations.
Week calendar
The WeekCalendar is a week-based calendar. Almost all topics covered above for the month calendar
will apply to the week calendar. The main difference is that state properties/methods will have a
slightly different name, typically with a week prefix/suffix instead of month.
For example: firstVisibleMonth => firstVisibleWeek, scrollToMonth() => scrollToWeek() and many others, but you get the idea.
We already showed how to use the WeekCalendar previously in the usage section, but in the most basic form, it is:
@Composable
fun MainScreen() {
val state = rememberWeekCalendarState(
startDate = ...,
endDate = ...,
firstVisibleWeekDate = ...,
firstDayOfWeek = ...
)
WeekCalendar(
state = state,
dayContent = { Day(it) }
)
}A week calendar implementation from the sample app:
If you would like to toggle the calendar between month and week modes, please see the sample app where we did this by animating the Modifier height and alternatively using the AnimatedVisibility API.
HeatMap calendar
This is a horizontally scrolling heatmap calendar implementation, useful for showing how data changes over time. A popular example is the user contribution chart on GitHub. Another usage could be to show changes in the frequency of a habit tracked by a user.
A screenshot from the sample app is shown below:
All the properties in the month-based calendar are also available in the HeatMap calendar except OutDateStyle configuration as that is not relevant in this case. Note that there are out-dates on the calendar but since the dates are laid out in columns instead of rows, the two OutDateStyle options EndOfRow and EndOfGrid are not needed here. All other month-based properties are available!
Basic HeatMap calendar usage:
@Composable
fun MainScreen() {
val currentMonth = remember { YearMonth.now() }
val startMonth = remember { currentMonth.minusMonths(100) } // Adjust as needed
val endMonth = remember { currentMonth.plusMonths(100) } // Adjust as needed
val firstDayOfWeek = remember { firstDayOfWeekFromLocale() } // Available from the library
val state = rememberHeatMapCalendarState(
startMonth = startMonth,
endMonth = startMonth,
firstVisibleMonth = currentMonth,
firstDayOfWeek = firstDayOfWeek,
)
HeatMapCalendar(
state = state,
dayContent = { day, _ -> Day(day) },
weekHeader = { WeekHeader(it) },
monthHeader = { MonthHeader(it) }
)
}Please see the HeatMapCalendar composable for the full documentation. There are also examples in the sample app.
Year calendar
The year-based calendar is best suited for large screens and can be used via
the HorizontalYearCalendar and VerticalYearCalendar composables. All topics covered above for
the month calendar will apply to the year calendar. The main difference is that state
properties/methods will have a slightly different name, typically with a year prefix/suffix
instead of month.
For example: firstVisibleMonth => firstVisibleYear, scrollToMonth() => scrollToYear() and
many others, but you get the idea.
The monthHeader and monthFooter parameters are available in both the month and year calendars
and serve the same purpose in both cases. The year calendar additionally provides the yearHeader
and yearFooter parameters to add a header or footer to each year on the calendar.
Basic year calendar usage:
@Composable
fun MainScreen() {
val currentYear = remember { Year.now() }
val startYear = remember { currentYear.minusYears(100) } // Adjust as needed
val endYear = remember { currentYear.plusYears(100) } // Adjust as needed
val firstDayOfWeek = remember { firstDayOfWeekFromLocale() } // Available from the library
val state = rememberYearCalendarState(
startYear = startYear,
endYear = endYear,
firstVisibleYear = currentYear,
firstDayOfWeek = firstDayOfWeek,
)
HorizontalYearCalendar(
state = state,
dayContent = { Day(it) },
yearHeader = { YearHeader(it) },
monthHeader = { MonthHeader(it) },
)
// If you need a vertical year calendar.
// VerticalYearCalendar(
// state = state,
// dayContent = { Day(it) }
// )
}There is an additional outDateStyle parameter that can be provided when creating the state
via rememberYearCalendarState. This determines how the out-dates are generated. See
the properties section to understand this parameter.
A year calendar implementation from the sample app:
The year calendar composables also provide a parameter isMonthVisible which determines if a month
is added to the calendar year grid. For example, if you want a calendar that starts in the year 2024
and ends in the year 2054, but only shows months from October 2024, the logic would look like
this:
@Composable
fun MainScreen() {
val october2024 = remember { YearMonth.of(2024, Month.OCTOBER) }
val startYear = remember { Year.of(2024) }
val endYear = remember { Year.of(2054) }
val firstDayOfWeek = remember { firstDayOfWeekFromLocale() }
val state = rememberYearCalendarState(
startYear = startYear,
endYear = endYear,
firstVisibleYear = startYear,
firstDayOfWeek = firstDayOfWeek,
)
HorizontalYearCalendar(
state = state,
dayContent = { Day(it) },
yearHeader = { YearHeader(it) },
monthHeader = { MonthHeader(it) },
isMonthVisible = { data ->
data.yearMonth >= october2024
}
)
}The logic above will produce this result:
Remember that all the screenshots shown so far are just examples of what you can achieve with the library and you can absolutely build your calendar to look however you want.
Made a cool calendar with this library? Share an image here.
MigrationGuide (`docs/MigrationGuide.md`)
Migration Guide
View
1.x.x => 2.x.x
If you're upgrading from version 1.x.x to 2.x.x, please keep in mind that version 2 is a total rewrite of the library with focus on performance. With that in mind, here are the breaking changes:
async()methods have been removed as the calendar is now capable of handling really large date ranges without performance issues. The calendar data is generated as needed. The removed methods aresetupAsync()andupdateMonthRangeAsync(). Please use the methodssetup()andupdateMonthData().updateMonthRange()method has been renamed toupdateMonthData()as it now allows you to optionally change the first day of the week if needed.daySizeproperty is no longer theSizeclass type withheightandwidthvalues. It is now aDaySizeenum with three options -Square,SeventhWidthandFreeForm. Please see theDaySizeenum documentation to understand each value.inDateStyleenum property has been removed as it is now redundant. The in-dates are present in each month data, you can choose to hide them on the view or keep them.outDateStyleenum property is still available. However, there are now two optionsEndOfRowandEndOfGrid. The previously available third optionNonehas been removed. You can simply hide the out-dates if you don't need them.scrollModeenum property is now a boolean type namedscrollPaged. Set this property tofalseto have the previousScrollMode.CONTINUOUSscroll behavior ortrueto have the previousScrollMode.PAGEDbehavior.DayOwnerenum has been renamed toDayPosition. The matching case values are:DayOwner.PREVIOUS_MONTH=>DayPosition.InDateDayOwner.THIS_MONTH=>DayPosition.MonthDateDayOwner.NEXT_MONTH=>DayPosition.OutDate
maxRowCountproperty has been removed as there is now aWeekCalendarViewclass that should be used if a week-based calendar is needed. The mainCalendarViewclass is used for the month calendar implementation.hasBoundariesproperty has been removed as it is no longer needed with the introduction of the week and month calendar implementations discussed above.monthMarginStart|monthMarginTop|monthMarginEnd|monthMarginBottomproperties have been merged into onemonthMarginsproperty.monthPaddingStart|monthPaddingTop|monthPaddingEnd|monthPaddingBottomproperties have been removed as they provided no real benefit. You can create a custommonthViewClassand set the paddings there if needed.
0.3.x => 0.4.x / 1.x.x
If you're upgrading from version 0.3.x to 0.4.x or 1.x.x, the main change is that the library moved from using ThreeTenABP to Java 8 API desugaring for dates. After following the new setup instructions, the next thing you need to do is change your imports for date/time related classes from org.threeten.bp.* to java.time.*.
You also need to remove the line AndroidThreeTen.init(this) from the onCreate() method of your application class as it's no longer needed.
Compose
Nothing to see here... yet ๐
View (`docs/View.md`)
Calendar View Documentation
Table of Contents
Quick links
Check out the sample app if you have not yet done so. Most techniques that you would want to implement are already done in the examples.
Download the sample app here
Read the sample app's source code here
Add the library to your project here
If you are looking for the compose documentation, you can find it here
Class information
The library can be used via three classes:
CalendarView: The typical month-based calendar.
WeekCalendarView: The week-based calendar.
YearCalendarView: The year-based calendar.
These classes extend from RecyclerView so you can use all RecyclerView customizations like decorators etc.
In the examples below, we will mostly use the CalendarView class since the three classes share the same basic concept. If you want a week-based calendar, replace CalendarView in your xml/code with WeekCalendarView. If you want a year-based calendar, replace CalendarView in your xml/code with YearCalendarView.
Most xml attributes and class properties/methods with the name prefix/suffix month (e.g monthHeaderResource) in the CalendarView will have an equivalent with the name prefix/suffix week (e.g weekHeaderResource) in the WeekCalendarView and the name prefix/suffix year (e.g yearHeaderResource) in the YearCalendarView.
Usage
Step 1
Add CalendarView to your XML like any other view.
<com.kizitonwose.calendar.view.CalendarView
android:id="@+id/calendarView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cv_dayViewResource="@layout/calendar_day_layout" />See all available attributes.
Create your day view resource in res/layout/calendar_day_layout.xml.
<TextView
android:id="@+id/calendarDayText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="16sp"
tools:text="22" />Create your view container which acts as a view holder for each date cell.
The view passed in here is the inflated day view resource which you provided.
class DayViewContainer(view: View) : ViewContainer(view) {
val textView = view.findViewById<TextView>(R.id.calendarDayText)
// With ViewBinding
// val textView = CalendarDayLayoutBinding.bind(view).calendarDayText
}Provide a MonthDayBinder for the CalendarView using your DayViewContainer type.
calendarView.dayBinder = object : MonthDayBinder<DayViewContainer> {
// Called only when a new container is needed.
override fun create(view: View) = DayViewContainer(view)
// Called every time we need to reuse a container.
override fun bind(container: DayViewContainer, data: CalendarDay) {
container.textView.text = data.date.dayOfMonth.toString()
}
}Step 2
Setup the desired dates in your Fragment or Activity:
CalendarView setup:
val currentMonth = YearMonth.now()
val startMonth = currentMonth.minusMonths(100) // Adjust as needed
val endMonth = currentMonth.plusMonths(100) // Adjust as needed
val firstDayOfWeek = firstDayOfWeekFromLocale() // Available from the library
calendarView.setup(startMonth, endMonth, firstDayOfWeek)
calendarView.scrollToMonth(currentMonth)WeekCalendarView setup:
- <com.kizitonwose.calendar.view.CalendarView
+ <com.kizitonwose.calendar.view.WeekCalendarView
android:id="@+id/weekCalendarView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cv_dayViewResource="@layout/calendar_day_layout" />val currentDate = LocalDate.now()
val currentMonth = YearMonth.now()
val startDate = currentMonth.minusMonths(100).atStartOfMonth() // Adjust as needed
val endDate = currentMonth.plusMonths(100).atEndOfMonth() // Adjust as needed
val firstDayOfWeek = firstDayOfWeekFromLocale() // Available from the library
weekCalendarView.setup(startDate, endDate, firstDayOfWeek)
weekCalendarView.scrollToWeek(currentDate)YearCalendarView setup:
- <com.kizitonwose.calendar.view.CalendarView
+ <com.kizitonwose.calendar.view.YearCalendarView
android:id="@+id/yearCalendarView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cv_dayViewResource="@layout/calendar_day_layout" />val currentYear = Year.now()
val startYear = currentYear.minusYears(100) // Adjust as needed
val endYear = currentYear.plusYears(100) // Adjust as needed
val firstDayOfWeek = firstDayOfWeekFromLocale() // Available from the library
yearCalendarView.setup(startYear, endYear, firstDayOfWeek)
yearCalendarView.scrollToYear(currentYear)And that's all you need for simple usage! But keep reading, there's more!
First day of the week and Day of week titles.
Of course, you want to show the day of week titles on the appropriate days on the calendar.
Sun | Mon | Tue | Wed | Thu | Fri | Sat
Here's a function that generates the weekdays from the user's current Locale.
val daysOfWeek = daysOfWeek() // Available in the libraryThe function takes a firstDayOfWeek parameter in case you want to generate the days of week such that the desired day is at the first position.
For example:
val daysOfWeek = daysOfWeek(firstDayOfWeek = DayOfWeek.THURSDAY)
// Will produce => Thu | Fri | Sat | Sun | Mon | Tue | WedUsing the daysOfWeek list, you can set up the calendar so the first day of the week is what the user would expect. This could be Sunday, Monday, etc. It is good practice to use what the Locale returns as that's what the user would expect.
To set up the calendar using the provided daysOfWeek list:
- val firstDayOfWeek = firstDayOfWeekFromLocale()
+ val daysOfWeek = daysOfWeek()
calendarView.setup(startMonth, endMonth, daysOfWeek.first())You should also use the daysOfWeek list values to set up the weekday titles, this way it matches what is shown on the CalendarView.
To set up the day of week titles, you can either use the month header which would show the titles on every month and allow the titles to scroll with the month, or you can show the title on a static view above the calendar. Both ways are covered below:
Setup days of week using a static view:
Step 1
Create your title text view in res/layout/calendar_day_title_text.xml.
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center" />Create a container resource to use the text in res/layout/calendar_day_titles_container.xml.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="7"
android:orientation="horizontal">
<include layout="@layout/calendar_day_title_text" />
<include layout="@layout/calendar_day_title_text" />
<include layout="@layout/calendar_day_title_text" />
<include layout="@layout/calendar_day_title_text" />
<include layout="@layout/calendar_day_title_text" />
<include layout="@layout/calendar_day_title_text" />
<include layout="@layout/calendar_day_title_text" />
</LinearLayout>Add the titles container in the same layout as the CalendarView:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include
android:id="@+id/titlesContainer"
layout="@layout/calendar_day_titles_container" />
<com.kizitonwose.calendar.view.CalendarView
android:id="@+id/calendarView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cv_dayViewResource="@layout/calendar_day_layout" />
</LinearLayout>Step 2
Now you can set up the titles using the daysOfWeek list discussed previously:
val titlesContainer = findViewById<ViewGroup>(R.id.titlesContainer)
titlesContainer.children
.map { it as TextView }
.forEachIndexed { index, textView ->
val dayOfWeek = daysOfWeek[index]
val title = dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.getDefault())
textView.text = title
}To use the titles as a month header so it scrolls with each month, continue to the month headers and footers section below!
Month headers and footers.
To add a header or footer to each month, the procedure is the same as we did for the day using the dayViewResource above, but instead of the day resource, you have to provide your monthHeaderResource or monthFooterResource attribute, then set the monthHeaderBinder or monthFooterBinder property of the CalendarView.
To add the days of week titles as the month header, we provide the title container as the header resource:
<com.kizitonwose.calendar.view.CalendarView
android:id="@+id/calendarView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cv_dayViewResource="@layout/calendar_day_layout"
app:cv_monthHeaderResource="@layout/calendar_day_titles_container" />Now we can use the header to show the title:
/* Detailed source-code truncated for AI context efficiency. */You can do more than just use the day titles as the header. For example, you can also show the month name if it is not already shown outside the calendar on a separate view. Feel free to get creative with your month headers and footers! For more complex usages, please see the sample project.
Attributes
XML (All prefixed `cv_` for clarity)
The following attributes are available for CalendarView, WeekCalendarView and YearCalendarView classes:
dayViewResource: The xml resource that is inflated and used as the day cell view. This must be provided.
scrollPaged: The scrolling behavior of the calendar. If
true, the calendar will snap to the nearest month or week (in the WeekCalendarView) after a scroll or swipe action. Iffalse, the calendar scrolls normally.daySize: Determines how the size of each day on the calendar is calculated. Can be one of three values:
- square: Each day will have both width and height matching the width of the calendar divided by 7.
- rectangle: Each day will have its width matching the width of the calendar divided by 7, and its height matching the height of the calendar divided by the number of weeks in the index - could be 4, 5 or 6 for the month calendar, and 1 for the week calendar.
Use this if you want each month or week to fill the parent's width and height. - seventhWidth: Each day will have its width matching the width of the calendar divided by 7. The day is allowed to determine its height by setting a specific value or using
LayoutParams.WRAP_CONTENT - freeForm: This day is allowed to determine its width and height by setting specific values or using
LayoutParams.WRAP_CONTENT.
The following attributes are available for CalendarView and YearCalendarView classes:
monthHeaderResource: The xml resource that is inflated and used as a header for each month.
monthFooterResource: The xml resource that is inflated and used as a footer for each month.
orientation: The calendar scroll direction, can be
horizontalorvertical. Default ishorizontal.monthViewClass: A ViewGroup that is instantiated and used as the container for each month. This class must have a constructor which takes only a Context. You should exclude the name and constructor of this class from code obfuscation if enabled.
outDateStyle: This determines how outDates are generated for each month on the calendar. Can be one of two values:
- endOfRow: The calendar will generate
outDatesuntil it reaches the end of the month row. This means that if a month has 5 rows, it will display 5 rows and if a month has 6 rows, it will display 6 rows. - endOfGrid: The calendar will generate
outDatesuntil it reaches the end of a 6 x 7 grid on each month. This means that all months will have 6 rows.
- endOfRow: The calendar will generate
If you are wondering what outDates and inDates mean, let's use the screenshot below as an example.
In the image, the dates within the green annotation are inDates, the ones within the red annotation are outDates while those without annotation are monthDates. You can check for this when binding your calendar. To achieve the exact effect on the image, we do this:
calendarView.dayBinder = object : MonthDayBinder<DayViewContainer> {
override fun create(view: View) = DayViewContainer(view)
override fun bind(container: DayViewContainer, data: CalendarDay) {
container.textView.text = data.date.dayOfMonth.toString()
if (data.position == DayPosition.MonthDate) {
container.textView.setTextColor(Color.WHITE)
} else {
container.textView.setTextColor(Color.GRAY)
}
}
}inDates have their position property set to DayPosition.InDate
outDates have their position property set to DayPosition.OutDate
monthDates have their position property set to DayPosition.MonthDate as seen in the code snippet above.
The following attributes are available for ONLY WeekCalendarView class:
weekHeaderResource: The xml resource that is inflated and used as a header for every week.
weekFooterResource: The xml resource that is inflated and used as a footer for every week.
weekViewClass: A ViewGroup that is instantiated and used as the container for each week. This class must have a constructor which takes only a Context. You should exclude the name and constructor of this class from code obfuscation if enabled.
The following attributes are available for ONLY YearCalendarView class:
yearHeaderResource: The xml resource that is inflated and used as a header for each year.
yearFooterResource: The xml resource that is inflated and used as a footer for each year.
yearViewClass: A ViewGroup that is instantiated and used as the container for each year. This class must have a constructor which takes only a Context. You should exclude the name and constructor of this class from code obfuscation if enabled.
monthColumns: The number of month columns in each year. Must be from 1 to 12.
monthHorizontalSpacing: The horizontal spacing between month columns in each year.
monthVerticalSpacing: The vertical spacing between month rows in each year.
monthHeight: This determines how the height of each month row on the calendar is calculated. Can be one of two values:
- followDaySize: Each month row height is determined by the
daySizevalue set on the calendar. Effectively, this iswrap-contentif the value isSquare,
SeventhWidth, orFreeForm, and will be equal to the calendar height divided by the number of rows if the value isRectangle. When used together withRectangle,
the calendar months and days will uniformly stretch to fill the parent's height. - fill: Each month row height will be the calendar height divided by the number of rows on the calendar. This means that the calendar months will be distributed
uniformly to fill the parent's height. However, the day content height will independently determine its height. This allows you to spread the calendar months evenly across the screen while
adaySizevalue ofSquareif you want square day content orSeventhWidthif you want to set a specific height value for the day content.
- followDaySize: Each month row height is determined by the
Properties
All the respective XML attributes listed above are also available as properties of the CalendarView and WeekCalendarView classes so they can be set via code. So in addition to those, we have:
CalendarView
monthScrollListener: Called when the calendar scrolls to a new month. Mostly beneficial if
scrollPagedistrue.dayBinder: An instance of
MonthDayBinderfor managing day cell views.monthHeaderBinder: An instance of
MonthHeaderFooterBinderfor managing header views. The header view is shown above each month on the calendar.monthFooterBinder: An instance of
MonthHeaderFooterBinderfor managing footer views. The footer view is shown below each month on the calendar.monthMargins: The margins, in pixels to be applied on each month view. This can be used to add a space between two months.
WeekCalendarView
weekScrollListener: Called when the calendar scrolls to a new week. Mostly beneficial if
scrollPagedistrue.dayBinder: An instance of
WeekDayBinderfor managing day cell views.weekHeaderBinder: An instance of
WeekHeaderFooterBinderfor managing the header views shown above each week on the calendar.weekFooterBinder: An instance of
WeekHeaderFooterBinderfor managing the footer views shown below each week on the calendar.weekMargins: The margins, in pixels to be applied on each week view. This can be used to add a space between two weeks.
YearCalendarView
yearScrollListener: Called when the calendar scrolls to a new year. Mostly beneficial if
scrollPagedistrue.dayBinder: An instance of
MonthDayBinderfor managing day cell views.monthHeaderBinder: An instance of
MonthHeaderFooterBinderfor managing the header views shown above each month on the calendar.monthFooterBinder: An instance of
MonthHeaderFooterBinderfor managing the footer views shown below each month on the calendar.monthMargins: The margins, in pixels to be applied on each month view. This can be used to add a space between two months.
yearHeaderBinder: An instance of
YearHeaderFooterBinderfor managing the header views shown above each year on the calendar.yearFooterBinder: An instance of
YearHeaderFooterBinderfor managing the footer views shown below each year on the calendar.yearMargins: The margins, in pixels to be applied on each year view. This is the container in which the year header, body and footer are placed. For example, this can be used to add a space between two years.
yearBodyMargins: The margins, in pixels to be applied on each year body view. This is the grid in which the months in each year are shown, excluding the year header and footer.
Methods
CalendarView
scrollToDate(date: LocalDate): Scroll to a specific date on the calendar. Use
smoothScrollToDate()to get a smooth scrolling animation.scrollToMonth(month: YearMonth): Scroll to a month on the calendar. Use
smoothScrollToMonth()to get a smooth scrolling animation.notifyDateChanged(date: LocalDate): Reload the view for the specified date.
notifyMonthChanged(month: YearMonth): Reload the header, body and footer views for the specified month.
notifyCalendarChanged(): Reload the entire calendar.
findFirstVisibleMonth() and findLastVisibleMonth(): Find the first and last visible months on the calendar respectively.
findFirstVisibleDay() and findLastVisibleDay(): Find the first and last visible days on the calendar respectively.
updateMonthData(): Update the calendar's start month or end month or the first day of week after the initial setup. The currently visible month is preserved. The calendar can handle really large date ranges so you may want to setup the calendar with a large date range instead of updating the range frequently.
WeekCalendarView
scrollToDate(date: LocalDate): Scroll to a specific date on the calendar. Use
smoothScrollToDate()to get a smooth scrolling animation.scrollToWeek(date: LocalDate): Scroll to the week containing this date on the calendar. Use
smoothScrollToWeek()to get a smooth scrolling animation.notifyDateChanged(date: LocalDate): Reload the view for the specified date.
notifyWeekChanged(date: LocalDate): Reload the header, body and footer views for the week containing this date.
notifyCalendarChanged(): Reload the entire calendar.
findFirstVisibleWeek() and findLastVisibleWeek(): Find the first and last visible weeks on the calendar respectively.
findFirstVisibleDay() and findLastVisibleDay(): Find the first and last visible days on the calendar respectively.
updateWeekData(): Update the calendar's start date or end date or the first day of week after the initial setup. The currently visible week is preserved. The calendar can handle really large date ranges so you may want to setup the calendar with a large date range instead of updating the range frequently.
YearCalendarView
scrollToDate(date: LocalDate): Scroll to a specific date on the calendar. Use
smoothScrollToDate()to get a smooth scrolling animation.scrollToMonth(month: YearMonth): Scroll to a month on the calendar. Use
smoothScrollToMonth()to get a smooth scrolling animation.scrollToYear(year: Year): Scroll to a year on the calendar. Use
smoothScrollToYear()to get a smooth scrolling animation.notifyDateChanged(date: LocalDate): Reload the view for the specified date.
notifyMonthChanged(month: YearMonth): Reload the header, body and footer views for the specified month.
notifyYearChanged(year: Year): Reload the header, body (all months in the year) and footer views for the specified year.
notifyCalendarChanged(): Reload the entire calendar.
findFirstVisibleYear() and findLastVisibleYear(): Find the first and last visible years on the calendar respectively.
findFirstVisibleMonth() and findLastVisibleMonth(): Find the first and last visible months on the calendar respectively.
findFirstVisibleDay() and findLastVisibleDay(): Find the first and last visible days on the calendar respectively.
updateYearData(): Update the calendar's start year or end year or the first day of week after the initial setup. The currently visible year is preserved. The calendar can handle really large date ranges so you may want to setup the calendar with a large date range instead of updating the range frequently.
There's no need to list all available methods or repeat the documentation here. Please see
the CalendarView, WeekCalendarView and YearCalendarView
classes for all properties and methods available with proper documentation.
Date clicks
You should set a click listener on the view which is provided to the view container.
XML file for the date cell calendar_day_layout.xml:
<TextView
android:id="@+id/calendarDayText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="16sp"
tools:text="22" />Of course, you need to set the file as cv_dayViewResource on the CalendarView:
<com.kizitonwose.calendarview.CalendarView
android:id="@+id/calendarView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cv_dayViewResource="@layout/calendar_day_layout" />Click listener implementation in your Fragment or Activity:
class DayViewContainer(view: View) : ViewContainer(view) {
val textView = view.findViewById<TextView>(R.id.calendarDayText)
// Will be set when this container is bound
lateinit var day: CalendarDay
init {
view.setOnClickListener {
// Use the CalendarDay associated with this container.
}
}
}
calendarView.dayBinder = object : MonthDayBinder<DayViewContainer> {
override fun create(view: View) = DayViewContainer(view)
override fun bind(container: DayViewContainer, data: CalendarDay) {
// Set the calendar day for this container.
container.day = data
// Set the date text
container.textView.text = data.date.dayOfMonth.toString()
// Any other binding logic
}
}Date Selection
The library has no inbuilt concept of selected/unselected dates, this gives you the freedom to choose how best you would like to implement this use case.
Implementing date selection is as simple as showing a background on a specific date in the date binder. Remember that since CalendarView, WeekCalendarView and YearCalendarView all extend from RecyclerView, you need to undo any special effects on dates where it is not needed.
For this example, I want only the last clicked date to be selected on the calendar.
Firstly, let's keep a reference to the selected date:
private var selectedDate: LocalDate? = nullNext, using the click logic on the view container already shown in the date click section above, we update this field whenever a date is clicked and show the selection background on the clicked date.
view.setOnClickListener {
// Check the day position as we do not want to select in or out dates.
if (day.position == DayPosition.MonthDate) {
// Keep a reference to any previous selection
// in case we overwrite it and need to reload it.
val currentSelection = selectedDate
if (currentSelection == day.date) {
// If the user clicks the same date, clear selection.
selectedDate = null
// Reload this date so the dayBinder is called
// and we can REMOVE the selection background.
calendarView.notifyDateChanged(currentSelection)
} else {
selectedDate = day.date
// Reload the newly selected date so the dayBinder is
// called and we can ADD the selection background.
calendarView.notifyDateChanged(day.date)
if currentSelection != null {
// We need to also reload the previously selected
// date so we can REMOVE the selection background.
calendarView.notifyDateChanged(currentSelection)
}
}
}
}Lastly, we implement the dayBinder to reflect the selection accordingly:
calendarView.dayBinder = object : MonthDayBinder<DayViewContainer> {
override fun create(view: View) = DayViewContainer(view)
override fun bind(container: DayViewContainer, data: CalendarDay) {
container.day = data
val day = data
val textView = container.textView
textView.text = day.date.dayOfMonth.toString()
if (day.position == DayPosition.MonthDate) {
// Show the month dates. Remember that views are reused!
textView.visibility = View.VISIBLE
if (day.date == selectedDate) {
// If this is the selected date, show a round background and change the text color.
textView.setTextColor(Color.WHITE)
textView.setBackgroundResource(R.drawable.selection_background)
} else {
// If this is NOT the selected date, remove the background and reset the text color.
textView.setTextColor(Color.BLACK)
textView.background = null
}
} else {
// Hide in and out dates
textView.visibility = View.INVISIBLE
}
}
}For more complex selection logic like range selection, please see the sample project. It's quite simple, the magic is all in your binding logic!
Disabling dates
As expected, the library does not provide this logic internally so you have complete flexibility.
To disable dates, you can simply set the texts on those dates to look disabled and ignore clicks on those dates. For example, if we want to show in and out dates but disable them so that they cannot be selected, we can just set the alpha property for those dates in the dayBinder to give the effect of being disabled. Of course, you can set a different color if that's what you prefer.
Continuing with the example in the date selection section, we already ignore clicks for in and out dates using this logic:
view.setOnClickListener {
// Check the day position as we do not want to select in or out dates.
if (day.position == DayPosition.MonthDate) {
// Only use month dates
}
}Then in the dayBinder, we check the day position and set the text alpha or color accordingly:
calendarView.dayBinder = object : MonthDayBinder<DayViewContainer> {
override fun create(view: View) = DayViewContainer(view)
override fun bind(container: DayViewContainer, data: CalendarDay) {
container.day = data
val textView = container.textView
textView.text = data.date.dayOfMonth.toString()
textView.alpha = if (day.position == DayPosition.MonthDate) 1f else 0.3f
}And we've now covered the typical usage. The beauty of the library is in its limitless possibilities. You are not constrained on how to build your user interface, the library provides you with the needed calendar data logic and you provide the desired UI logic.
See the sample project for some complex implementations.
Week view
The WeekCalendarView class is a week-based calendar implementation. Almost all topics covered above for the month calendar will apply to the week calendar. The main difference is that the xml attributes and class properties/methods will have a slightly different name, typically with a week prefix/suffix instead of month.
For example: monthHeaderResource => weekHeaderResource, scrollToMonth() => scrollToWeek(), findFirstVisibleMonth() => findFirstVisibleWeek() and many others, but you get the idea.
To show the week calendar in your layout, add the view:
<com.kizitonwose.calendar.view.WeekCalendarView
android:id="@+id/weekCalendarView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cv_dayViewResource="@layout/calendar_day_layout" />Then follow the setup instructions above to provide a day resource/binder etc as you would do for the month calendar.
If you would like to toggle the calendar between month and week views, please see the sample app where we did this using a ValueAnimator. You can use whichever animation logic you prefer. The result is shown below:
If you wish to show more or less than 7 days at a time on the week calendar, you should set the scrollPaged attribute to false. Also, set the daySize property to FreeForm which gives you the freedom to define a preferred size for your day cells. Please read the documentation in the DaySize class to fully understand the available options.
A week calendar implementation from the sample app:
Year view
The YearCalendarView class is a year-based calendar implementation. All topics covered above for the month calendar will apply to the year calendar. The year calendar also has additional xml attributes and class properties/methods, typically with a year prefix/suffix.
For example: yearHeaderResource, scrollToYear(), findFirstVisibleYear() and many others, but you get the idea.
To show the year calendar in your layout, add the view:
<com.kizitonwose.calendar.view.YearCalendarView
android:id="@+id/yearCalendarView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cv_dayViewResource="@layout/calendar_day_layout" />Then follow the setup instructions above to provide a day resource/binder etc as you would do for the month calendar.
A year calendar implementation from the sample app:
Remember that all the screenshots shown so far are just examples of what you can achieve with the library and you can absolutely build your calendar to look however you want.
Made a cool calendar with this library? Share an image here.
FAQ
Q: How do I use this library in a Java project?
A: It works out of the box, however, the MonthScrollListener, WeekScrollListener and YearScrollListener are not interfaces but Kotlin functions. To set the listener in a Java project see this.
Q: How do I disable user scrolling on the calendar so I can only scroll programmatically?
A: See this.
Migration
Please see the migration guide.
--- METRICS ---
- Files Extracted: 4
- Estimated Token Budget: ~19292 tokens
- Canonical Reference: https://codewiki.google/github.com/kizitonwose/Calendar