From dd2b05afa03790a093b87d3475cb0eae0bbb54a6 Mon Sep 17 00:00:00 2001 From: Kizito Nwose Date: Sat, 11 Apr 2020 08:17:49 +0200 Subject: [PATCH 1/2] Fix pagination when the Calendar size is larger than the month size. --- .../kizitonwose/calendarview/CalendarView.kt | 3 +- .../calendarview/ui/CalenderPageSnapHelper.kt | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 library/src/main/java/com/kizitonwose/calendarview/ui/CalenderPageSnapHelper.kt diff --git a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt index 90baf49e..a9385e19 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.* @@ -563,7 +562,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/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 + } +} From 4e3865936af30745c851722d6022c26bf1d52a2d Mon Sep 17 00:00:00 2001 From: Kizito Nwose Date: Sat, 11 Apr 2020 09:45:22 +0200 Subject: [PATCH 2/2] Force the calender height to match the current month's height in vertical, paged mode. --- library/build.gradle | 3 ++- .../kizitonwose/calendarview/CalendarView.kt | 13 ++++++++++ .../calendarview/ui/CalendarAdapter.kt | 24 ++++++++++++------- library/src/main/res/values/attrs.xml | 6 +++++ 4 files changed, 37 insertions(+), 9 deletions(-) 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 a9385e19..cd0cbfdc 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt @@ -197,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 @@ -233,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() } 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/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