Calendar Documentation

A highly customizable calendar view and compose library for Android and Kotlin Multiplatform.

RAW Doc

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:

groovy
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:

kotlin
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:

kotlin
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

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:

kotlin
@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:

kotlin
@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:

kotlin
@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:

kotlin
@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.

kotlin
val daysOfWeek = daysOfWeek() // Available in the library

The 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:

kotlin
val daysOfWeek = daysOfWeek(firstDayOfWeek = DayOfWeek.THURSDAY)
// Will produce => Thu | Fri | Sat | Sun | Mon | Tue | Wed

Using 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:

diff
- 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:

kotlin
@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:

kotlin
@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:

kotlin
@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:

kotlin
@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:

kotlin
@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. When false, 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 and startMonth will 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 modifier parameter. 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 LazyListLayoutInfo calculated 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:

    1. EndOfRow: The calendar will generate outDates until 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.
    2. EndOfGrid: The calendar will generate outDates until 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 = ...).

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:

kotlin
@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 LazyListLayoutInfo calculated 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 LazyListLayoutInfo calculated 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:

kotlin
@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:

kotlin
@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:

kotlin
@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:

kotlin
@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:

kotlin
@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:

kotlin
@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:

kotlin
@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:

kotlin
@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 are setupAsync() and updateMonthRangeAsync(). Please use the methods setup() and updateMonthData().

  • updateMonthRange() method has been renamed to updateMonthData() as it now allows you to optionally change the first day of the week if needed.

  • daySize property is no longer the Size class type with height and width values. It is now a DaySize enum with three options - Square, SeventhWidth and FreeForm. Please see the DaySize enum documentation to understand each value.

  • inDateStyle enum 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.

  • outDateStyle enum property is still available. However, there are now two options EndOfRow and EndOfGrid. The previously available third option None has been removed. You can simply hide the out-dates if you don't need them.

  • scrollMode enum property is now a boolean type named scrollPaged. Set this property to false to have the previous ScrollMode.CONTINUOUS scroll behavior or true to have the previous ScrollMode.PAGED behavior.

  • DayOwner enum has been renamed to DayPosition. The matching case values are:

    • DayOwner.PREVIOUS_MONTH => DayPosition.InDate
    • DayOwner.THIS_MONTH => DayPosition.MonthDate
    • DayOwner.NEXT_MONTH => DayPosition.OutDate
  • maxRowCount property has been removed as there is now a WeekCalendarView class that should be used if a week-based calendar is needed. The main CalendarView class is used for the month calendar implementation.

  • hasBoundaries property has been removed as it is no longer needed with the introduction of the week and month calendar implementations discussed above.

  • monthMarginStart | monthMarginTop | monthMarginEnd | monthMarginBottom properties have been merged into one monthMargins property.

  • monthPaddingStart | monthPaddingTop | monthPaddingEnd | monthPaddingBottom properties have been removed as they provided no real benefit. You can create a custom monthViewClass and 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

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.

xml
<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.

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.

kotlin
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.

kotlin
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:

kotlin
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:

diff
- <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" />
kotlin
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:

diff
- <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" />
kotlin
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.

kotlin
val daysOfWeek = daysOfWeek() // Available in the library

The 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:

kotlin
val daysOfWeek = daysOfWeek(firstDayOfWeek = DayOfWeek.THURSDAY)
// Will produce => Thu | Fri | Sat | Sun | Mon | Tue | Wed

Using 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:

diff
- 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.

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.

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:

xml
<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:

kotlin
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:

xml
<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:

text
/* 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. If false, the calendar scrolls normally.

  • daySize: Determines how the size of each day on the calendar is calculated. Can be one of three values:

    1. square: Each day will have both width and height matching the width of the calendar divided by 7.
    2. 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.
    3. 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
    4. 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 horizontal or vertical. Default is horizontal.

  • 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:

    1. endOfRow: The calendar will generate outDates until 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.
    2. endOfGrid: The calendar will generate outDates until it reaches the end of a 6 x 7 grid on each month. This means that all months will have 6 rows.

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:

kotlin
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:

    1. followDaySize: Each month row height is determined by the daySize value set on the calendar. Effectively, this is wrap-content if the value is Square,
      SeventhWidth, or FreeForm, and will be equal to the calendar height divided by the number of rows if the value is Rectangle. When used together with Rectangle,
      the calendar months and days will uniformly stretch to fill the parent's height.
    2. 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
      a daySize value of Square if you want square day content or SeventhWidth if you want to set a specific height value for the day content.

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 scrollPaged is true.

  • dayBinder: An instance of MonthDayBinder for managing day cell views.

  • monthHeaderBinder: An instance of MonthHeaderFooterBinder for managing header views. The header view is shown above each month on the calendar.

  • monthFooterBinder: An instance of MonthHeaderFooterBinder for 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 scrollPaged is true.

  • dayBinder: An instance of WeekDayBinder for managing day cell views.

  • weekHeaderBinder: An instance of WeekHeaderFooterBinder for managing the header views shown above each week on the calendar.

  • weekFooterBinder: An instance of WeekHeaderFooterBinder for 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 scrollPaged is true.

  • dayBinder: An instance of MonthDayBinder for managing day cell views.

  • monthHeaderBinder: An instance of MonthHeaderFooterBinder for managing the header views shown above each month on the calendar.

  • monthFooterBinder: An instance of MonthHeaderFooterBinder for 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 YearHeaderFooterBinder for managing the header views shown above each year on the calendar.

  • yearFooterBinder: An instance of YearHeaderFooterBinder for 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:

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:

xml
<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:

kotlin
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:

kotlin
private var selectedDate: LocalDate? = null

Next, 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.

kotlin
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:

kotlin
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:

kotlin
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:

kotlin
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:

xml
<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:

xml
<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 ---