From 91cd87a0261e3054871f37e250dce60c08d3af06 Mon Sep 17 00:00:00 2001 From: luis Date: Fri, 17 Jul 2020 16:46:36 -0400 Subject: [PATCH 01/14] #183 Removing redundant linear layout. In order to preserve the order of views added to the rootLayout, inflation of week and day views was moved to the main onCreateViewHolder method. Moving all inflation to onCreateViewHolder (from the constructor of MonthViewHolder) also allows us to remove the DayConfig dependency from MonthViewHolder and WeekViewHolder as these classes don't actually need it. --- .../calendarview/ui/CalendarAdapter.kt | 28 +++++++++---------- .../calendarview/ui/MonthViewHolder.kt | 13 +-------- .../kizitonwose/calendarview/ui/WeekHolder.kt | 4 +-- 3 files changed, 15 insertions(+), 30 deletions(-) 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 ffb77f61..e0fd09b1 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/ui/CalendarAdapter.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/ui/CalendarAdapter.kt @@ -37,9 +37,6 @@ internal class CalendarAdapter( private val months: List get() = monthConfig.months - val bodyViewId = ViewCompat.generateViewId() - val rootViewId = ViewCompat.generateViewId() - // Values of headerViewId & footerViewId will be // replaced with IDs set in the XML if present. var headerViewId = ViewCompat.generateViewId() @@ -66,7 +63,6 @@ internal class CalendarAdapter( val context = parent.context val rootLayout = LinearLayout(context).apply { orientation = LinearLayout.VERTICAL - id = rootViewId } if (viewConfig.monthHeaderRes != 0) { @@ -80,12 +76,14 @@ internal class CalendarAdapter( rootLayout.addView(monthHeaderView) } - val monthBodyLayout = LinearLayout(context).apply { - layoutParams = LinearLayout.LayoutParams(LP.WRAP_CONTENT, LP.WRAP_CONTENT) - orientation = LinearLayout.VERTICAL - id = bodyViewId - } - rootLayout.addView(monthBodyLayout) + @Suppress("UNCHECKED_CAST") val dayConfig = DayConfig( + calView.dayWidth, calView.dayHeight, viewConfig.dayViewRes, + calView.dayBinder as DayBinder + ) + + val weekHolders = (1..6) + .map { WeekHolder(createDayHolders(dayConfig)) } + .onEach { weekHolder -> rootLayout.addView(weekHolder.inflateWeekView(rootLayout)) } if (viewConfig.monthFooterRes != 0) { val monthFooterView = rootLayout.inflate(viewConfig.monthFooterRes) @@ -130,16 +128,16 @@ internal class CalendarAdapter( @Suppress("UNCHECKED_CAST") return MonthViewHolder( - this, userRoot, - DayConfig( - calView.dayWidth, calView.dayHeight, viewConfig.dayViewRes, - calView.dayBinder as DayBinder - ), + this, + userRoot, + weekHolders, calView.monthHeaderBinder as MonthHeaderFooterBinder?, calView.monthFooterBinder as MonthHeaderFooterBinder? ) } + private fun createDayHolders(dayConfig: DayConfig) = (1..7).map { DayHolder(dayConfig) } + override fun onBindViewHolder(holder: MonthViewHolder, position: Int, payloads: List) { if (payloads.isEmpty()) { super.onBindViewHolder(holder, position, payloads) diff --git a/library/src/main/java/com/kizitonwose/calendarview/ui/MonthViewHolder.kt b/library/src/main/java/com/kizitonwose/calendarview/ui/MonthViewHolder.kt index 6856f7b6..a8abb464 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/ui/MonthViewHolder.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/ui/MonthViewHolder.kt @@ -2,7 +2,6 @@ package com.kizitonwose.calendarview.ui import android.view.View import android.view.ViewGroup -import android.widget.LinearLayout import androidx.recyclerview.widget.RecyclerView import com.kizitonwose.calendarview.model.CalendarDay import com.kizitonwose.calendarview.model.CalendarMonth @@ -10,29 +9,19 @@ import com.kizitonwose.calendarview.model.CalendarMonth internal class MonthViewHolder constructor( adapter: CalendarAdapter, rootLayout: ViewGroup, - dayConfig: DayConfig, + private val weekHolders: List, private var monthHeaderBinder: MonthHeaderFooterBinder?, private var monthFooterBinder: MonthHeaderFooterBinder? ) : RecyclerView.ViewHolder(rootLayout) { - private val weekHolders = (1..6).map { WeekHolder(dayConfig) } - val headerView: View? = rootLayout.findViewById(adapter.headerViewId) val footerView: View? = rootLayout.findViewById(adapter.footerViewId) - val bodyLayout: LinearLayout = rootLayout.findViewById(adapter.bodyViewId) private var headerContainer: ViewContainer? = null private var footerContainer: ViewContainer? = null lateinit var month: CalendarMonth - init { - // Add week rows. - weekHolders.forEach { - bodyLayout.addView(it.inflateWeekView(bodyLayout)) - } - } - fun bindMonth(month: CalendarMonth) { this.month = month headerView?.let { view -> diff --git a/library/src/main/java/com/kizitonwose/calendarview/ui/WeekHolder.kt b/library/src/main/java/com/kizitonwose/calendarview/ui/WeekHolder.kt index 4ab350c7..c760436c 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/ui/WeekHolder.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/ui/WeekHolder.kt @@ -5,9 +5,7 @@ import android.view.ViewGroup import android.widget.LinearLayout import com.kizitonwose.calendarview.model.CalendarDay -internal class WeekHolder(dayConfig: DayConfig) { - - val dayHolders = (1..7).map { DayHolder(dayConfig) } +internal class WeekHolder(val dayHolders: List) { private lateinit var container: LinearLayout From 902cb1839396125b7ded5c9807a9e8ef7c9d6377 Mon Sep 17 00:00:00 2001 From: luis Date: Sat, 18 Jul 2020 12:02:26 -0400 Subject: [PATCH 02/14] #183 Using flatMap in place of map() + flatten() and using view visibility extensions. --- .../java/com/kizitonwose/calendarview/ui/DayHolder.kt | 10 ++++++---- .../com/kizitonwose/calendarview/ui/MonthViewHolder.kt | 2 +- .../java/com/kizitonwose/calendarview/ui/WeekHolder.kt | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt b/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt index 2e343172..a956da9b 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt @@ -6,6 +6,8 @@ import android.widget.FrameLayout import android.widget.LinearLayout import androidx.annotation.LayoutRes import androidx.annotation.Px +import androidx.core.view.isGone +import androidx.core.view.isVisible import com.kizitonwose.calendarview.model.CalendarDay import com.kizitonwose.calendarview.utils.inflate @@ -55,12 +57,12 @@ internal class DayHolder(private val config: DayConfig) { } if (currentDay != null) { - if (containerView.visibility != View.VISIBLE) { - containerView.visibility = View.VISIBLE + if (!containerView.isVisible) { + containerView.isVisible = true } config.viewBinder.bind(viewContainer, currentDay) - } else if (containerView.visibility != View.GONE) { - containerView.visibility = View.GONE + } else if (!containerView.isGone) { + containerView.isGone = true } } diff --git a/library/src/main/java/com/kizitonwose/calendarview/ui/MonthViewHolder.kt b/library/src/main/java/com/kizitonwose/calendarview/ui/MonthViewHolder.kt index a8abb464..4762c133 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/ui/MonthViewHolder.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/ui/MonthViewHolder.kt @@ -42,6 +42,6 @@ internal class MonthViewHolder constructor( } fun reloadDay(day: CalendarDay) { - weekHolders.map { it.dayHolders }.flatten().firstOrNull { it.day == day }?.reloadView() + weekHolders.flatMap { it.dayHolders }.firstOrNull { it.day == day }?.reloadView() } } diff --git a/library/src/main/java/com/kizitonwose/calendarview/ui/WeekHolder.kt b/library/src/main/java/com/kizitonwose/calendarview/ui/WeekHolder.kt index c760436c..27ed0912 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/ui/WeekHolder.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/ui/WeekHolder.kt @@ -3,6 +3,7 @@ package com.kizitonwose.calendarview.ui import android.view.View import android.view.ViewGroup import android.widget.LinearLayout +import androidx.core.view.isGone import com.kizitonwose.calendarview.model.CalendarDay internal class WeekHolder(val dayHolders: List) { @@ -25,7 +26,7 @@ internal class WeekHolder(val dayHolders: List) { } fun bindWeekView(daysOfWeek: List) { - container.visibility = if (daysOfWeek.isEmpty()) View.GONE else View.VISIBLE + container.isGone = daysOfWeek.isEmpty() dayHolders.forEachIndexed { index, holder -> // Indices can be null if OutDateStyle is NONE. We set the // visibility for the views at these indices to INVISIBLE. From 8709ba6a23ca05202efa4e0f4ebfc9a6c04711aa Mon Sep 17 00:00:00 2001 From: luis Date: Sat, 18 Jul 2020 12:36:49 -0400 Subject: [PATCH 03/14] Retrying GitHub Actions From 72dede53db82b3157e185337813d4d4464d9b32f Mon Sep 17 00:00:00 2001 From: luis Date: Wed, 22 Jul 2020 13:31:28 -0400 Subject: [PATCH 04/14] #183 Removing extraneous FrameLayout. Two main things needed to change in order for this to work out. First, the way DayHolder was tied to a CalendarDay needed to change from assigning a view id on the frame layout to assigning a tag on dateView. Second, the width and height of dateView had to account for any margins the user may have supplied. --- .../calendarview/ui/CalendarAdapter.kt | 2 +- .../calendarview/ui/CalendarLayoutManager.kt | 2 +- .../kizitonwose/calendarview/ui/DayHolder.kt | 37 ++++++++----------- .../calenderviewsample/CalenderViewTests.kt | 17 +++++---- 4 files changed, 26 insertions(+), 32 deletions(-) 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 e0fd09b1..8bc6cec8 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/ui/CalendarAdapter.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/ui/CalendarAdapter.kt @@ -316,7 +316,7 @@ internal class CalendarAdapter( return months[visibleIndex].weekDays.flatten() .run { if (isFirst) this else reversed() } .firstOrNull { - val dayView = visibleItemView.findViewById(it.date.hashCode()) ?: return@firstOrNull false + val dayView = visibleItemView.findViewWithTag(it.date.hashCode()) ?: return@firstOrNull false dayView.getGlobalVisibleRect(dayRect) dayRect.intersect(monthRect) } diff --git a/library/src/main/java/com/kizitonwose/calendarview/ui/CalendarLayoutManager.kt b/library/src/main/java/com/kizitonwose/calendarview/ui/CalendarLayoutManager.kt index cf84d367..92f883d7 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/ui/CalendarLayoutManager.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/ui/CalendarLayoutManager.kt @@ -60,7 +60,7 @@ internal class CalendarLayoutManager(private val calView: CalendarView, @Recycle } private fun calculateDayViewOffsetInParent(day: CalendarDay, itemView: View): Int { - val dayView = itemView.findViewById(day.date.hashCode()) ?: return 0 + val dayView = itemView.findViewWithTag(day.date.hashCode()) ?: return 0 val rect = Rect() dayView.getDrawingRect(rect) (itemView as ViewGroup).offsetDescendantRectToMyCoords(dayView, rect) diff --git a/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt b/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt index a956da9b..0d839ace 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt @@ -1,13 +1,12 @@ package com.kizitonwose.calendarview.ui import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout import android.widget.LinearLayout import androidx.annotation.LayoutRes import androidx.annotation.Px -import androidx.core.view.isGone -import androidx.core.view.isVisible +import androidx.core.view.* +import androidx.core.view.MarginLayoutParamsCompat.getMarginEnd +import androidx.core.view.MarginLayoutParamsCompat.getMarginStart import com.kizitonwose.calendarview.model.CalendarDay import com.kizitonwose.calendarview.utils.inflate @@ -21,28 +20,22 @@ internal data class DayConfig( internal class DayHolder(private val config: DayConfig) { private lateinit var dateView: View - private lateinit var containerView: FrameLayout private lateinit var viewContainer: ViewContainer var day: CalendarDay? = null fun inflateDayView(parent: LinearLayout): View { dateView = parent.inflate(config.dayViewRes).apply { - // We ensure the layout params of the supplied child view is - // MATCH_PARENT so it fills the parent container. - layoutParams = layoutParams.apply { - height = ViewGroup.LayoutParams.MATCH_PARENT - width = ViewGroup.LayoutParams.MATCH_PARENT - } - } - containerView = FrameLayout(parent.context).apply { // This will be placed in the WeekLayout(A LinearLayout) hence we // use LinearLayout.LayoutParams and set the weight appropriately. // The parent's wightSum is already set to 7 to accommodate seven week days. - layoutParams = LinearLayout.LayoutParams(config.width, config.height, 1F) - addView(dateView) + updateLayoutParams { + width = config.width - getMarginStart(this) - getMarginEnd(this) + height = config.height - marginTop - marginBottom + weight = 1f + } } - return containerView + return dateView } fun bindDayView(currentDay: CalendarDay?) { @@ -52,17 +45,17 @@ internal class DayHolder(private val config: DayConfig) { } val dayHash = currentDay?.date.hashCode() - if (containerView.id != dayHash) { - containerView.id = dayHash + if (viewContainer.view.tag != dayHash) { + viewContainer.view.tag = dayHash } if (currentDay != null) { - if (!containerView.isVisible) { - containerView.isVisible = true + if (!viewContainer.view.isVisible) { + viewContainer.view.isVisible = true } config.viewBinder.bind(viewContainer, currentDay) - } else if (!containerView.isGone) { - containerView.isGone = true + } else if (!viewContainer.view.isGone) { + viewContainer.view.isGone = true } } diff --git a/sample/src/androidTest/java/com/kizitonwose/calenderviewsample/CalenderViewTests.kt b/sample/src/androidTest/java/com/kizitonwose/calenderviewsample/CalenderViewTests.kt index 799fba86..b96fd797 100644 --- a/sample/src/androidTest/java/com/kizitonwose/calenderviewsample/CalenderViewTests.kt +++ b/sample/src/androidTest/java/com/kizitonwose/calenderviewsample/CalenderViewTests.kt @@ -22,6 +22,7 @@ import com.kizitonwose.calendarview.ui.ViewContainer import com.kizitonwose.calendarview.utils.yearMonth import com.kizitonwose.calendarviewsample.* import org.junit.After +import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule @@ -136,7 +137,7 @@ class CalenderViewTests { val calendarView = findFragment().findViewById(R.id.exFiveCalendar) - assertTrue(calendarView.findViewById(currentMonth.atDay(1).hashCode()) != null) + assertTrue(calendarView.findViewWithTag(currentMonth.atDay(1).hashCode()) != null) val nextFourMonths = currentMonth.plusMonths(4) @@ -146,8 +147,8 @@ class CalenderViewTests { sleep(2000) - assertTrue(calendarView.findViewById(currentMonth.atDay(1).hashCode()) == null) - assertTrue(calendarView.findViewById(nextFourMonths.atDay(1).hashCode()) != null) + assertTrue(calendarView.findViewWithTag(currentMonth.atDay(1).hashCode()) == null) + assertTrue(calendarView.findViewWithTag(nextFourMonths.atDay(1).hashCode()) != null) } @Test @@ -164,7 +165,7 @@ class CalenderViewTests { sleep(2000) - val dayView = calendarView.findViewById(targetDate.hashCode()) + val dayView = calendarView.findViewWithTag(targetDate.hashCode()) val calendarViewRect = Rect() calendarView.getGlobalVisibleRect(calendarViewRect) @@ -172,7 +173,7 @@ class CalenderViewTests { val dayViewRect = Rect() dayView.getGlobalVisibleRect(dayViewRect) - assertTrue(calendarViewRect.top == dayViewRect.top) + assertEquals(calendarViewRect.top, dayViewRect.top) } @Test @@ -189,7 +190,7 @@ class CalenderViewTests { sleep(2000) - val dayView = calendarView.findViewById(targetDate.hashCode()) + val dayView = calendarView.findViewWithTag(targetDate.hashCode()) val calendarViewRect = Rect() calendarView.getGlobalVisibleRect(calendarViewRect) @@ -197,7 +198,7 @@ class CalenderViewTests { val dayViewRect = Rect() dayView.getGlobalVisibleRect(dayViewRect) - assertTrue(calendarViewRect.left == dayViewRect.left) + assertEquals(calendarViewRect.left, dayViewRect.left) } @Test @@ -219,7 +220,7 @@ class CalenderViewTests { sleep(5000) // Enough time for smooth scrolling animation. - assertTrue(targetCalMonth?.yearMonth == targetMonth) + assertEquals(targetCalMonth?.yearMonth, targetMonth) } @Test From 191edd2cdb471d2c7bc2a47137540110651e74ed Mon Sep 17 00:00:00 2001 From: luis Date: Wed, 22 Jul 2020 14:01:03 -0400 Subject: [PATCH 05/14] #183 Improving encapsulation and simplifying view reload logic. --- .../com/kizitonwose/calendarview/ui/DayHolder.kt | 12 ++++++++---- .../kizitonwose/calendarview/ui/MonthViewHolder.kt | 2 +- .../com/kizitonwose/calendarview/ui/WeekHolder.kt | 4 +++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt b/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt index 0d839ace..5b57a50b 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt @@ -21,8 +21,7 @@ internal class DayHolder(private val config: DayConfig) { private lateinit var dateView: View private lateinit var viewContainer: ViewContainer - - var day: CalendarDay? = null + private var day: CalendarDay? = null fun inflateDayView(parent: LinearLayout): View { dateView = parent.inflate(config.dayViewRes).apply { @@ -59,7 +58,12 @@ internal class DayHolder(private val config: DayConfig) { } } - fun reloadView() { - bindDayView(day) + fun reloadViewIfNecessary(day: CalendarDay): Boolean { + return if (day == this.day) { + bindDayView(this.day) + true + } else { + false + } } } diff --git a/library/src/main/java/com/kizitonwose/calendarview/ui/MonthViewHolder.kt b/library/src/main/java/com/kizitonwose/calendarview/ui/MonthViewHolder.kt index 4762c133..beb017ba 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/ui/MonthViewHolder.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/ui/MonthViewHolder.kt @@ -42,6 +42,6 @@ internal class MonthViewHolder constructor( } fun reloadDay(day: CalendarDay) { - weekHolders.flatMap { it.dayHolders }.firstOrNull { it.day == day }?.reloadView() + weekHolders.find { it.reloadDay(day) } } } diff --git a/library/src/main/java/com/kizitonwose/calendarview/ui/WeekHolder.kt b/library/src/main/java/com/kizitonwose/calendarview/ui/WeekHolder.kt index 27ed0912..ab04d03a 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/ui/WeekHolder.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/ui/WeekHolder.kt @@ -6,7 +6,7 @@ import android.widget.LinearLayout import androidx.core.view.isGone import com.kizitonwose.calendarview.model.CalendarDay -internal class WeekHolder(val dayHolders: List) { +internal class WeekHolder(private val dayHolders: List) { private lateinit var container: LinearLayout @@ -33,4 +33,6 @@ internal class WeekHolder(val dayHolders: List) { holder.bindDayView(daysOfWeek.getOrNull(index)) } } + + fun reloadDay(day: CalendarDay): Boolean = dayHolders.any { it.reloadViewIfNecessary(day) } } From 6085657ab0b1a7fafd027fcd11680c7435c267c7 Mon Sep 17 00:00:00 2001 From: luis Date: Wed, 22 Jul 2020 14:05:54 -0400 Subject: [PATCH 06/14] #183 Using fully qualified imports. --- .../main/java/com/kizitonwose/calendarview/ui/DayHolder.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt b/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt index 5b57a50b..a0f104fb 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt @@ -4,9 +4,13 @@ import android.view.View import android.widget.LinearLayout import androidx.annotation.LayoutRes import androidx.annotation.Px -import androidx.core.view.* import androidx.core.view.MarginLayoutParamsCompat.getMarginEnd import androidx.core.view.MarginLayoutParamsCompat.getMarginStart +import androidx.core.view.isGone +import androidx.core.view.isVisible +import androidx.core.view.marginBottom +import androidx.core.view.marginTop +import androidx.core.view.updateLayoutParams import com.kizitonwose.calendarview.model.CalendarDay import com.kizitonwose.calendarview.utils.inflate From 87e88f561765b7ff3130d3294505b61b0aa95bf3 Mon Sep 17 00:00:00 2001 From: Kizito Nwose Date: Thu, 23 Jul 2020 14:11:06 +0200 Subject: [PATCH 07/14] Add `configure` method for multiple property updates. --- .../kizitonwose/calendarview/CalendarView.kt | 35 +++++++++++++++++++ .../calendarviewsample/Example1Fragment.kt | 4 +-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt index fe573f78..661b9fd9 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt @@ -409,6 +409,11 @@ open class CalendarView : RecyclerView { // This removes all views but is internal. // removeAndRecycleViews() + if (isUpdating) { + pendingViewHolderInvalidation = true + return + } + if (adapter == null || layoutManager == null) return val state = layoutManager?.onSaveInstanceState() adapter = adapter @@ -417,6 +422,10 @@ open class CalendarView : RecyclerView { } private fun updateAdapterMonthConfig() { + if (isUpdating) { + pendingAdapterMonthConfigUpdate = true + return + } if (adapter != null) { calendarAdapter.monthConfig = MonthConfig( @@ -434,6 +443,10 @@ open class CalendarView : RecyclerView { } private fun updateAdapterViewConfig() { + if (isUpdating) { + pendingAdapterViewConfigUpdate = true + return + } if (adapter != null) { calendarAdapter.viewConfig = ViewConfig(dayViewResource, monthHeaderResource, monthFooterResource, monthViewClass) @@ -441,6 +454,28 @@ open class CalendarView : RecyclerView { } } + private var isUpdating = false + private var pendingAdapterMonthConfigUpdate = false + private var pendingAdapterViewConfigUpdate = false + private var pendingViewHolderInvalidation = false + + /** + * Update multiple properties of [CalendarView] without triggering multiple calls to + * [updateAdapterViewConfig], [updateAdapterMonthConfig] or [invalidateViewHolders]. + * Beneficial if you want to change multiple properties at once. + */ + fun configure(block: CalendarView.() -> Unit) { + isUpdating = true + this.block() + isUpdating = false + if (pendingAdapterMonthConfigUpdate) updateAdapterMonthConfig() + if (pendingAdapterViewConfigUpdate) updateAdapterViewConfig() + if (pendingViewHolderInvalidation && !pendingAdapterViewConfigUpdate) invalidateViewHolders() + pendingAdapterMonthConfigUpdate = false + pendingAdapterViewConfigUpdate = false + pendingViewHolderInvalidation = false + } + /** * Scroll to a specific month on the calendar. This only * shows the view for the month without any animations. diff --git a/sample/src/main/java/com/kizitonwose/calendarviewsample/Example1Fragment.kt b/sample/src/main/java/com/kizitonwose/calendarviewsample/Example1Fragment.kt index e48eb040..0b17d722 100644 --- a/sample/src/main/java/com/kizitonwose/calendarviewsample/Example1Fragment.kt +++ b/sample/src/main/java/com/kizitonwose/calendarviewsample/Example1Fragment.kt @@ -153,7 +153,7 @@ class Example1Fragment : BaseFragment(R.layout.example_1_fragment), HasToolbar { animator.doOnStart { if (!monthToWeek) { - binding.exOneCalendar.apply { + binding.exOneCalendar.configure { inDateStyle = InDateStyle.ALL_MONTHS maxRowCount = 6 hasBoundaries = true @@ -162,7 +162,7 @@ class Example1Fragment : BaseFragment(R.layout.example_1_fragment), HasToolbar { } animator.doOnEnd { if (monthToWeek) { - binding.exOneCalendar.apply { + binding.exOneCalendar.configure { inDateStyle = InDateStyle.FIRST_MONTH maxRowCount = 1 hasBoundaries = false From a6ee4210d1ccc5675624983e363a4bcfc936ea45 Mon Sep 17 00:00:00 2001 From: Kizito Nwose Date: Thu, 23 Jul 2020 21:21:44 +0200 Subject: [PATCH 08/14] Allow async calendar setup and start/end month updates. --- dependencies.gradle | 7 +- library/build.gradle | 5 +- .../kizitonwose/calendarview/CalendarView.kt | 158 ++++++++++++++---- .../calendarview/model/MonthConfig.kt | 18 +- sample/build.gradle | 2 +- 5 files changed, 141 insertions(+), 49 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index 4ffc8010..a9871d24 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -8,6 +8,7 @@ ext.versions = [ ] def espresso_version = "3.2.0" +def coroutines_version = "1.3.8" ext.deps = [ gradle_plugins: [ @@ -30,7 +31,11 @@ ext.deps = [ desugaring: "com.android.tools:desugar_jdk_libs:1.0.5", - kotlin_stdlib8: "org.jetbrains.kotlin:kotlin-stdlib-jdk8", + kotlin: [ + stdlib8: "org.jetbrains.kotlin:kotlin-stdlib-jdk8", + coroutines_core: "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version", + coroutines_android: "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" + ], test: [ junit: "junit:junit:4.12", diff --git a/library/build.gradle b/library/build.gradle index f2b856b3..bf4d889c 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -35,8 +35,11 @@ android { dependencies { coreLibraryDesugaring deps.desugaring - implementation deps.kotlin_stdlib8 + implementation deps.kotlin.stdlib8 implementation deps.androidx.core_ktx + implementation deps.kotlin.coroutines_core + implementation deps.kotlin.coroutines_android + // Expose RecyclerView which is CalendarView's superclass to // prevent a compile error when using the library in a project: diff --git a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt index 661b9fd9..9cf12228 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt @@ -5,13 +5,16 @@ import android.util.AttributeSet import android.view.View.MeasureSpec.UNSPECIFIED import android.view.ViewGroup import androidx.annotation.Px +import androidx.core.content.withStyledAttributes import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.kizitonwose.calendarview.model.* import com.kizitonwose.calendarview.ui.* +import kotlinx.coroutines.* import java.time.DayOfWeek import java.time.LocalDate import java.time.YearMonth +import kotlin.coroutines.CoroutineContext open class CalendarView : RecyclerView { @@ -233,22 +236,22 @@ open class CalendarView : RecyclerView { private fun init(attributeSet: AttributeSet, defStyleAttr: Int, defStyleRes: Int) { if (isInEditMode) return setHasFixedSize(true) - val a = context.obtainStyledAttributes(attributeSet, R.styleable.CalendarView, defStyleAttr, defStyleRes) - dayViewResource = a.getResourceId(R.styleable.CalendarView_cv_dayViewResource, dayViewResource) - monthHeaderResource = a.getResourceId(R.styleable.CalendarView_cv_monthHeaderResource, monthHeaderResource) - monthFooterResource = a.getResourceId(R.styleable.CalendarView_cv_monthFooterResource, monthFooterResource) - orientation = a.getInt(R.styleable.CalendarView_cv_orientation, orientation) - scrollMode = ScrollMode.values()[a.getInt(R.styleable.CalendarView_cv_scrollMode, scrollMode.ordinal)] - outDateStyle = OutDateStyle.values()[a.getInt(R.styleable.CalendarView_cv_outDateStyle, outDateStyle.ordinal)] - inDateStyle = InDateStyle.values()[a.getInt(R.styleable.CalendarView_cv_inDateStyle, inDateStyle.ordinal)] - 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() + context.withStyledAttributes(attributeSet, R.styleable.CalendarView, defStyleAttr, defStyleRes) { + dayViewResource = getResourceId(R.styleable.CalendarView_cv_dayViewResource, dayViewResource) + monthHeaderResource = getResourceId(R.styleable.CalendarView_cv_monthHeaderResource, monthHeaderResource) + monthFooterResource = getResourceId(R.styleable.CalendarView_cv_monthFooterResource, monthFooterResource) + orientation = getInt(R.styleable.CalendarView_cv_orientation, orientation) + scrollMode = ScrollMode.values()[getInt(R.styleable.CalendarView_cv_scrollMode, scrollMode.ordinal)] + outDateStyle = OutDateStyle.values()[getInt(R.styleable.CalendarView_cv_outDateStyle, outDateStyle.ordinal)] + inDateStyle = InDateStyle.values()[getInt(R.styleable.CalendarView_cv_inDateStyle, inDateStyle.ordinal)] + maxRowCount = getInt(R.styleable.CalendarView_cv_maxRowCount, maxRowCount) + monthViewClass = getString(R.styleable.CalendarView_cv_monthViewClass) + hasBoundaries = getBoolean(R.styleable.CalendarView_cv_hasBoundaries, hasBoundaries) + wrappedPageHeightAnimationDuration = getInt( + R.styleable.CalendarView_cv_wrappedPageHeightAnimationDuration, + wrappedPageHeightAnimationDuration + ) + } } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { @@ -621,6 +624,7 @@ open class CalendarView : RecyclerView { * @param firstDayOfWeek An instance of [DayOfWeek] enum to be the first day of week. */ fun setup(startMonth: YearMonth, endMonth: YearMonth, firstDayOfWeek: DayOfWeek) { + asyncJob?.cancel() if (this.startMonth != null && this.endMonth != null && this.firstDayOfWeek != null) { this.firstDayOfWeek = firstDayOfWeek updateMonthRange(startMonth, endMonth) @@ -628,16 +632,7 @@ open class CalendarView : RecyclerView { this.startMonth = startMonth this.endMonth = endMonth this.firstDayOfWeek = firstDayOfWeek - - // Remove the listener before adding again to prevent - // multiple additions if we already added it before. - removeOnScrollListener(scrollListenerInternal) - addOnScrollListener(scrollListenerInternal) - - layoutManager = CalendarLayoutManager(this, orientation) - adapter = CalendarAdapter( - this, - ViewConfig(dayViewResource, monthHeaderResource, monthFooterResource, monthViewClass), + finishSetup( MonthConfig( outDateStyle, inDateStyle, maxRowCount, startMonth, endMonth, firstDayOfWeek, hasBoundaries @@ -646,6 +641,48 @@ open class CalendarView : RecyclerView { } } + private var asyncJob: Job? = null + + @JvmOverloads + fun setupAsync( + startMonth: YearMonth, + endMonth: YearMonth, + firstDayOfWeek: DayOfWeek, + completion: (() -> Unit)? = null + ) { + asyncJob?.cancel() + if (this.startMonth != null && this.endMonth != null && this.firstDayOfWeek != null) { + this.firstDayOfWeek = firstDayOfWeek + updateMonthRangeAsync(startMonth, endMonth, completion) + } else { + this.startMonth = startMonth + this.endMonth = endMonth + this.firstDayOfWeek = firstDayOfWeek + asyncJob = GlobalScope.launch { + val monthConfig = MonthConfig( + outDateStyle, inDateStyle, maxRowCount, startMonth, + endMonth, firstDayOfWeek, hasBoundaries + ) + withContext(Dispatchers.Main) { + finishSetup(monthConfig) + completion?.invoke() + } + } + } + } + + private fun finishSetup(monthConfig: MonthConfig) { + removeOnScrollListener(scrollListenerInternal) + addOnScrollListener(scrollListenerInternal) + + layoutManager = CalendarLayoutManager(this, orientation) + adapter = CalendarAdapter( + this, + ViewConfig(dayViewResource, monthHeaderResource, monthFooterResource, monthViewClass), + monthConfig + ) + } + /** * Update the CalendarView's start month. * This can be called only if you have called [setup] in the past. @@ -656,6 +693,13 @@ open class CalendarView : RecyclerView { endMonth ?: throw IllegalStateException("`endMonth` is not set. Have you called `setup()`?") ) + @JvmOverloads + fun updateStartMonthAsync(startMonth: YearMonth, completion: (() -> Unit)? = null) = updateMonthRangeAsync( + startMonth, + endMonth ?: throw IllegalStateException("`endMonth` is not set. Have you called `setup()`?"), + completion + ) + /** * Update the CalendarView's end month. * This can be called only if you have called [setup] in the past. @@ -666,28 +710,68 @@ open class CalendarView : RecyclerView { endMonth ) + @JvmOverloads + fun updateEndMonthAsync(endMonth: YearMonth, completion: (() -> Unit)? = null) = updateMonthRangeAsync( + startMonth ?: throw IllegalStateException("`startMonth` is not set. Have you called `setup()`?"), + endMonth, + completion + ) + /** * Update the CalendarView's start and end months. * This can be called only if you have called [setup] in the past. * See [updateStartMonth] and [updateEndMonth]. */ fun updateMonthRange(startMonth: YearMonth, endMonth: YearMonth) { + asyncJob?.cancel() + this.startMonth = startMonth + this.endMonth = endMonth + val firstDayOfWeek = + firstDayOfWeek ?: throw IllegalStateException("`firstDayOfWeek` is not set. Have you called `setup()`?") + val (config, diff) = getMonthUpdateData(startMonth, endMonth, firstDayOfWeek) + finishUpdateMonthRange(config, diff) + } + + @JvmOverloads + fun updateMonthRangeAsync(startMonth: YearMonth, endMonth: YearMonth, completion: (() -> Unit)? = null) { + asyncJob?.cancel() this.startMonth = startMonth this.endMonth = endMonth + val firstDayOfWeek = + firstDayOfWeek ?: throw IllegalStateException("`firstDayOfWeek` is not set. Have you called `setup()`?") + asyncJob = GlobalScope.launch { + val (config, diff) = getMonthUpdateData(startMonth, endMonth, firstDayOfWeek) + withContext(Dispatchers.Main) { + finishUpdateMonthRange(config, diff) + completion?.invoke() + } + } + } - val oldConfig = calendarAdapter.monthConfig - val newConfig = MonthConfig( - outDateStyle, - inDateStyle, - maxRowCount, - startMonth, - endMonth, - firstDayOfWeek ?: throw IllegalStateException("`firstDayOfWeek` is not set. Have you called `setup()`?"), - hasBoundaries + private fun getMonthUpdateData( + startMonth: YearMonth, + endMonth: YearMonth, + firstDayOfWeek: DayOfWeek + ): Pair { + val monthConfig = MonthConfig( + outDateStyle, inDateStyle, maxRowCount, startMonth, + endMonth, firstDayOfWeek, hasBoundaries ) + val diffResult = DiffUtil.calculateDiff( + MonthRangeDiffCallback(calendarAdapter.monthConfig.months, monthConfig.months), + false + ) + return Pair(monthConfig, diffResult) + } + + private fun finishUpdateMonthRange(newConfig: MonthConfig, diffResult: DiffUtil.DiffResult) { calendarAdapter.monthConfig = newConfig - DiffUtil.calculateDiff(MonthRangeDiffCallback(oldConfig.months, newConfig.months), false) - .dispatchUpdatesTo(calendarAdapter) + diffResult.dispatchUpdatesTo(calendarAdapter) + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + asyncJob?.cancel() } private class MonthRangeDiffCallback( diff --git a/library/src/main/java/com/kizitonwose/calendarview/model/MonthConfig.kt b/library/src/main/java/com/kizitonwose/calendarview/model/MonthConfig.kt index f312af51..6533d4e5 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/model/MonthConfig.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/model/MonthConfig.kt @@ -7,17 +7,17 @@ import java.time.YearMonth import java.time.temporal.WeekFields internal data class MonthConfig( - val outDateStyle: OutDateStyle, - val inDateStyle: InDateStyle, - val maxRowCount: Int, - val startMonth: YearMonth, - val endMonth: YearMonth, - val firstDayOfWeek: DayOfWeek, - val hasBoundaries: Boolean + internal val outDateStyle: OutDateStyle, + internal val inDateStyle: InDateStyle, + internal val maxRowCount: Int, + internal val startMonth: YearMonth, + internal val endMonth: YearMonth, + internal val firstDayOfWeek: DayOfWeek, + internal val hasBoundaries: Boolean ) { - internal val months: List by lazy lazy@{ - return@lazy if (hasBoundaries) { + internal val months: List = run { + return@run if (hasBoundaries) { generateBoundedMonths( startMonth, endMonth, firstDayOfWeek, maxRowCount, inDateStyle, outDateStyle diff --git a/sample/build.gradle b/sample/build.gradle index 988e6f13..beb5a1d5 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -34,7 +34,7 @@ android { dependencies { implementation project(':library') coreLibraryDesugaring deps.desugaring - implementation deps.kotlin_stdlib8 + implementation deps.kotlin.stdlib8 implementation deps.androidx.legacy implementation deps.androidx.appcompat implementation deps.androidx.core_ktx From 02895fb24d59353c6e2e22da8d9a92ef3f025816 Mon Sep 17 00:00:00 2001 From: Kizito Nwose Date: Sat, 25 Jul 2020 10:23:41 +0200 Subject: [PATCH 09/14] Check job status while generating dates. --- .../kizitonwose/calendarview/CalendarView.kt | 42 ++++++++++--------- .../calendarview/model/MonthConfig.kt | 28 ++++++------- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt index 9cf12228..f219ccf7 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt @@ -10,11 +10,14 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.kizitonwose.calendarview.model.* import com.kizitonwose.calendarview.ui.* -import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.time.DayOfWeek import java.time.LocalDate import java.time.YearMonth -import kotlin.coroutines.CoroutineContext open class CalendarView : RecyclerView { @@ -438,7 +441,7 @@ open class CalendarView : RecyclerView { startMonth ?: return, endMonth ?: return, firstDayOfWeek ?: return, - hasBoundaries + hasBoundaries, Job() ) calendarAdapter.notifyDataSetChanged() post { calendarAdapter.notifyMonthScrollListenerIfNeeded() } @@ -624,7 +627,7 @@ open class CalendarView : RecyclerView { * @param firstDayOfWeek An instance of [DayOfWeek] enum to be the first day of week. */ fun setup(startMonth: YearMonth, endMonth: YearMonth, firstDayOfWeek: DayOfWeek) { - asyncJob?.cancel() + setupJob?.cancel() if (this.startMonth != null && this.endMonth != null && this.firstDayOfWeek != null) { this.firstDayOfWeek = firstDayOfWeek updateMonthRange(startMonth, endMonth) @@ -635,13 +638,13 @@ open class CalendarView : RecyclerView { finishSetup( MonthConfig( outDateStyle, inDateStyle, maxRowCount, startMonth, - endMonth, firstDayOfWeek, hasBoundaries + endMonth, firstDayOfWeek, hasBoundaries, Job() ) ) } } - private var asyncJob: Job? = null + private var setupJob: Job? = null @JvmOverloads fun setupAsync( @@ -650,7 +653,7 @@ open class CalendarView : RecyclerView { firstDayOfWeek: DayOfWeek, completion: (() -> Unit)? = null ) { - asyncJob?.cancel() + setupJob?.cancel() if (this.startMonth != null && this.endMonth != null && this.firstDayOfWeek != null) { this.firstDayOfWeek = firstDayOfWeek updateMonthRangeAsync(startMonth, endMonth, completion) @@ -658,12 +661,12 @@ open class CalendarView : RecyclerView { this.startMonth = startMonth this.endMonth = endMonth this.firstDayOfWeek = firstDayOfWeek - asyncJob = GlobalScope.launch { + setupJob = GlobalScope.launch { val monthConfig = MonthConfig( outDateStyle, inDateStyle, maxRowCount, startMonth, - endMonth, firstDayOfWeek, hasBoundaries + endMonth, firstDayOfWeek, hasBoundaries, setupJob ?: Job() ) - withContext(Dispatchers.Main) { + withContext(Main) { finishSetup(monthConfig) completion?.invoke() } @@ -723,25 +726,25 @@ open class CalendarView : RecyclerView { * See [updateStartMonth] and [updateEndMonth]. */ fun updateMonthRange(startMonth: YearMonth, endMonth: YearMonth) { - asyncJob?.cancel() + setupJob?.cancel() this.startMonth = startMonth this.endMonth = endMonth val firstDayOfWeek = firstDayOfWeek ?: throw IllegalStateException("`firstDayOfWeek` is not set. Have you called `setup()`?") - val (config, diff) = getMonthUpdateData(startMonth, endMonth, firstDayOfWeek) + val (config, diff) = getMonthUpdateData(startMonth, endMonth, firstDayOfWeek, Job()) finishUpdateMonthRange(config, diff) } @JvmOverloads fun updateMonthRangeAsync(startMonth: YearMonth, endMonth: YearMonth, completion: (() -> Unit)? = null) { - asyncJob?.cancel() + setupJob?.cancel() this.startMonth = startMonth this.endMonth = endMonth val firstDayOfWeek = firstDayOfWeek ?: throw IllegalStateException("`firstDayOfWeek` is not set. Have you called `setup()`?") - asyncJob = GlobalScope.launch { - val (config, diff) = getMonthUpdateData(startMonth, endMonth, firstDayOfWeek) - withContext(Dispatchers.Main) { + setupJob = GlobalScope.launch { + val (config, diff) = getMonthUpdateData(startMonth, endMonth, firstDayOfWeek, setupJob ?: Job()) + withContext(Main) { finishUpdateMonthRange(config, diff) completion?.invoke() } @@ -751,11 +754,12 @@ open class CalendarView : RecyclerView { private fun getMonthUpdateData( startMonth: YearMonth, endMonth: YearMonth, - firstDayOfWeek: DayOfWeek + firstDayOfWeek: DayOfWeek, + job: Job ): Pair { val monthConfig = MonthConfig( outDateStyle, inDateStyle, maxRowCount, startMonth, - endMonth, firstDayOfWeek, hasBoundaries + endMonth, firstDayOfWeek, hasBoundaries, job ) val diffResult = DiffUtil.calculateDiff( MonthRangeDiffCallback(calendarAdapter.monthConfig.months, monthConfig.months), @@ -771,7 +775,7 @@ open class CalendarView : RecyclerView { override fun onDetachedFromWindow() { super.onDetachedFromWindow() - asyncJob?.cancel() + setupJob?.cancel() } private class MonthRangeDiffCallback( diff --git a/library/src/main/java/com/kizitonwose/calendarview/model/MonthConfig.kt b/library/src/main/java/com/kizitonwose/calendarview/model/MonthConfig.kt index 6533d4e5..73cfcf2a 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/model/MonthConfig.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/model/MonthConfig.kt @@ -1,6 +1,7 @@ package com.kizitonwose.calendarview.model import com.kizitonwose.calendarview.utils.next +import kotlinx.coroutines.Job import java.time.DayOfWeek import java.time.LocalDate import java.time.YearMonth @@ -13,41 +14,39 @@ internal data class MonthConfig( internal val startMonth: YearMonth, internal val endMonth: YearMonth, internal val firstDayOfWeek: DayOfWeek, - internal val hasBoundaries: Boolean + internal val hasBoundaries: Boolean, + internal val job: Job ) { internal val months: List = run { return@run if (hasBoundaries) { - generateBoundedMonths( - startMonth, endMonth, firstDayOfWeek, - maxRowCount, inDateStyle, outDateStyle - ) + generateBoundedMonths(startMonth, endMonth, firstDayOfWeek, maxRowCount, inDateStyle, outDateStyle, job) } else { - generateUnboundedMonths( - startMonth, endMonth, firstDayOfWeek, - maxRowCount, inDateStyle, outDateStyle - ) + generateUnboundedMonths(startMonth, endMonth, firstDayOfWeek, maxRowCount, inDateStyle, outDateStyle, job) } } internal companion object { + private val uninterruptedJob = Job() + /** * A [YearMonth] will have multiple [CalendarMonth] instances if the [maxRowCount] is * less than 6. Each [CalendarMonth] will hold just enough [CalendarDay] instances(weekDays) * to fit in the [maxRowCount]. */ - internal fun generateBoundedMonths( + fun generateBoundedMonths( startMonth: YearMonth, endMonth: YearMonth, firstDayOfWeek: DayOfWeek, maxRowCount: Int, inDateStyle: InDateStyle, - outDateStyle: OutDateStyle + outDateStyle: OutDateStyle, + job: Job = uninterruptedJob ): List { val months = mutableListOf() var currentMonth = startMonth - while (currentMonth <= endMonth) { + while (currentMonth <= endMonth && job.isActive) { val generateInDates = when (inDateStyle) { InDateStyle.ALL_MONTHS -> true InDateStyle.FIRST_MONTH -> currentMonth == startMonth @@ -79,13 +78,14 @@ internal data class MonthConfig( firstDayOfWeek: DayOfWeek, maxRowCount: Int, inDateStyle: InDateStyle, - outDateStyle: OutDateStyle + outDateStyle: OutDateStyle, + job: Job = uninterruptedJob ): List { // Generate a flat list of all days in the given month range val allDays = mutableListOf() var currentMonth = startMonth - while (currentMonth <= endMonth) { + while (currentMonth <= endMonth && job.isActive) { // If inDates are enabled with boundaries disabled, // we show them on the first month only. From df82bc28ab096d9ba1f760a70ff5e678f90814e5 Mon Sep 17 00:00:00 2001 From: Kizito Nwose Date: Sat, 25 Jul 2020 15:40:39 +0200 Subject: [PATCH 10/14] Clean up code. --- .../kizitonwose/calendarview/CalendarView.kt | 244 +++++++++++------- .../calendarviewsample/Example1Fragment.kt | 16 +- 2 files changed, 157 insertions(+), 103 deletions(-) diff --git a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt index f219ccf7..cd3438a2 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt @@ -2,6 +2,7 @@ package com.kizitonwose.calendarview import android.content.Context import android.util.AttributeSet +import android.util.Size import android.view.View.MeasureSpec.UNSPECIFIED import android.view.ViewGroup import androidx.annotation.Px @@ -10,15 +11,17 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.kizitonwose.calendarview.model.* import com.kizitonwose.calendarview.ui.* +import kotlinx.coroutines.* import kotlinx.coroutines.Dispatchers.Main -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.time.DayOfWeek import java.time.LocalDate import java.time.YearMonth +typealias Completion = () -> Unit + +val CoroutineScope.job: Job + get() = requireNotNull(coroutineContext[Job]) + open class CalendarView : RecyclerView { /** @@ -140,6 +143,9 @@ open class CalendarView : RecyclerView { * If set to [InDateStyle.FIRST_MONTH], inDates will be generated for the first month only. * If set to [InDateStyle.NONE], inDates will not be generated, this means there will * be no offset on any month. + * + * Note: This causes month data to be regenerated, consider using [updateMonthConfiguration] + * if updating this property alongside [outDateStyle], [maxRowCount] or [hasBoundaries]. */ var inDateStyle = InDateStyle.ALL_MONTHS set(value) { @@ -157,6 +163,9 @@ open class CalendarView : RecyclerView { * If set to [OutDateStyle.END_OF_GRID], the calendar will generate outDates until * it reaches the end of a 6 x 7 grid. This means that all months will have 6 rows. * If set to [OutDateStyle.NONE], no outDates will be generated. + * + * Note: This causes month data to be regenerated, consider using [updateMonthConfiguration] + * if updating this value property [inDateStyle], [maxRowCount] or [hasBoundaries]. */ var outDateStyle = OutDateStyle.END_OF_ROW set(value) { @@ -171,6 +180,9 @@ open class CalendarView : RecyclerView { * rows and [maxRowCount] is set to 4, there will be two appearances of that month on the, * calendar the first one will show 4 rows and the second one will show the remaining 2 rows. * To show a week mode calendar, set this value to 1. + * + * Note: This causes month data to be regenerated, consider using [updateMonthConfiguration] + * if updating this property alongside [inDateStyle], [outDateStyle] or [hasBoundaries]. */ var maxRowCount = 6 set(value) { @@ -193,6 +205,9 @@ open class CalendarView : RecyclerView { * only the last index will contain outDates. * - If [OutDateStyle] is [OutDateStyle.END_OF_GRID], outDates are generated for the last index until it * satisfies the [maxRowCount] requirement. + * + * Note: This causes month data to be regenerated, consider using [updateMonthConfiguration] + * if updating this property alongside [inDateStyle], [outDateStyle] or [maxRowCount]. */ var hasBoundaries = true set(value) { @@ -226,6 +241,9 @@ open class CalendarView : RecyclerView { internal val isHorizontal: Boolean get() = !isVertical + private var setupJob: Job? = null + private var updatingMonthConfig = false + constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { @@ -415,11 +433,6 @@ open class CalendarView : RecyclerView { // This removes all views but is internal. // removeAndRecycleViews() - if (isUpdating) { - pendingViewHolderInvalidation = true - return - } - if (adapter == null || layoutManager == null) return val state = layoutManager?.onSaveInstanceState() adapter = adapter @@ -427,32 +440,72 @@ open class CalendarView : RecyclerView { post { calendarAdapter.notifyMonthScrollListenerIfNeeded() } } - private fun updateAdapterMonthConfig() { - if (isUpdating) { - pendingAdapterMonthConfigUpdate = true - return - } + private fun updateAdapterMonthConfig(config: MonthConfig? = null) { + if (updatingMonthConfig) return if (adapter != null) { - calendarAdapter.monthConfig = - MonthConfig( - outDateStyle, - inDateStyle, - maxRowCount, - startMonth ?: return, - endMonth ?: return, - firstDayOfWeek ?: return, - hasBoundaries, Job() - ) + calendarAdapter.monthConfig = config ?: MonthConfig( + outDateStyle, + inDateStyle, + maxRowCount, + startMonth ?: return, + endMonth ?: return, + firstDayOfWeek ?: return, + hasBoundaries, Job() + ) calendarAdapter.notifyDataSetChanged() post { calendarAdapter.notifyMonthScrollListenerIfNeeded() } } } - private fun updateAdapterViewConfig() { - if (isUpdating) { - pendingAdapterViewConfigUpdate = true - return + /** + * Update [inDateStyle], [outDateStyle], [maxRowCount] and [hasBoundaries] + * without generating the underlying month data multiple times. + * See [updateMonthConfigurationAsync] if you wish to do this asynchronously. + */ + fun updateMonthConfiguration( + inDateStyle: InDateStyle = this.inDateStyle, + outDateStyle: OutDateStyle = this.outDateStyle, + maxRowCount: Int = this.maxRowCount, + hasBoundaries: Boolean = this.hasBoundaries + ) { + updatingMonthConfig = true + this.inDateStyle = inDateStyle + this.outDateStyle = outDateStyle + this.maxRowCount = maxRowCount + this.hasBoundaries = hasBoundaries + updatingMonthConfig = false + updateAdapterMonthConfig() + } + + /** + * Update [inDateStyle], [outDateStyle], [maxRowCount] and [hasBoundaries] + * asynchronously without generating the underlying month data multiple times. + * Useful if your [startMonth] and [endMonth] values are many years apart. + * See [updateMonthConfiguration] if you wish to do this synchronously. + */ + fun updateMonthConfigurationAsync( + inDateStyle: InDateStyle = this.inDateStyle, + outDateStyle: OutDateStyle = this.outDateStyle, + maxRowCount: Int = this.maxRowCount, + hasBoundaries: Boolean = this.hasBoundaries, + completion: Completion? = null + ) { + updatingMonthConfig = true + this.inDateStyle = inDateStyle + this.outDateStyle = outDateStyle + this.maxRowCount = maxRowCount + this.hasBoundaries = hasBoundaries + updatingMonthConfig = false + setupJob = GlobalScope.launch { + val monthConfig = generateMonthConfig(job) + withContext(Main) { + updateAdapterMonthConfig(monthConfig) + completion?.invoke() + } } + } + + private fun updateAdapterViewConfig() { if (adapter != null) { calendarAdapter.viewConfig = ViewConfig(dayViewResource, monthHeaderResource, monthFooterResource, monthViewClass) @@ -460,28 +513,6 @@ open class CalendarView : RecyclerView { } } - private var isUpdating = false - private var pendingAdapterMonthConfigUpdate = false - private var pendingAdapterViewConfigUpdate = false - private var pendingViewHolderInvalidation = false - - /** - * Update multiple properties of [CalendarView] without triggering multiple calls to - * [updateAdapterViewConfig], [updateAdapterMonthConfig] or [invalidateViewHolders]. - * Beneficial if you want to change multiple properties at once. - */ - fun configure(block: CalendarView.() -> Unit) { - isUpdating = true - this.block() - isUpdating = false - if (pendingAdapterMonthConfigUpdate) updateAdapterMonthConfig() - if (pendingAdapterViewConfigUpdate) updateAdapterViewConfig() - if (pendingViewHolderInvalidation && !pendingAdapterViewConfigUpdate) invalidateViewHolders() - pendingAdapterMonthConfigUpdate = false - pendingAdapterViewConfigUpdate = false - pendingViewHolderInvalidation = false - } - /** * Scroll to a specific month on the calendar. This only * shows the view for the month without any animations. @@ -620,7 +651,7 @@ open class CalendarView : RecyclerView { /** * Setup the CalendarView. You can call this any time to change the * the desired [startMonth], [endMonth] or [firstDayOfWeek] on the Calendar. - * See [updateStartMonth], [updateEndMonth] and [updateMonthRange] for more refined updates. + * See [updateMonthRange] and [updateMonthRangeAsync] for more refined updates. * * @param startMonth The first month on the calendar. * @param endMonth The last month on the calendar. @@ -644,14 +675,22 @@ open class CalendarView : RecyclerView { } } - private var setupJob: Job? = null - + /** + * Setup the CalendarView, asynchronously. You can call this any time to change the + * the desired [startMonth], [endMonth] or [firstDayOfWeek] on the Calendar. + * Useful if your [startMonth] and [endMonth] values are many years apart. + * See [updateMonthRange] and [updateMonthRangeAsync] for more refined updates. + * + * @param startMonth The first month on the calendar. + * @param endMonth The last month on the calendar. + * @param firstDayOfWeek An instance of [DayOfWeek] enum to be the first day of week. + */ @JvmOverloads fun setupAsync( startMonth: YearMonth, endMonth: YearMonth, firstDayOfWeek: DayOfWeek, - completion: (() -> Unit)? = null + completion: Completion? = null ) { setupJob?.cancel() if (this.startMonth != null && this.endMonth != null && this.firstDayOfWeek != null) { @@ -664,7 +703,7 @@ open class CalendarView : RecyclerView { setupJob = GlobalScope.launch { val monthConfig = MonthConfig( outDateStyle, inDateStyle, maxRowCount, startMonth, - endMonth, firstDayOfWeek, hasBoundaries, setupJob ?: Job() + endMonth, firstDayOfWeek, hasBoundaries, job ) withContext(Main) { finishSetup(monthConfig) @@ -691,59 +730,56 @@ open class CalendarView : RecyclerView { * This can be called only if you have called [setup] in the past. * See [updateEndMonth] and [updateMonthRange]. */ - fun updateStartMonth(startMonth: YearMonth) = updateMonthRange( - startMonth, - endMonth ?: throw IllegalStateException("`endMonth` is not set. Have you called `setup()`?") - ) - - @JvmOverloads - fun updateStartMonthAsync(startMonth: YearMonth, completion: (() -> Unit)? = null) = updateMonthRangeAsync( - startMonth, - endMonth ?: throw IllegalStateException("`endMonth` is not set. Have you called `setup()`?"), - completion + @Deprecated( + "This will be removed in the future to clean up the library's API.", + ReplaceWith("updateMonthRange()"), + DeprecationLevel.ERROR ) + fun updateStartMonth(startMonth: YearMonth) = updateMonthRange(startMonth, requireEndMonth()) /** * Update the CalendarView's end month. * This can be called only if you have called [setup] in the past. * See [updateStartMonth] and [updateMonthRange]. */ - fun updateEndMonth(endMonth: YearMonth) = updateMonthRange( - startMonth ?: throw IllegalStateException("`startMonth` is not set. Have you called `setup()`?"), - endMonth - ) - - @JvmOverloads - fun updateEndMonthAsync(endMonth: YearMonth, completion: (() -> Unit)? = null) = updateMonthRangeAsync( - startMonth ?: throw IllegalStateException("`startMonth` is not set. Have you called `setup()`?"), - endMonth, - completion + @Deprecated( + "This will be removed in the future to clean up the library's API.", + ReplaceWith("updateMonthRange()"), + DeprecationLevel.ERROR ) + fun updateEndMonth(endMonth: YearMonth) = updateMonthRange(requireStartMonth(), endMonth) /** * Update the CalendarView's start and end months. - * This can be called only if you have called [setup] in the past. - * See [updateStartMonth] and [updateEndMonth]. + * This can be called only if you have called [setup] or [setupAsync] in the past. + * See [updateMonthRangeAsync] if you wish to do this asynchronously. */ - fun updateMonthRange(startMonth: YearMonth, endMonth: YearMonth) { + @JvmOverloads + fun updateMonthRange(startMonth: YearMonth = requireStartMonth(), endMonth: YearMonth = requireEndMonth()) { setupJob?.cancel() this.startMonth = startMonth this.endMonth = endMonth - val firstDayOfWeek = - firstDayOfWeek ?: throw IllegalStateException("`firstDayOfWeek` is not set. Have you called `setup()`?") - val (config, diff) = getMonthUpdateData(startMonth, endMonth, firstDayOfWeek, Job()) + val (config, diff) = getMonthUpdateData(Job()) finishUpdateMonthRange(config, diff) } + /** + * Update the CalendarView's start and end months, asynchronously. + * This can be called only if you have called [setup] or [setupAsync] in the past. + * Useful if your [startMonth] and [endMonth] values are many years apart. + * See [updateMonthRange] if you wish to do this synchronously. + */ @JvmOverloads - fun updateMonthRangeAsync(startMonth: YearMonth, endMonth: YearMonth, completion: (() -> Unit)? = null) { + fun updateMonthRangeAsync( + startMonth: YearMonth = requireStartMonth(), + endMonth: YearMonth = requireEndMonth(), + completion: Completion? = null + ) { setupJob?.cancel() this.startMonth = startMonth this.endMonth = endMonth - val firstDayOfWeek = - firstDayOfWeek ?: throw IllegalStateException("`firstDayOfWeek` is not set. Have you called `setup()`?") setupJob = GlobalScope.launch { - val (config, diff) = getMonthUpdateData(startMonth, endMonth, firstDayOfWeek, setupJob ?: Job()) + val (config, diff) = getMonthUpdateData(job) withContext(Main) { finishUpdateMonthRange(config, diff) completion?.invoke() @@ -751,16 +787,8 @@ open class CalendarView : RecyclerView { } } - private fun getMonthUpdateData( - startMonth: YearMonth, - endMonth: YearMonth, - firstDayOfWeek: DayOfWeek, - job: Job - ): Pair { - val monthConfig = MonthConfig( - outDateStyle, inDateStyle, maxRowCount, startMonth, - endMonth, firstDayOfWeek, hasBoundaries, job - ) + private fun getMonthUpdateData(job: Job): Pair { + val monthConfig = generateMonthConfig(job) val diffResult = DiffUtil.calculateDiff( MonthRangeDiffCallback(calendarAdapter.monthConfig.months, monthConfig.months), false @@ -794,6 +822,32 @@ open class CalendarView : RecyclerView { areItemsTheSame(oldItemPosition, newItemPosition) } + private fun generateMonthConfig(job: Job): MonthConfig { + return MonthConfig( + outDateStyle, + inDateStyle, + maxRowCount, + requireStartMonth(), + requireEndMonth(), + requireFirstDayOfWeek(), + hasBoundaries, + job + ) + } + + fun requireStartMonth(): YearMonth { + return startMonth ?: throw IllegalStateException("`startMonth` is not set. Have you called `setup()`?") + } + + fun requireEndMonth(): YearMonth { + return endMonth ?: throw IllegalStateException("`endMonth` is not set. Have you called `setup()`?") + } + + fun requireFirstDayOfWeek(): DayOfWeek { + return firstDayOfWeek ?: throw IllegalStateException("`firstDayOfWeek` is not set. Have you called `setup()`?") + } + + companion object { /** * A value for [dayWidth] and [dayHeight] which indicates that the day diff --git a/sample/src/main/java/com/kizitonwose/calendarviewsample/Example1Fragment.kt b/sample/src/main/java/com/kizitonwose/calendarviewsample/Example1Fragment.kt index 0b17d722..477dfeb9 100644 --- a/sample/src/main/java/com/kizitonwose/calendarviewsample/Example1Fragment.kt +++ b/sample/src/main/java/com/kizitonwose/calendarviewsample/Example1Fragment.kt @@ -153,20 +153,20 @@ class Example1Fragment : BaseFragment(R.layout.example_1_fragment), HasToolbar { animator.doOnStart { if (!monthToWeek) { - binding.exOneCalendar.configure { - inDateStyle = InDateStyle.ALL_MONTHS - maxRowCount = 6 + binding.exOneCalendar.updateMonthConfiguration( + inDateStyle = InDateStyle.ALL_MONTHS, + maxRowCount = 6, hasBoundaries = true - } + ) } } animator.doOnEnd { if (monthToWeek) { - binding.exOneCalendar.configure { - inDateStyle = InDateStyle.FIRST_MONTH - maxRowCount = 1 + binding.exOneCalendar.updateMonthConfiguration( + inDateStyle = InDateStyle.FIRST_MONTH, + maxRowCount = 1, hasBoundaries = false - } + ) } if (monthToWeek) { From e88ce738f9ce90d380a3a2457fb7628ab78c8bd8 Mon Sep 17 00:00:00 2001 From: Kizito Nwose Date: Sat, 25 Jul 2020 16:33:08 +0200 Subject: [PATCH 11/14] Introduce `daySize`, deprecate `dayWidth` and `dayHeight`. --- .../kizitonwose/calendarview/CalendarView.kt | 82 +++++++++++++------ .../calendarview/ui/CalendarAdapter.kt | 4 +- .../kizitonwose/calendarview/ui/DayHolder.kt | 15 ++-- .../calendarview/utils/Extensions.kt | 5 ++ .../kizitonwose/calendarview/utils/Size.kt | 9 ++ .../calendarviewsample/Example1Fragment.kt | 2 +- .../calendarviewsample/Example6Fragment.kt | 6 +- .../calendarviewsample/Example7Fragment.kt | 6 +- 8 files changed, 87 insertions(+), 42 deletions(-) create mode 100644 library/src/main/java/com/kizitonwose/calendarview/utils/Size.kt diff --git a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt index cd3438a2..9f75373d 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt @@ -2,26 +2,28 @@ package com.kizitonwose.calendarview import android.content.Context import android.util.AttributeSet -import android.util.Size import android.view.View.MeasureSpec.UNSPECIFIED import android.view.ViewGroup import androidx.annotation.Px import androidx.core.content.withStyledAttributes import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView +import com.kizitonwose.calendarview.CalendarView.Companion.DAY_SIZE_SQUARE import com.kizitonwose.calendarview.model.* import com.kizitonwose.calendarview.ui.* -import kotlinx.coroutines.* +import com.kizitonwose.calendarview.utils.Size +import com.kizitonwose.calendarview.utils.job import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.time.DayOfWeek import java.time.LocalDate import java.time.YearMonth typealias Completion = () -> Unit -val CoroutineScope.job: Job - get() = requireNotNull(coroutineContext[Job]) - open class CalendarView : RecyclerView { /** @@ -241,7 +243,7 @@ open class CalendarView : RecyclerView { internal val isHorizontal: Boolean get() = !isVertical - private var setupJob: Job? = null + private var configJob: Job? = null private var updatingMonthConfig = false constructor(context: Context) : super(context) @@ -286,11 +288,11 @@ open class CalendarView : RecyclerView { } // +0.5 => round to the nearest pixel - val squareSize = (((widthSize - (monthPaddingStart + monthPaddingEnd)) / 7f) + 0.5).toInt() - if (dayWidth != squareSize || dayHeight != squareSize) { + val size = (((widthSize - (monthPaddingStart + monthPaddingEnd)) / 7f) + 0.5).toInt() + val squareSize = daySize.copy(width = size, height = size) + if (daySize != squareSize) { sizedInternally = true - dayWidth = squareSize - dayHeight = squareSize + daySize = squareSize sizedInternally = false invalidateViewHolders() } @@ -306,13 +308,14 @@ open class CalendarView : RecyclerView { * @see [DAY_SIZE_SQUARE] */ @Px + @Deprecated( + "Will be removed to clean up the library's API.", + replaceWith = ReplaceWith("daySize") + ) var dayWidth: Int = DAY_SIZE_SQUARE set(value) { field = value - if (!sizedInternally) { - autoSize = value == DAY_SIZE_SQUARE - invalidateViewHolders() - } + daySize = Size(field, dayHeight) } /** @@ -323,11 +326,28 @@ open class CalendarView : RecyclerView { * @see [DAY_SIZE_SQUARE] */ @Px + @Deprecated( + "Will be removed to clean up the library's API.", + replaceWith = ReplaceWith("daySize") + ) var dayHeight: Int = DAY_SIZE_SQUARE + set(value) { + field = value + daySize = Size(dayWidth, field) + } + + /** + * The size in pixels for each day cell view. + * Set this to [SIZE_SQUARE] to have a nice + * square item view. + * + * @see [SIZE_SQUARE] + */ + var daySize: Size = SIZE_SQUARE set(value) { field = value if (!sizedInternally) { - autoSize = value == DAY_SIZE_SQUARE + autoSize = value == SIZE_SQUARE invalidateViewHolders() } } @@ -468,6 +488,7 @@ open class CalendarView : RecyclerView { maxRowCount: Int = this.maxRowCount, hasBoundaries: Boolean = this.hasBoundaries ) { + configJob?.cancel() updatingMonthConfig = true this.inDateStyle = inDateStyle this.outDateStyle = outDateStyle @@ -490,13 +511,14 @@ open class CalendarView : RecyclerView { hasBoundaries: Boolean = this.hasBoundaries, completion: Completion? = null ) { + configJob?.cancel() updatingMonthConfig = true this.inDateStyle = inDateStyle this.outDateStyle = outDateStyle this.maxRowCount = maxRowCount this.hasBoundaries = hasBoundaries updatingMonthConfig = false - setupJob = GlobalScope.launch { + configJob = GlobalScope.launch { val monthConfig = generateMonthConfig(job) withContext(Main) { updateAdapterMonthConfig(monthConfig) @@ -658,7 +680,7 @@ open class CalendarView : RecyclerView { * @param firstDayOfWeek An instance of [DayOfWeek] enum to be the first day of week. */ fun setup(startMonth: YearMonth, endMonth: YearMonth, firstDayOfWeek: DayOfWeek) { - setupJob?.cancel() + configJob?.cancel() if (this.startMonth != null && this.endMonth != null && this.firstDayOfWeek != null) { this.firstDayOfWeek = firstDayOfWeek updateMonthRange(startMonth, endMonth) @@ -692,7 +714,7 @@ open class CalendarView : RecyclerView { firstDayOfWeek: DayOfWeek, completion: Completion? = null ) { - setupJob?.cancel() + configJob?.cancel() if (this.startMonth != null && this.endMonth != null && this.firstDayOfWeek != null) { this.firstDayOfWeek = firstDayOfWeek updateMonthRangeAsync(startMonth, endMonth, completion) @@ -700,7 +722,7 @@ open class CalendarView : RecyclerView { this.startMonth = startMonth this.endMonth = endMonth this.firstDayOfWeek = firstDayOfWeek - setupJob = GlobalScope.launch { + configJob = GlobalScope.launch { val monthConfig = MonthConfig( outDateStyle, inDateStyle, maxRowCount, startMonth, endMonth, firstDayOfWeek, hasBoundaries, job @@ -731,7 +753,7 @@ open class CalendarView : RecyclerView { * See [updateEndMonth] and [updateMonthRange]. */ @Deprecated( - "This will be removed in the future to clean up the library's API.", + "Will be removed to clean up the library's API.", ReplaceWith("updateMonthRange()"), DeprecationLevel.ERROR ) @@ -756,7 +778,7 @@ open class CalendarView : RecyclerView { */ @JvmOverloads fun updateMonthRange(startMonth: YearMonth = requireStartMonth(), endMonth: YearMonth = requireEndMonth()) { - setupJob?.cancel() + configJob?.cancel() this.startMonth = startMonth this.endMonth = endMonth val (config, diff) = getMonthUpdateData(Job()) @@ -775,10 +797,10 @@ open class CalendarView : RecyclerView { endMonth: YearMonth = requireEndMonth(), completion: Completion? = null ) { - setupJob?.cancel() + configJob?.cancel() this.startMonth = startMonth this.endMonth = endMonth - setupJob = GlobalScope.launch { + configJob = GlobalScope.launch { val (config, diff) = getMonthUpdateData(job) withContext(Main) { finishUpdateMonthRange(config, diff) @@ -803,7 +825,7 @@ open class CalendarView : RecyclerView { override fun onDetachedFromWindow() { super.onDetachedFromWindow() - setupJob?.cancel() + configJob?.cancel() } private class MonthRangeDiffCallback( @@ -847,13 +869,23 @@ open class CalendarView : RecyclerView { return firstDayOfWeek ?: throw IllegalStateException("`firstDayOfWeek` is not set. Have you called `setup()`?") } - companion object { /** * A value for [dayWidth] and [dayHeight] which indicates that the day * cells should have equal width and height. Each view's width and height * will be the width of the calender divided by 7. */ + @Deprecated( + "Will be removed to clean up the library's API.", + replaceWith = ReplaceWith("CalendarView.SIZE_SQUARE") + ) const val DAY_SIZE_SQUARE = Int.MIN_VALUE + + /** + * A value for [daySize] which indicates that the day cells should + * have equal width and height. Each view's width and height will + * be the width of the calender divided by 7. + */ + val SIZE_SQUARE = Size(DAY_SIZE_SQUARE, DAY_SIZE_SQUARE) } } 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 8bc6cec8..8e9e7ce1 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/ui/CalendarAdapter.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/ui/CalendarAdapter.kt @@ -77,7 +77,7 @@ internal class CalendarAdapter( } @Suppress("UNCHECKED_CAST") val dayConfig = DayConfig( - calView.dayWidth, calView.dayHeight, viewConfig.dayViewRes, + calView.daySize, viewConfig.dayViewRes, calView.dayBinder as DayBinder ) @@ -210,7 +210,7 @@ internal class CalendarAdapter( // visibleVH.bodyLayout.height` won't not give us the right height as it differs // depending on row count in the month. So we calculate the appropriate height // by checking the number of visible(non-empty) rows. - visibleMonth.weekDays.size * calView.dayHeight + + visibleMonth.weekDays.size * calView.daySize.height + visibleVH.footerView?.height.orZero() if (calView.height != newHeight) { ValueAnimator.ofInt(calView.height, newHeight).apply { diff --git a/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt b/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt index a0f104fb..7317e91c 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/ui/DayHolder.kt @@ -3,20 +3,15 @@ package com.kizitonwose.calendarview.ui import android.view.View import android.widget.LinearLayout import androidx.annotation.LayoutRes -import androidx.annotation.Px +import androidx.core.view.* import androidx.core.view.MarginLayoutParamsCompat.getMarginEnd import androidx.core.view.MarginLayoutParamsCompat.getMarginStart -import androidx.core.view.isGone -import androidx.core.view.isVisible -import androidx.core.view.marginBottom -import androidx.core.view.marginTop -import androidx.core.view.updateLayoutParams import com.kizitonwose.calendarview.model.CalendarDay +import com.kizitonwose.calendarview.utils.Size import com.kizitonwose.calendarview.utils.inflate internal data class DayConfig( - @Px val width: Int, - @Px val height: Int, + val size: Size, @LayoutRes val dayViewRes: Int, val viewBinder: DayBinder ) @@ -33,8 +28,8 @@ internal class DayHolder(private val config: DayConfig) { // use LinearLayout.LayoutParams and set the weight appropriately. // The parent's wightSum is already set to 7 to accommodate seven week days. updateLayoutParams { - width = config.width - getMarginStart(this) - getMarginEnd(this) - height = config.height - marginTop - marginBottom + width = config.size.width - getMarginStart(this) - getMarginEnd(this) + height = config.size.height - marginTop - marginBottom weight = 1f } } diff --git a/library/src/main/java/com/kizitonwose/calendarview/utils/Extensions.kt b/library/src/main/java/com/kizitonwose/calendarview/utils/Extensions.kt index 888ae586..2c5264f3 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/utils/Extensions.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/utils/Extensions.kt @@ -5,6 +5,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.LayoutRes +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import java.time.LocalDate import java.time.YearMonth @@ -29,3 +31,6 @@ internal const val NO_INDEX = -1 internal val Rect.namedString: String get() = "[L: $left, T: $top][R: $right, B: $bottom]" + +internal val CoroutineScope.job: Job + get() = requireNotNull(coroutineContext[Job]) diff --git a/library/src/main/java/com/kizitonwose/calendarview/utils/Size.kt b/library/src/main/java/com/kizitonwose/calendarview/utils/Size.kt new file mode 100644 index 00000000..8e16f6f0 --- /dev/null +++ b/library/src/main/java/com/kizitonwose/calendarview/utils/Size.kt @@ -0,0 +1,9 @@ +package com.kizitonwose.calendarview.utils + +import androidx.annotation.Px + +/** + * Class for describing width and height dimensions in pixels. + * Basically [android.util.Size], but allows this library to keep minSdk < 21. + */ +data class Size(@Px val width: Int, @Px val height: Int) diff --git a/sample/src/main/java/com/kizitonwose/calendarviewsample/Example1Fragment.kt b/sample/src/main/java/com/kizitonwose/calendarviewsample/Example1Fragment.kt index 477dfeb9..597f8a51 100644 --- a/sample/src/main/java/com/kizitonwose/calendarviewsample/Example1Fragment.kt +++ b/sample/src/main/java/com/kizitonwose/calendarviewsample/Example1Fragment.kt @@ -131,7 +131,7 @@ class Example1Fragment : BaseFragment(R.layout.example_1_fragment), HasToolbar { val firstDate = binding.exOneCalendar.findFirstVisibleDay()?.date ?: return@setOnCheckedChangeListener val lastDate = binding.exOneCalendar.findLastVisibleDay()?.date ?: return@setOnCheckedChangeListener - val oneWeekHeight = binding.exOneCalendar.dayHeight + val oneWeekHeight = binding.exOneCalendar.daySize.height val oneMonthHeight = oneWeekHeight * 6 val oldHeight = if (monthToWeek) oneMonthHeight else oneWeekHeight diff --git a/sample/src/main/java/com/kizitonwose/calendarviewsample/Example6Fragment.kt b/sample/src/main/java/com/kizitonwose/calendarviewsample/Example6Fragment.kt index b17e11c4..1c1fe54c 100644 --- a/sample/src/main/java/com/kizitonwose/calendarviewsample/Example6Fragment.kt +++ b/sample/src/main/java/com/kizitonwose/calendarviewsample/Example6Fragment.kt @@ -15,6 +15,7 @@ import com.kizitonwose.calendarview.model.DayOwner import com.kizitonwose.calendarview.ui.DayBinder import com.kizitonwose.calendarview.ui.MonthHeaderFooterBinder import com.kizitonwose.calendarview.ui.ViewContainer +import com.kizitonwose.calendarview.utils.Size import com.kizitonwose.calendarviewsample.databinding.Example6CalendarDayBinding import com.kizitonwose.calendarviewsample.databinding.Example6CalendarHeaderBinding import com.kizitonwose.calendarviewsample.databinding.Example6FragmentBinding @@ -52,8 +53,9 @@ class Example6Fragment : BaseFragment(R.layout.example_6_fragment), HasBackButto // We want the immediately following/previous month to be // partially visible so we multiply the total width by 0.73 val monthWidth = (dm.widthPixels * 0.73).toInt() - dayWidth = monthWidth / 7 - dayHeight = (dayWidth * 1.73).toInt() // We don't want a square calendar. + val dayWidth = monthWidth / 7 + val dayHeight = (dayWidth * 1.73).toInt() // We don't want a square calendar. + daySize = Size(dayWidth, dayHeight) // Add margins around our card view. val horizontalMargin = dpToPx(8, requireContext()) diff --git a/sample/src/main/java/com/kizitonwose/calendarviewsample/Example7Fragment.kt b/sample/src/main/java/com/kizitonwose/calendarviewsample/Example7Fragment.kt index 3630277e..cd635ae2 100644 --- a/sample/src/main/java/com/kizitonwose/calendarviewsample/Example7Fragment.kt +++ b/sample/src/main/java/com/kizitonwose/calendarviewsample/Example7Fragment.kt @@ -10,6 +10,7 @@ import androidx.core.view.isVisible import com.kizitonwose.calendarview.model.CalendarDay import com.kizitonwose.calendarview.ui.DayBinder import com.kizitonwose.calendarview.ui.ViewContainer +import com.kizitonwose.calendarview.utils.Size import com.kizitonwose.calendarviewsample.databinding.Example7CalendarDayBinding import com.kizitonwose.calendarviewsample.databinding.Example7FragmentBinding import java.time.DayOfWeek @@ -40,8 +41,9 @@ class Example7Fragment : BaseFragment(R.layout.example_7_fragment), HasToolbar, val wm = requireContext().getSystemService(Context.WINDOW_SERVICE) as WindowManager wm.defaultDisplay.getMetrics(dm) binding.exSevenCalendar.apply { - dayWidth = dm.widthPixels / 5 - dayHeight = (dayWidth * 1.25).toInt() + val dayWidth = dm.widthPixels / 5 + val dayHeight = (dayWidth * 1.25).toInt() + daySize = Size(dayWidth, dayHeight) } class DayViewContainer(view: View) : ViewContainer(view) { From 4cd3cdb0fe9f4f7421876b2c16a6a9a231472f2c Mon Sep 17 00:00:00 2001 From: Kizito Nwose Date: Sat, 25 Jul 2020 18:19:48 +0200 Subject: [PATCH 12/14] Add `setMonthMargins` and `setMonthPadding` methods. --- .../kizitonwose/calendarview/CalendarView.kt | 65 +++++++++++++++---- .../calenderviewsample/CalenderViewTests.kt | 22 ++++++- 2 files changed, 73 insertions(+), 14 deletions(-) diff --git a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt index 9f75373d..83110734 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt @@ -244,7 +244,7 @@ open class CalendarView : RecyclerView { get() = !isVertical private var configJob: Job? = null - private var updatingMonthConfig = false + private var internalConfigUpdate = false constructor(context: Context) : super(context) @@ -446,12 +446,21 @@ open class CalendarView : RecyclerView { private val calendarAdapter: CalendarAdapter get() = adapter as CalendarAdapter + private fun updateAdapterViewConfig() { + if (adapter != null) { + calendarAdapter.viewConfig = + ViewConfig(dayViewResource, monthHeaderResource, monthFooterResource, monthViewClass) + invalidateViewHolders() + } + } + private fun invalidateViewHolders() { // This does not remove visible views. // recycledViewPool.clear() // This removes all views but is internal. // removeAndRecycleViews() + if (internalConfigUpdate) return if (adapter == null || layoutManager == null) return val state = layoutManager?.onSaveInstanceState() @@ -461,7 +470,7 @@ open class CalendarView : RecyclerView { } private fun updateAdapterMonthConfig(config: MonthConfig? = null) { - if (updatingMonthConfig) return + if (internalConfigUpdate) return if (adapter != null) { calendarAdapter.monthConfig = config ?: MonthConfig( outDateStyle, @@ -489,12 +498,12 @@ open class CalendarView : RecyclerView { hasBoundaries: Boolean = this.hasBoundaries ) { configJob?.cancel() - updatingMonthConfig = true + internalConfigUpdate = true this.inDateStyle = inDateStyle this.outDateStyle = outDateStyle this.maxRowCount = maxRowCount this.hasBoundaries = hasBoundaries - updatingMonthConfig = false + internalConfigUpdate = false updateAdapterMonthConfig() } @@ -512,12 +521,12 @@ open class CalendarView : RecyclerView { completion: Completion? = null ) { configJob?.cancel() - updatingMonthConfig = true + internalConfigUpdate = true this.inDateStyle = inDateStyle this.outDateStyle = outDateStyle this.maxRowCount = maxRowCount this.hasBoundaries = hasBoundaries - updatingMonthConfig = false + internalConfigUpdate = false configJob = GlobalScope.launch { val monthConfig = generateMonthConfig(job) withContext(Main) { @@ -527,12 +536,44 @@ open class CalendarView : RecyclerView { } } - private fun updateAdapterViewConfig() { - if (adapter != null) { - calendarAdapter.viewConfig = - ViewConfig(dayViewResource, monthHeaderResource, monthFooterResource, monthViewClass) - invalidateViewHolders() - } + /** + * Set the [monthPaddingStart], [monthPaddingTop], [monthPaddingEnd] and [monthPaddingBottom] + * values without invalidating the view holders multiple times which would happen if these + * values were set individually. + */ + fun setMonthPadding( + @Px start: Int = monthPaddingStart, + @Px top: Int = monthPaddingTop, + @Px end: Int = monthPaddingEnd, + @Px bottom: Int = monthPaddingBottom + ) { + internalConfigUpdate = true + monthPaddingStart = start + monthPaddingTop = top + monthPaddingEnd = end + monthPaddingBottom = bottom + internalConfigUpdate = false + invalidateViewHolders() + } + + /** + * Set the [monthMarginStart], [monthMarginTop], [monthMarginEnd] and [monthMarginBottom] + * values without invalidating the view holders multiple times which would happen if these + * values were set individually. + */ + fun setMonthMargins( + @Px start: Int = monthMarginStart, + @Px top: Int = monthMarginTop, + @Px end: Int = monthMarginEnd, + @Px bottom: Int = monthMarginBottom + ) { + internalConfigUpdate = true + monthMarginStart = start + monthMarginTop = top + monthMarginEnd = end + monthMarginBottom = bottom + internalConfigUpdate = false + invalidateViewHolders() } /** diff --git a/sample/src/androidTest/java/com/kizitonwose/calenderviewsample/CalenderViewTests.kt b/sample/src/androidTest/java/com/kizitonwose/calenderviewsample/CalenderViewTests.kt index b96fd797..3ddc0dac 100644 --- a/sample/src/androidTest/java/com/kizitonwose/calenderviewsample/CalenderViewTests.kt +++ b/sample/src/androidTest/java/com/kizitonwose/calenderviewsample/CalenderViewTests.kt @@ -22,13 +22,13 @@ import com.kizitonwose.calendarview.ui.ViewContainer import com.kizitonwose.calendarview.utils.yearMonth import com.kizitonwose.calendarviewsample.* import org.junit.After -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue +import org.junit.Assert.* import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import java.lang.Thread.sleep +import java.time.DayOfWeek import java.time.LocalDate import java.time.YearMonth @@ -309,6 +309,24 @@ class CalenderViewTests { assertTrue(calendarView.findFirstVisibleMonth() == targetVisibleCalMonth) } + @Test + fun completionBlocksAreCalledOnTheMainThread() { + val calendarView = CalendarView(homeScreenRule.activity) + homeScreenRule.runOnUiThread { + val threadName = Thread.currentThread().name + calendarView.setupAsync(YearMonth.now(), YearMonth.now().plusMonths(10), DayOfWeek.SUNDAY) { + assertTrue(threadName == Thread.currentThread().name) + calendarView.updateMonthConfigurationAsync { + assertTrue(threadName == Thread.currentThread().name) + calendarView.updateMonthRangeAsync { + assertTrue(threadName == Thread.currentThread().name) + } + } + } + } + sleep(3000) + } + private inline fun findFragment(): T { return homeScreenRule.activity.supportFragmentManager .findFragmentByTag(T::class.java.simpleName) as T From bf58e144db4a8e16f9bfe8b62a47fe5bac1ceb7b Mon Sep 17 00:00:00 2001 From: Kizito Nwose Date: Sun, 26 Jul 2020 14:44:36 +0200 Subject: [PATCH 13/14] Clean up code. --- .../kizitonwose/calendarview/CalendarView.kt | 56 ++++++++++++++----- .../calendarviewsample/Example6Fragment.kt | 5 +- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt index 83110734..0fbfff3b 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt @@ -309,7 +309,7 @@ open class CalendarView : RecyclerView { */ @Px @Deprecated( - "Will be removed to clean up the library's API.", + "The new `daySize` property clarifies how cell sizing should be done.", replaceWith = ReplaceWith("daySize") ) var dayWidth: Int = DAY_SIZE_SQUARE @@ -327,7 +327,7 @@ open class CalendarView : RecyclerView { */ @Px @Deprecated( - "Will be removed to clean up the library's API.", + "The new `daySize` property clarifies how cell sizing should be done.", replaceWith = ReplaceWith("daySize") ) var dayHeight: Int = DAY_SIZE_SQUARE @@ -358,6 +358,10 @@ open class CalendarView : RecyclerView { */ @Px var monthPaddingStart = 0 + @Deprecated( + "Directly setting this along with related properties causes repeated invalidation of view holders.", + replaceWith = ReplaceWith("setMonthPadding") + ) set(value) { field = value invalidateViewHolders() @@ -369,6 +373,10 @@ open class CalendarView : RecyclerView { */ @Px var monthPaddingEnd = 0 + @Deprecated( + "Directly setting this along with related properties causes repeated invalidation of view holders.", + replaceWith = ReplaceWith("setMonthPadding") + ) set(value) { field = value invalidateViewHolders() @@ -380,6 +388,10 @@ open class CalendarView : RecyclerView { */ @Px var monthPaddingTop = 0 + @Deprecated( + "Directly setting this along with related properties causes repeated invalidation of view holders.", + replaceWith = ReplaceWith("setMonthPadding") + ) set(value) { field = value invalidateViewHolders() @@ -391,6 +403,10 @@ open class CalendarView : RecyclerView { */ @Px var monthPaddingBottom = 0 + @Deprecated( + "Directly setting this along with related properties causes repeated invalidation of view holders.", + replaceWith = ReplaceWith("setMonthPadding") + ) set(value) { field = value invalidateViewHolders() @@ -402,6 +418,10 @@ open class CalendarView : RecyclerView { */ @Px var monthMarginStart = 0 + @Deprecated( + "Directly setting this along with related properties causes repeated invalidation of view holders.", + replaceWith = ReplaceWith("setMonthMargins") + ) set(value) { field = value invalidateViewHolders() @@ -413,6 +433,10 @@ open class CalendarView : RecyclerView { */ @Px var monthMarginEnd = 0 + @Deprecated( + "Directly setting this along with related properties causes repeated invalidation of view holders.", + replaceWith = ReplaceWith("setMonthMargins") + ) set(value) { field = value invalidateViewHolders() @@ -424,6 +448,10 @@ open class CalendarView : RecyclerView { */ @Px var monthMarginTop = 0 + @Deprecated( + "Directly setting this along with related properties causes repeated invalidation of view holders.", + replaceWith = ReplaceWith("setMonthMargins") + ) set(value) { field = value invalidateViewHolders() @@ -435,6 +463,10 @@ open class CalendarView : RecyclerView { */ @Px var monthMarginBottom = 0 + @Deprecated( + "Directly setting this along with related properties causes repeated invalidation of view holders.", + replaceWith = ReplaceWith("setMonthMargins") + ) set(value) { field = value invalidateViewHolders() @@ -794,9 +826,8 @@ open class CalendarView : RecyclerView { * See [updateEndMonth] and [updateMonthRange]. */ @Deprecated( - "Will be removed to clean up the library's API.", - ReplaceWith("updateMonthRange()"), - DeprecationLevel.ERROR + "This helper method will be removed to clean up the library's API.", + ReplaceWith("updateMonthRange()") ) fun updateStartMonth(startMonth: YearMonth) = updateMonthRange(startMonth, requireEndMonth()) @@ -806,9 +837,8 @@ open class CalendarView : RecyclerView { * See [updateStartMonth] and [updateMonthRange]. */ @Deprecated( - "This will be removed in the future to clean up the library's API.", - ReplaceWith("updateMonthRange()"), - DeprecationLevel.ERROR + "This helper method will be removed to clean up the library's API.", + ReplaceWith("updateMonthRange()") ) fun updateEndMonth(endMonth: YearMonth) = updateMonthRange(requireStartMonth(), endMonth) @@ -898,15 +928,15 @@ open class CalendarView : RecyclerView { ) } - fun requireStartMonth(): YearMonth { + private fun requireStartMonth(): YearMonth { return startMonth ?: throw IllegalStateException("`startMonth` is not set. Have you called `setup()`?") } - fun requireEndMonth(): YearMonth { + private fun requireEndMonth(): YearMonth { return endMonth ?: throw IllegalStateException("`endMonth` is not set. Have you called `setup()`?") } - fun requireFirstDayOfWeek(): DayOfWeek { + private fun requireFirstDayOfWeek(): DayOfWeek { return firstDayOfWeek ?: throw IllegalStateException("`firstDayOfWeek` is not set. Have you called `setup()`?") } @@ -916,10 +946,6 @@ open class CalendarView : RecyclerView { * cells should have equal width and height. Each view's width and height * will be the width of the calender divided by 7. */ - @Deprecated( - "Will be removed to clean up the library's API.", - replaceWith = ReplaceWith("CalendarView.SIZE_SQUARE") - ) const val DAY_SIZE_SQUARE = Int.MIN_VALUE /** diff --git a/sample/src/main/java/com/kizitonwose/calendarviewsample/Example6Fragment.kt b/sample/src/main/java/com/kizitonwose/calendarviewsample/Example6Fragment.kt index 1c1fe54c..6393f1e6 100644 --- a/sample/src/main/java/com/kizitonwose/calendarviewsample/Example6Fragment.kt +++ b/sample/src/main/java/com/kizitonwose/calendarviewsample/Example6Fragment.kt @@ -60,10 +60,7 @@ class Example6Fragment : BaseFragment(R.layout.example_6_fragment), HasBackButto // Add margins around our card view. val horizontalMargin = dpToPx(8, requireContext()) val verticalMargin = dpToPx(14, requireContext()) - monthMarginStart = horizontalMargin - monthMarginEnd = horizontalMargin - monthMarginTop = verticalMargin - monthMarginBottom = verticalMargin + setMonthMargins(start = horizontalMargin, end = horizontalMargin, top = verticalMargin, bottom = verticalMargin) } class DayViewContainer(view: View) : ViewContainer(view) { From 8b29681d7c02d58845b0afa380812656586879a8 Mon Sep 17 00:00:00 2001 From: Kizito Nwose Date: Sun, 26 Jul 2020 15:49:03 +0200 Subject: [PATCH 14/14] Update README --- README.md | 49 +++++++++++++------ .../kizitonwose/calendarview/CalendarView.kt | 12 ++--- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 8e0b5e54..1d6f12f2 100644 --- a/README.md +++ b/README.md @@ -222,6 +222,8 @@ calendarView.dayBinder = object : DayBinder { #### Properties +All XML attributes are also available as properties of the CalendarView class via code. So in addition to those, we have: + - **monthScrollListener**: Called when the calendar scrolls to a new month. Mostly beneficial if `scrollMode` is `paged`. - **dayBinder**: An instance of `DayBinder` for managing day cell views. @@ -230,11 +232,9 @@ calendarView.dayBinder = object : DayBinder { - **monthFooterBinder**: An instance of `MonthHeaderFooterBinder` for managing footer views. -- **dayWidth**: The width, in pixels for each day cell view. - -- **dayHeight**: The height, in pixels for each day cell view. +- **daySize**: The size, in pixels for each day cell view. -Note that setting either `dayWidth` or `dayHeight` to `CalendarView.DAY_SIZE_SQUARE` makes the day cells have equal width and height which is basically the width of the calendar divided by 7. `DAY_SIZE_SQUARE` is the default day width and height value. +Note that setting the `daySize` property to `CalendarView.SIZE_SQUARE` makes the day cells have equal width and height which is basically the width of the calendar divided by 7. `SIZE_SQUARE` is the default size value. #### Methods @@ -248,28 +248,47 @@ Note that setting either `dayWidth` or `dayHeight` to `CalendarView.DAY_SIZE_SQU - **notifyCalendarChanged()**: Reload the entire calendar. -There's no need listing all available methods or repeating the documentation here. Please see the [CalendarView](https://github.com/kizitonwose/CalendarView/blob/master/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt) class for all properties and methods available with proper documentation. +- **findFirstVisibleMonth()** and **findLastVisibleMonth()**: Find the first and last visible months on the CalendarView respectively. + +- **findFirstVisibleDay()** and **findLastVisibleDay()**: Find the first and last visible days on the CalendarView respectively. + +- **setupAsync()**: Setup the CalendarView, *asynchronously*, useful if your `startMonth` and `endMonth` values are *many* years apart. + +- **updateMonthRange()**: Update the CalendarView's `startMonth` and/or `endMonth` values after the initial setup. The currently visible month is preserved. Use `updateMonthRangeAsync()` to do this asynchronously. + +- **updateMonthConfiguration()**: Update `inDateStyle`, `outDateStyle`, `maxRowCount` and `hasBoundaries` properties without generating the underlying calendar data repeatedly. Prefer this if setting more than one of these properties at the same time. Use `updateMonthConfigurationAsync()` to do this asynchronously. + + +There's no need to list all available methods or repeating the documentation here. Please see the [CalendarView](https://github.com/kizitonwose/CalendarView/blob/master/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt) class for all properties and methods available with proper documentation. ## Week view and Month view This library has no concept of week/month view. You'll need to configure the calendar to mimic this behavior by changing its state between a 6 or 1 row calendar, depending on your needs. This feature can be seen in Example 1 in the sample app. In summary, here's what you need: -```kotlin -// Common configurations for both modes. -calendarView.inDateStyle = InDateStyle.ALL_MONTHS -calendarView.outDateStyle = OutDateStyle.END_OF_ROW -calendarView.scrollMode = ScrollMode.PAGED -calendarView.orientation = RecyclerView.HORIZONTAL +```xml + +app:cv_orientation="horizontal" +app:cv_outDateStyle="endOfRow" +app:cv_inDateStyle="allMonths" +app:cv_scrollMode="paged" +``` +```kotlin val monthToWeek = monthViewCheckBox.isChecked if (monthToWeek) { // One row calendar for week mode - calendarView.maxRowCount = 1 - calendarView.hasBoundaries = false + calendarView.updateMonthConfiguration( + inDateStyle = InDateStyle.ALL_MONTHS, + maxRowCount = 1, + hasBoundaries = false + ) } else { // Six row calendar for month mode - calendarView.maxRowCount = 6 - calendarView.hasBoundaries = true + calendarView.updateMonthConfiguration( + inDateStyle = InDateStyle.FIRST_MONTH, + maxRowCount = 6, + hasBoundaries = true + ) } ``` diff --git a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt index 0fbfff3b..dc1582db 100644 --- a/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt +++ b/library/src/main/java/com/kizitonwose/calendarview/CalendarView.kt @@ -146,7 +146,7 @@ open class CalendarView : RecyclerView { * If set to [InDateStyle.NONE], inDates will not be generated, this means there will * be no offset on any month. * - * Note: This causes month data to be regenerated, consider using [updateMonthConfiguration] + * Note: This causes calendar data to be regenerated, consider using [updateMonthConfiguration] * if updating this property alongside [outDateStyle], [maxRowCount] or [hasBoundaries]. */ var inDateStyle = InDateStyle.ALL_MONTHS @@ -166,7 +166,7 @@ open class CalendarView : RecyclerView { * it reaches the end of a 6 x 7 grid. This means that all months will have 6 rows. * If set to [OutDateStyle.NONE], no outDates will be generated. * - * Note: This causes month data to be regenerated, consider using [updateMonthConfiguration] + * Note: This causes calendar data to be regenerated, consider using [updateMonthConfiguration] * if updating this value property [inDateStyle], [maxRowCount] or [hasBoundaries]. */ var outDateStyle = OutDateStyle.END_OF_ROW @@ -183,7 +183,7 @@ open class CalendarView : RecyclerView { * calendar the first one will show 4 rows and the second one will show the remaining 2 rows. * To show a week mode calendar, set this value to 1. * - * Note: This causes month data to be regenerated, consider using [updateMonthConfiguration] + * Note: This causes calendar data to be regenerated, consider using [updateMonthConfiguration] * if updating this property alongside [inDateStyle], [outDateStyle] or [hasBoundaries]. */ var maxRowCount = 6 @@ -208,7 +208,7 @@ open class CalendarView : RecyclerView { * - If [OutDateStyle] is [OutDateStyle.END_OF_GRID], outDates are generated for the last index until it * satisfies the [maxRowCount] requirement. * - * Note: This causes month data to be regenerated, consider using [updateMonthConfiguration] + * Note: This causes calendar data to be regenerated, consider using [updateMonthConfiguration] * if updating this property alongside [inDateStyle], [outDateStyle] or [maxRowCount]. */ var hasBoundaries = true @@ -520,7 +520,7 @@ open class CalendarView : RecyclerView { /** * Update [inDateStyle], [outDateStyle], [maxRowCount] and [hasBoundaries] - * without generating the underlying month data multiple times. + * without generating the underlying calendar data multiple times. * See [updateMonthConfigurationAsync] if you wish to do this asynchronously. */ fun updateMonthConfiguration( @@ -541,7 +541,7 @@ open class CalendarView : RecyclerView { /** * Update [inDateStyle], [outDateStyle], [maxRowCount] and [hasBoundaries] - * asynchronously without generating the underlying month data multiple times. + * asynchronously without generating the underlying calendar data multiple times. * Useful if your [startMonth] and [endMonth] values are many years apart. * See [updateMonthConfiguration] if you wish to do this synchronously. */