diff --git a/library/build.gradle b/library/build.gradle
index af7acb9c..69ca09ee 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -33,6 +33,7 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin_lang"
+ implementation "androidx.core:core-ktx:$versions.androidx_core_ktx"
// Expose ThreeTenABP so library users can use it directly.
api "com.jakewharton.threetenabp:threetenabp:$versions.threetenabp"
@@ -50,7 +51,7 @@ dependencies {
api "androidx.recyclerview:recyclerview:$versions.recyclerview"
testImplementation "junit:junit:$versions.junit"
- testImplementation('org.threeten:threetenbp:1.3.7') {
+ testImplementation('org.threeten:threetenbp:1.4.2') {
// Use threetenBP library for tests as context will
// not be available to initialise threetenABP
exclude group: 'com.jakewharton.threetenabp', module: 'threetenabp'
diff --git a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt
index 90baf49e..cd0cbfdc 100644
--- a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt
+++ b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt
@@ -6,7 +6,6 @@ import android.view.View.MeasureSpec.UNSPECIFIED
import android.view.ViewGroup
import androidx.annotation.Px
import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import com.kizitonwose.calendarview.model.*
import com.kizitonwose.calendarview.ui.*
@@ -198,6 +197,15 @@ open class CalendarView : RecyclerView {
}
}
+ /**
+ * The duration in milliseconds of the animation used to adjust the CalendarView's
+ * height when [scrollMode] is [ScrollMode.PAGED] and the CalendarView's height is
+ * set to `wrap_content`. The height change happens when the CalendarView scrolls to
+ * a month which has less or more rows than the previous one. Default value is 200.
+ * To disable the animation, set this value to zero.
+ */
+ var wrappedPageHeightAnimationDuration = 200
+
private var startMonth: YearMonth? = null
private var endMonth: YearMonth? = null
private var firstDayOfWeek: DayOfWeek? = null
@@ -234,6 +242,10 @@ open class CalendarView : RecyclerView {
maxRowCount = a.getInt(R.styleable.CalendarView_cv_maxRowCount, maxRowCount)
monthViewClass = a.getString(R.styleable.CalendarView_cv_monthViewClass)
hasBoundaries = a.getBoolean(R.styleable.CalendarView_cv_hasBoundaries, hasBoundaries)
+ wrappedPageHeightAnimationDuration = a.getInt(
+ R.styleable.CalendarView_cv_wrappedPageHeightAnimationDuration,
+ wrappedPageHeightAnimationDuration
+ )
a.recycle()
}
@@ -563,7 +575,7 @@ open class CalendarView : RecyclerView {
}
}
- private val pagerSnapHelper = PagerSnapHelper()
+ private val pagerSnapHelper = CalenderPageSnapHelper()
/**
* Setup the CalendarView. You can call this any time to change the
diff --git a/library/src/main/java/com/kizitonwose/calendarview/ui/CalendarAdapter.kt b/library/src/main/java/com/kizitonwose/calendarview/ui/CalendarAdapter.kt
index 78513d80..90e6e04b 100644
--- a/library/src/main/java/com/kizitonwose/calendarview/ui/CalendarAdapter.kt
+++ b/library/src/main/java/com/kizitonwose/calendarview/ui/CalendarAdapter.kt
@@ -1,5 +1,6 @@
package com.kizitonwose.calendarview.ui
+import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Rect
import android.os.Build
@@ -8,6 +9,7 @@ import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.annotation.LayoutRes
import androidx.core.view.ViewCompat
+import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import com.kizitonwose.calendarview.CalendarView
import com.kizitonwose.calendarview.model.*
@@ -164,6 +166,7 @@ internal class CalendarAdapter(
private var visibleMonth: CalendarMonth? = null
private var calWrapsHeight: Boolean? = null
+ private var initialLayout = true
fun notifyMonthScrollListenerIfNeeded() {
// Guard for cv.post() calls and other callbacks which use this method.
if (!isAttached) return
@@ -194,12 +197,14 @@ internal class CalendarAdapter(
// calculating height and uses the tallest one of the three meaning that the current index's
// view will end up having a blank space at the bottom unless the immediate previous and next
// indices are also missing the last row. I think there should be a better way to fix this.
- if (calView.isHorizontal && calView.scrollMode == ScrollMode.PAGED) {
+ // New: Also fixes issue where the calendar does not wrap each month's height when in vertical,
+ // paged mode and just matches parent's height instead.
+ if (calView.scrollMode == ScrollMode.PAGED) {
val calWrapsHeight = calWrapsHeight ?: (calView.layoutParams.height == LP.WRAP_CONTENT).also {
// We modify the layoutParams so we save the initial value set by the user.
calWrapsHeight = it
}
- if (calWrapsHeight.not()) return // Bug only happens when the CalenderView wraps its height.
+ if (!calWrapsHeight) return // Bug only happens when the CalenderView wraps its height.
val visibleVH =
calView.findViewHolderForAdapterPosition(visibleItemPos) as? MonthViewHolder ?: return
val newHeight = visibleVH.headerView?.height.orZero() +
@@ -208,13 +213,16 @@ internal class CalendarAdapter(
// by checking the number of visible(non-empty) rows.
visibleMonth.weekDays.size * calView.dayHeight +
visibleVH.footerView?.height.orZero()
- if (calView.layoutParams.height != newHeight) {
- calView.layoutParams = calView.layoutParams.apply {
- this.height = newHeight
- }
- visibleVH.itemView.layoutParams = visibleVH.itemView.layoutParams.apply {
- this.height = newHeight
+ if (calView.height != newHeight) {
+ ValueAnimator.ofInt(calView.height, newHeight).apply {
+ duration = if (initialLayout) 0 else calView.wrappedPageHeightAnimationDuration.toLong()
+ addUpdateListener {
+ calView.updateLayoutParams { height = it.animatedValue as Int }
+ visibleVH.itemView.requestLayout()
+ }
+ start()
}
+ if (initialLayout) initialLayout = false
}
}
}
diff --git a/library/src/main/java/com/kizitonwose/calendarview/ui/CalenderPageSnapHelper.kt b/library/src/main/java/com/kizitonwose/calendarview/ui/CalenderPageSnapHelper.kt
new file mode 100644
index 00000000..622ee38d
--- /dev/null
+++ b/library/src/main/java/com/kizitonwose/calendarview/ui/CalenderPageSnapHelper.kt
@@ -0,0 +1,50 @@
+package com.kizitonwose.calendarview.ui
+
+import android.view.View
+import androidx.recyclerview.widget.OrientationHelper
+import androidx.recyclerview.widget.PagerSnapHelper
+import androidx.recyclerview.widget.RecyclerView
+
+class CalenderPageSnapHelper : PagerSnapHelper() {
+
+ /**
+ * The default implementation of this method in [PagerSnapHelper.calculateDistanceToFinalSnap] uses the distance
+ * between the target view center vs RecyclerView center as final snap distance. This does not always give the
+ * desired result for calendar usage. For example in a vertical calendar when the RecyclerView is taller than
+ * the item view(e.g two or more visible months), we don't actually want the item view's center to be at the
+ * center of the RecyclerView when it snaps but instead we want the item view and RecyclerView top(in vertical)
+ * or left(in horizontal) to match at the end of the snap.
+ */
+ override fun calculateDistanceToFinalSnap(layoutManager: RecyclerView.LayoutManager, targetView: View): IntArray {
+ return IntArray(2).apply {
+ this[0] = if (layoutManager.canScrollHorizontally())
+ distanceToStart(targetView, getHorizontalHelper(layoutManager)) else 0
+
+ this[1] = if (layoutManager.canScrollVertically())
+ distanceToStart(targetView, getVerticalHelper(layoutManager)) else 0
+ }
+ }
+
+ private fun distanceToStart(targetView: View, helper: OrientationHelper): Int {
+ val childStart = (helper.getDecoratedStart(targetView))
+ val containerStart = helper.startAfterPadding
+ return childStart - containerStart
+ }
+
+ private lateinit var verticalHelper: OrientationHelper
+ private lateinit var horizontalHelper: OrientationHelper
+
+ private fun getVerticalHelper(layoutManager: RecyclerView.LayoutManager): OrientationHelper {
+ if (!::verticalHelper.isInitialized || verticalHelper.layoutManager != layoutManager) {
+ verticalHelper = OrientationHelper.createVerticalHelper(layoutManager)
+ }
+ return verticalHelper
+ }
+
+ private fun getHorizontalHelper(layoutManager: RecyclerView.LayoutManager): OrientationHelper {
+ if (!::horizontalHelper.isInitialized || horizontalHelper.layoutManager != layoutManager) {
+ horizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager)
+ }
+ return horizontalHelper
+ }
+}
diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml
index 91a4d929..79704cec 100644
--- a/library/src/main/res/values/attrs.xml
+++ b/library/src/main/res/values/attrs.xml
@@ -70,5 +70,11 @@
+
+
+
\ No newline at end of file