From aea1992d86f9e8b2e1f67c84edc3181a2df15860 Mon Sep 17 00:00:00 2001 From: Kizito Nwose Date: Tue, 14 Jan 2025 20:07:54 +0100 Subject: [PATCH] Allow overriding `calculateExtraLayoutSpace` in the layout manager (#601) --- view/api/view.api | 14 ++++++++++++ .../kizitonwose/calendar/view/CalendarView.kt | 6 +++++ .../kizitonwose/calendar/view/LayoutHelper.kt | 22 +++++++++++++++++++ .../calendar/view/WeekCalendarView.kt | 6 +++++ .../calendar/view/YearCalendarView.kt | 6 +++++ .../view/internal/CalendarLayoutManager.kt | 15 +++++++++++++ .../MonthCalendarLayoutManager.kt | 2 ++ .../weekcalendar/WeekCalendarLayoutManager.kt | 2 ++ .../yearcalendar/YearCalendarLayoutManager.kt | 17 ++++++++------ 9 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 view/src/main/java/com/kizitonwose/calendar/view/LayoutHelper.kt diff --git a/view/api/view.api b/view/api/view.api index c7b0741c..510d93f2 100644 --- a/view/api/view.api +++ b/view/api/view.api @@ -14,6 +14,7 @@ public class com/kizitonwose/calendar/view/CalendarView : androidx/recyclerview/ public final fun getDayBinder ()Lcom/kizitonwose/calendar/view/MonthDayBinder; public final fun getDaySize ()Lcom/kizitonwose/calendar/view/DaySize; public final fun getDayViewResource ()I + public final fun getLayoutHelper ()Lcom/kizitonwose/calendar/view/LayoutHelper; public final fun getMonthFooterBinder ()Lcom/kizitonwose/calendar/view/MonthHeaderFooterBinder; public final fun getMonthFooterResource ()I public final fun getMonthHeaderBinder ()Lcom/kizitonwose/calendar/view/MonthHeaderFooterBinder; @@ -39,6 +40,7 @@ public class com/kizitonwose/calendar/view/CalendarView : androidx/recyclerview/ public final fun setDayBinder (Lcom/kizitonwose/calendar/view/MonthDayBinder;)V public final fun setDaySize (Lcom/kizitonwose/calendar/view/DaySize;)V public final fun setDayViewResource (I)V + public final fun setLayoutHelper (Lcom/kizitonwose/calendar/view/LayoutHelper;)V public final fun setMonthFooterBinder (Lcom/kizitonwose/calendar/view/MonthHeaderFooterBinder;)V public final fun setMonthFooterResource (I)V public final fun setMonthHeaderBinder (Lcom/kizitonwose/calendar/view/MonthHeaderFooterBinder;)V @@ -72,6 +74,14 @@ public final class com/kizitonwose/calendar/view/DaySize : java/lang/Enum { public static fun values ()[Lcom/kizitonwose/calendar/view/DaySize; } +public abstract interface class com/kizitonwose/calendar/view/LayoutHelper { + public abstract fun calculateExtraLayoutSpace (Landroidx/recyclerview/widget/RecyclerView$State;[I)V +} + +public final class com/kizitonwose/calendar/view/LayoutHelper$DefaultImpls { + public static fun calculateExtraLayoutSpace (Lcom/kizitonwose/calendar/view/LayoutHelper;Landroidx/recyclerview/widget/RecyclerView$State;[I)V +} + public final class com/kizitonwose/calendar/view/MarginValues { public static final field Companion Lcom/kizitonwose/calendar/view/MarginValues$Companion; public fun ()V @@ -128,6 +138,7 @@ public class com/kizitonwose/calendar/view/WeekCalendarView : androidx/recyclerv public final fun getDayBinder ()Lcom/kizitonwose/calendar/view/WeekDayBinder; public final fun getDaySize ()Lcom/kizitonwose/calendar/view/DaySize; public final fun getDayViewResource ()I + public final fun getLayoutHelper ()Lcom/kizitonwose/calendar/view/LayoutHelper; public final fun getScrollPaged ()Z public final fun getWeekFooterBinder ()Lcom/kizitonwose/calendar/view/WeekHeaderFooterBinder; public final fun getWeekFooterResource ()I @@ -148,6 +159,7 @@ public class com/kizitonwose/calendar/view/WeekCalendarView : androidx/recyclerv public final fun setDayBinder (Lcom/kizitonwose/calendar/view/WeekDayBinder;)V public final fun setDaySize (Lcom/kizitonwose/calendar/view/DaySize;)V public final fun setDayViewResource (I)V + public final fun setLayoutHelper (Lcom/kizitonwose/calendar/view/LayoutHelper;)V public final fun setScrollPaged (Z)V public final fun setWeekFooterBinder (Lcom/kizitonwose/calendar/view/WeekHeaderFooterBinder;)V public final fun setWeekFooterResource (I)V @@ -187,6 +199,7 @@ public class com/kizitonwose/calendar/view/YearCalendarView : androidx/recyclerv public final fun getDayBinder ()Lcom/kizitonwose/calendar/view/MonthDayBinder; public final fun getDaySize ()Lcom/kizitonwose/calendar/view/DaySize; public final fun getDayViewResource ()I + public final fun getLayoutHelper ()Lcom/kizitonwose/calendar/view/LayoutHelper; public final fun getMonthColumns ()I public final fun getMonthFooterBinder ()Lcom/kizitonwose/calendar/view/MonthHeaderFooterBinder; public final fun getMonthFooterResource ()I @@ -225,6 +238,7 @@ public class com/kizitonwose/calendar/view/YearCalendarView : androidx/recyclerv public final fun setDayBinder (Lcom/kizitonwose/calendar/view/MonthDayBinder;)V public final fun setDaySize (Lcom/kizitonwose/calendar/view/DaySize;)V public final fun setDayViewResource (I)V + public final fun setLayoutHelper (Lcom/kizitonwose/calendar/view/LayoutHelper;)V public final fun setMonthColumns (I)V public final fun setMonthFooterBinder (Lcom/kizitonwose/calendar/view/MonthHeaderFooterBinder;)V public final fun setMonthFooterResource (I)V diff --git a/view/src/main/java/com/kizitonwose/calendar/view/CalendarView.kt b/view/src/main/java/com/kizitonwose/calendar/view/CalendarView.kt index 1810d1c7..d3330402 100644 --- a/view/src/main/java/com/kizitonwose/calendar/view/CalendarView.kt +++ b/view/src/main/java/com/kizitonwose/calendar/view/CalendarView.kt @@ -183,6 +183,12 @@ public open class CalendarView : RecyclerView { } } + /** + * Interface with methods that can be overridden + * in the internal layout manager. + */ + public var layoutHelper: LayoutHelper? = null + private val scrollListenerInternal = object : OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {} override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { diff --git a/view/src/main/java/com/kizitonwose/calendar/view/LayoutHelper.kt b/view/src/main/java/com/kizitonwose/calendar/view/LayoutHelper.kt new file mode 100644 index 00000000..b85d926a --- /dev/null +++ b/view/src/main/java/com/kizitonwose/calendar/view/LayoutHelper.kt @@ -0,0 +1,22 @@ +package com.kizitonwose.calendar.view + +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.kizitonwose.calendar.view.internal.CalendarLayoutManager + +/** + * An interface with methods that can be overridden + * in the internal [LinearLayoutManager]. + */ +public interface LayoutHelper { + /** + * Calculates the amount of extra space (in pixels) that should be laid out by + * [CalendarLayoutManager] and stores the result in [extraLayoutSpace]. + * [extraLayoutSpace[0]] should be used for the extra space at the top in a vertical + * calendar or left in a horizontal calendar, and extraLayoutSpace[1] should be used for + * the extra space at the bottom in a vertical calendar or right in a horizontal calendar. + * + * @see [LinearLayoutManager.calculateExtraLayoutSpace] + */ + public fun calculateExtraLayoutSpace(state: RecyclerView.State, extraLayoutSpace: IntArray) {} +} diff --git a/view/src/main/java/com/kizitonwose/calendar/view/WeekCalendarView.kt b/view/src/main/java/com/kizitonwose/calendar/view/WeekCalendarView.kt index 578b5629..966d8bd4 100644 --- a/view/src/main/java/com/kizitonwose/calendar/view/WeekCalendarView.kt +++ b/view/src/main/java/com/kizitonwose/calendar/view/WeekCalendarView.kt @@ -150,6 +150,12 @@ public open class WeekCalendarView : RecyclerView { } } + /** + * Interface with methods that can be overridden + * in the internal layout manager. + */ + public var layoutHelper: LayoutHelper? = null + private val scrollListenerInternal = object : OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {} override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { diff --git a/view/src/main/java/com/kizitonwose/calendar/view/YearCalendarView.kt b/view/src/main/java/com/kizitonwose/calendar/view/YearCalendarView.kt index a40f4588..f28f4d5f 100644 --- a/view/src/main/java/com/kizitonwose/calendar/view/YearCalendarView.kt +++ b/view/src/main/java/com/kizitonwose/calendar/view/YearCalendarView.kt @@ -326,6 +326,12 @@ public open class YearCalendarView : RecyclerView { } } + /** + * Interface with methods that can be overridden + * in the internal layout manager. + */ + public var layoutHelper: LayoutHelper? = null + private val scrollListenerInternal = object : OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {} override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { diff --git a/view/src/main/java/com/kizitonwose/calendar/view/internal/CalendarLayoutManager.kt b/view/src/main/java/com/kizitonwose/calendar/view/internal/CalendarLayoutManager.kt index 05d759ca..2de7b181 100644 --- a/view/src/main/java/com/kizitonwose/calendar/view/internal/CalendarLayoutManager.kt +++ b/view/src/main/java/com/kizitonwose/calendar/view/internal/CalendarLayoutManager.kt @@ -6,6 +6,7 @@ import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearSmoothScroller import androidx.recyclerview.widget.RecyclerView +import com.kizitonwose.calendar.view.LayoutHelper import com.kizitonwose.calendar.view.MarginValues internal abstract class CalendarLayoutManager( @@ -18,6 +19,7 @@ internal abstract class CalendarLayoutManager( abstract fun getItemMargins(): MarginValues abstract fun scrollPaged(): Boolean abstract fun notifyScrollListenerIfNeeded() + abstract fun getLayoutHelper(): LayoutHelper? fun scrollToIndex(indexData: IndexData) { val position = getaItemAdapterPosition(indexData) @@ -70,6 +72,19 @@ internal abstract class CalendarLayoutManager( } } + override fun calculateExtraLayoutSpace(state: RecyclerView.State, extraLayoutSpace: IntArray) { + val layoutHelper = getLayoutHelper() + if (layoutHelper != null) { + layoutHelper.calculateExtraLayoutSpace(state, extraLayoutSpace) + // If the interface is provided but the method is not overridden. + if (extraLayoutSpace.isEmpty()) { + super.calculateExtraLayoutSpace(state, extraLayoutSpace) + } + } else { + super.calculateExtraLayoutSpace(state, extraLayoutSpace) + } + } + private inner class CalendarSmoothScroller(position: Int, val day: DayData?) : LinearSmoothScroller(calView.context) { init { diff --git a/view/src/main/java/com/kizitonwose/calendar/view/internal/monthcalendar/MonthCalendarLayoutManager.kt b/view/src/main/java/com/kizitonwose/calendar/view/internal/monthcalendar/MonthCalendarLayoutManager.kt index 0cb3fbed..237e3461 100644 --- a/view/src/main/java/com/kizitonwose/calendar/view/internal/monthcalendar/MonthCalendarLayoutManager.kt +++ b/view/src/main/java/com/kizitonwose/calendar/view/internal/monthcalendar/MonthCalendarLayoutManager.kt @@ -2,6 +2,7 @@ package com.kizitonwose.calendar.view.internal.monthcalendar import com.kizitonwose.calendar.core.CalendarDay import com.kizitonwose.calendar.view.CalendarView +import com.kizitonwose.calendar.view.LayoutHelper import com.kizitonwose.calendar.view.MarginValues import com.kizitonwose.calendar.view.internal.CalendarLayoutManager import com.kizitonwose.calendar.view.internal.dayTag @@ -18,4 +19,5 @@ internal class MonthCalendarLayoutManager(private val calView: CalendarView) : override fun getItemMargins(): MarginValues = calView.monthMargins override fun scrollPaged(): Boolean = calView.scrollPaged override fun notifyScrollListenerIfNeeded() = adapter.notifyMonthScrollListenerIfNeeded() + override fun getLayoutHelper(): LayoutHelper? = calView.layoutHelper } diff --git a/view/src/main/java/com/kizitonwose/calendar/view/internal/weekcalendar/WeekCalendarLayoutManager.kt b/view/src/main/java/com/kizitonwose/calendar/view/internal/weekcalendar/WeekCalendarLayoutManager.kt index 98add2e8..4cbe1058 100644 --- a/view/src/main/java/com/kizitonwose/calendar/view/internal/weekcalendar/WeekCalendarLayoutManager.kt +++ b/view/src/main/java/com/kizitonwose/calendar/view/internal/weekcalendar/WeekCalendarLayoutManager.kt @@ -1,5 +1,6 @@ package com.kizitonwose.calendar.view.internal.weekcalendar +import com.kizitonwose.calendar.view.LayoutHelper import com.kizitonwose.calendar.view.MarginValues import com.kizitonwose.calendar.view.WeekCalendarView import com.kizitonwose.calendar.view.internal.CalendarLayoutManager @@ -17,4 +18,5 @@ internal class WeekCalendarLayoutManager(private val calView: WeekCalendarView) override fun getItemMargins(): MarginValues = calView.weekMargins override fun scrollPaged(): Boolean = calView.scrollPaged override fun notifyScrollListenerIfNeeded() = adapter.notifyWeekScrollListenerIfNeeded() + override fun getLayoutHelper(): LayoutHelper? = calView.layoutHelper } diff --git a/view/src/main/java/com/kizitonwose/calendar/view/internal/yearcalendar/YearCalendarLayoutManager.kt b/view/src/main/java/com/kizitonwose/calendar/view/internal/yearcalendar/YearCalendarLayoutManager.kt index 44638c5d..bb003abf 100644 --- a/view/src/main/java/com/kizitonwose/calendar/view/internal/yearcalendar/YearCalendarLayoutManager.kt +++ b/view/src/main/java/com/kizitonwose/calendar/view/internal/yearcalendar/YearCalendarLayoutManager.kt @@ -5,6 +5,7 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.LinearSmoothScroller import com.kizitonwose.calendar.core.CalendarDay +import com.kizitonwose.calendar.view.LayoutHelper import com.kizitonwose.calendar.view.MarginValues import com.kizitonwose.calendar.view.YearCalendarView import com.kizitonwose.calendar.view.internal.CalendarLayoutManager @@ -24,6 +25,8 @@ internal class YearCalendarLayoutManager(private val calView: YearCalendarView) override fun getItemMargins(): MarginValues = calView.yearMargins override fun scrollPaged(): Boolean = calView.scrollPaged override fun notifyScrollListenerIfNeeded() = adapter.notifyYearScrollListenerIfNeeded() + override fun getLayoutHelper(): LayoutHelper? = calView.layoutHelper + fun smoothScrollToMonth(month: YearMonth) { val indexPosition = adapter.getAdapterPosition(month) if (indexPosition == NO_INDEX) return @@ -42,18 +45,18 @@ internal class YearCalendarLayoutManager(private val calView: YearCalendarView) calView.post { val itemView = calView.findViewHolderForAdapterPosition(indexPosition)?.itemView ?: return@post - val offset = calculateDayViewOffsetInParent(month, itemView) + val offset = calculateMonthViewOffsetInParent(month, itemView) scrollToPositionWithOffset(indexPosition, -offset) calView.post { notifyScrollListenerIfNeeded() } } } } - private fun calculateDayViewOffsetInParent(month: YearMonth, itemView: View): Int { - val dayView = itemView.findViewWithTag(monthTag(month)) ?: return 0 + private fun calculateMonthViewOffsetInParent(month: YearMonth, itemView: View): Int { + val monthView = itemView.findViewWithTag(monthTag(month)) ?: return 0 val rect = Rect() - dayView.getDrawingRect(rect) - (itemView as ViewGroup).offsetDescendantRectToMyCoords(dayView, rect) + monthView.getDrawingRect(rect) + (itemView as ViewGroup).offsetDescendantRectToMyCoords(monthView, rect) return if (orientation == VERTICAL) rect.top else rect.left } @@ -72,7 +75,7 @@ internal class YearCalendarLayoutManager(private val calView: YearCalendarView) if (month == null) { return dy } - val offset = calculateDayViewOffsetInParent(month, view) + val offset = calculateMonthViewOffsetInParent(month, view) return dy - offset } @@ -81,7 +84,7 @@ internal class YearCalendarLayoutManager(private val calView: YearCalendarView) if (month == null) { return dx } - val offset = calculateDayViewOffsetInParent(month, view) + val offset = calculateMonthViewOffsetInParent(month, view) return dx - offset } }