From 8890a4695c00433041c403114174bc031473b2f6 Mon Sep 17 00:00:00 2001 From: PekingSpades <180665176+PekingSpades@users.noreply.github.com> Date: Thu, 14 Apr 2022 17:44:51 +0800 Subject: [PATCH 1/4] feat: Add custom week calendar support and restore deprecated APIs - Add beginWeekCalendar property to WeekView for custom week number calculation - Restore deprecated navigation methods (goToToday, goToCurrentTime, goToDate, goToHour) - Restore deprecated scrolling properties (xScrollingSpeed, isHorizontalFlingEnabled, etc.) - Remove drag-and-drop functionality from event handling - Simplify event click/long-click callbacks - Change rendering from hardware to software acceleration - Change onCreateEntity from abstract to open method with runtime exception BREAKING CHANGE: Removed drag-and-drop support for events BREAKING CHANGE: Removed stickToActualWeek property --- .../java/com/alamkanak/weekview/WeekView.kt | 340 +++++++++++------- 1 file changed, 207 insertions(+), 133 deletions(-) diff --git a/core/src/main/java/com/alamkanak/weekview/WeekView.kt b/core/src/main/java/com/alamkanak/weekview/WeekView.kt index 8104bf7e..5273bcca 100644 --- a/core/src/main/java/com/alamkanak/weekview/WeekView.kt +++ b/core/src/main/java/com/alamkanak/weekview/WeekView.kt @@ -6,6 +6,7 @@ import android.content.res.Configuration import android.graphics.Canvas import android.graphics.RectF import android.graphics.Typeface +import android.os.Build import android.os.Parcelable import android.util.AttributeSet import android.view.MotionEvent @@ -14,7 +15,7 @@ import android.view.accessibility.AccessibilityManager import androidx.annotation.RequiresApi import androidx.core.view.ViewCompat import java.util.Calendar -import kotlin.math.abs +import kotlin.math.ceil import kotlin.math.min import kotlin.math.roundToInt @@ -28,7 +29,12 @@ class WeekView @JvmOverloads constructor( ViewStateFactory.create(context, attrs) } - private val eventsCacheProvider: EventsCacheProvider = { adapter?.eventsCache } + var beginWeekCalendar: Calendar ? = null + set(value) { + field = value + invalidate() + } + private val eventChipsCacheProvider: EventChipsCacheProvider = { adapter?.eventChipsCache } private val touchHandler = WeekViewTouchHandler(viewState) @@ -42,15 +48,10 @@ class WeekView @JvmOverloads constructor( notifyRangeChangedListener() } - override fun onVerticalScrollPositionChanged(distance: Float) { - notifyVerticalScrollChanged(distance) + override fun onVerticalScrollPositionChanged() { invalidate() } - override fun onVerticalScrollingFinished() { - notifyVerticalScrollFinished() - } - override fun requestInvalidation() { ViewCompat.postInvalidateOnAnimation(this@WeekView) } @@ -58,21 +59,11 @@ class WeekView @JvmOverloads constructor( private val navigator = Navigator(viewState = viewState, listener = navigationListener) - private val dragHandler = DragHandler( - viewState = viewState, - touchHandler = touchHandler, - navigator = navigator, - dragListener = { id -> adapter?.handleDragAndDrop(id) }, - eventsCacheProvider = eventsCacheProvider, - eventsProcessorProvider = { adapter?.eventsProcessor }, - ) - private val gestureHandler = WeekViewGestureHandler( context = context, viewState = viewState, touchHandler = touchHandler, - navigator = navigator, - dragHandler = dragHandler, + navigator = navigator ) private var accessibilityTouchHelper = WeekViewAccessibilityTouchHelper( @@ -85,7 +76,7 @@ class WeekView @JvmOverloads constructor( private val renderers: List = listOf( TimeColumnRenderer(viewState), CalendarRenderer(viewState, eventChipsCacheProvider), - HeaderRenderer(context, viewState, eventChipsCacheProvider, onHeaderHeightChanged = this::invalidate) + HeaderRenderer(context, viewState, eventChipsCacheProvider, onHeaderHeightChanged = this::invalidate, beginWeekCalendar = beginWeekCalendar) ) // We use width and height instead of view.isLaidOut(), because the latter seems to @@ -106,7 +97,7 @@ class WeekView @JvmOverloads constructor( ViewCompat.setAccessibilityDelegate(this, accessibilityTouchHelper) } - setLayerType(LAYER_TYPE_HARDWARE, null) + setLayerType(LAYER_TYPE_SOFTWARE, null) } override fun onConfigurationChanged(newConfig: Configuration) { @@ -195,7 +186,7 @@ class WeekView @JvmOverloads constructor( val didFirstVisibleDateChange = !currentFirstVisibleDate.isSameDate(newFirstVisibleDate) viewState.firstVisibleDate = newFirstVisibleDate - if (didFirstVisibleDateChange) { + if (didFirstVisibleDateChange && navigator.isNotRunning) { val newLastVisibleDate = newDateRange.last() adapter?.onRangeChanged( firstVisibleDate = newFirstVisibleDate, @@ -204,19 +195,6 @@ class WeekView @JvmOverloads constructor( } } - private fun notifyVerticalScrollChanged(distance: Float) { - if (abs(distance) >= 1f) { - adapter?.onVerticalScrollPositionChanged( - currentOffset = verticalScrollOffset, - distance = distance - ) - } - } - - private fun notifyVerticalScrollFinished() { - adapter?.onVerticalScrollFinished(currentOffset = verticalScrollOffset) - } - /* *********************************************************************************************** * @@ -248,11 +226,6 @@ class WeekView @JvmOverloads constructor( * Returns whether the first day of the week should be displayed at the start position * when WeekView is rendered for the first time. */ - @Deprecated( - message = "Use stickToWeekInWeekView instead.", - replaceWith = ReplaceWith(expression = "stickToWeekInWeekView"), - level = DeprecationLevel.ERROR, - ) @PublicApi var showFirstDayOfWeekFirst: Boolean get() = viewState.showFirstDayOfWeekFirst @@ -260,19 +233,6 @@ class WeekView @JvmOverloads constructor( viewState.showFirstDayOfWeekFirst = value } - /** - * Returns whether weeks should always be scrolled as a whole when showing a week view, meaning - * that the [firstVisibleDate] will actually be the first date of the currently displayed week - * (as per [Calendar.getFirstDayOfWeek]). If not, any day of the week can be the - * [firstVisibleDate]. - */ - @PublicApi - var stickToActualWeek: Boolean - get() = viewState.stickToActualWeek - set(value) { - viewState.stickToActualWeek = value - } - /** * Returns whether all-day events are arranged vertically. If false, all-day events are shown * in a horizontal arrangement, occupying only a single row. @@ -352,6 +312,7 @@ class WeekView @JvmOverloads constructor( var headerBottomShadowColor: Int @RequiresApi(api = 29) get() = viewState.headerBackgroundWithShadowPaint.shadowLayerColor + @RequiresApi(Build.VERSION_CODES.Q) set(value) { val paint = viewState.headerBackgroundWithShadowPaint paint.setShadowLayer(headerBottomShadowRadius.toFloat(), 0f, 0f, value) @@ -365,6 +326,7 @@ class WeekView @JvmOverloads constructor( var headerBottomShadowRadius: Int @RequiresApi(api = 29) get() = viewState.headerBackgroundWithShadowPaint.shadowLayerRadius.roundToInt() + @RequiresApi(Build.VERSION_CODES.Q) set(value) { val paint = viewState.headerBackgroundWithShadowPaint paint.setShadowLayer(value.toFloat(), 0f, 0f, headerBottomShadowColor) @@ -1143,6 +1105,35 @@ class WeekView @JvmOverloads constructor( *********************************************************************************************** */ + /** + * Returns the scrolling speed factor in horizontal direction. + */ + @PublicApi + @Deprecated( + message = "This value is no longer being taken into account.", + level = DeprecationLevel.ERROR + ) + var xScrollingSpeed: Float + get() = viewState.xScrollingSpeed + set(value) { + viewState.xScrollingSpeed = value + } + + /** + * Returns whether WeekView can fling horizontally. + */ + @PublicApi + @Deprecated( + message = "Use isHorizontalScrollingEnabled instead.", + replaceWith = ReplaceWith("isHorizontalScrollingEnabled"), + level = DeprecationLevel.ERROR + ) + var isHorizontalFlingEnabled: Boolean + get() = viewState.horizontalFlingEnabled + set(value) { + viewState.horizontalFlingEnabled = value + } + /** * Returns whether WeekView can scroll horizontally. */ @@ -1153,6 +1144,31 @@ class WeekView @JvmOverloads constructor( viewState.horizontalScrollingEnabled = value } + /** + * Returns whether WeekView can fling vertically. + */ + @Deprecated( + message = "This value is no longer being taken into account.", + level = DeprecationLevel.ERROR + ) + @PublicApi + var isVerticalFlingEnabled: Boolean + get() = viewState.verticalFlingEnabled + set(value) { + viewState.verticalFlingEnabled = value + } + + @PublicApi + @Deprecated( + message = "This value is no longer being taken into account.", + level = DeprecationLevel.ERROR + ) + var scrollDuration: Int + get() = viewState.scrollDuration + set(value) { + viewState.scrollDuration = value + } + @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean = gestureHandler.onTouchEvent(event) @@ -1242,6 +1258,51 @@ class WeekView @JvmOverloads constructor( navigator.scrollVerticallyTo(offset = finalOffset) } + /** + * Scrolls to the current date. + */ + @Deprecated( + message = "This method will be removed in a future release. Use scrollToDate() instead.", + replaceWith = ReplaceWith(expression = "scrollToDate"), + level = DeprecationLevel.ERROR + ) + @PublicApi + fun goToToday() { + scrollToDate(today()) + } + + /** + * Scrolls to the current date and time. + */ + @Deprecated( + message = "This method will be removed in a future release. Use scrollToDateTime() instead.", + replaceWith = ReplaceWith(expression = "scrollToDateTime"), + level = DeprecationLevel.ERROR + ) + @PublicApi + fun goToCurrentTime() { + internalScrollToDate( + date = now(), + onComplete = { scrollToTime(hour = it.hour, minute = it.minute) } + ) + } + + /** + * Scrolls to a specific date. If the date is before [minDate] or after [maxDate], [WeekView] + * will scroll to them instead. + * + * @param date The date to show. + */ + @Deprecated( + message = "This method will be removed in a future release. Use scrollToDate() instead.", + replaceWith = ReplaceWith(expression = "scrollToDate"), + level = DeprecationLevel.ERROR + ) + @PublicApi + fun goToDate(date: Calendar) { + scrollToDate(date) + } + private fun internalScrollToDate(date: Calendar, onComplete: (Calendar) -> Unit = {}) { val adjustedDate = viewState.getStartDateInAllowedRange(date) if (adjustedDate.toEpochDays() == viewState.firstVisibleDate.toEpochDays()) { @@ -1258,50 +1319,38 @@ class WeekView @JvmOverloads constructor( return } - navigator.scrollHorizontallyTo(date = adjustedDate) { - onComplete(adjustedDate) - } + navigator.scrollHorizontallyTo(date = date, onFinished = { onComplete(adjustedDate) }) } /** - * Returns the first hour that is visible on the screen. - */ - @PublicApi - val firstVisibleHour: Int - get() = viewState.firstVisibleHour - - /** - * Returns the first hour that is fully visible on the screen. - */ - @PublicApi - val firstFullyVisibleHour: Int - get() = viewState.firstFullyVisibleHour - - /** - * Returns the last hour that is visible on the screen. + * Scrolls to a specific hour. If the hour is before [minHour] or after [maxHour], [WeekView] + * will scroll to them instead. + * + * @param hour The hour to scroll to, in 24-hour format. Supported values are 0-24. */ + @Deprecated( + message = "This method will be removed in a future release. Use scrollToTime() instead.", + replaceWith = ReplaceWith(expression = "scrollToTime"), + level = DeprecationLevel.ERROR + ) @PublicApi - val lastVisibleHour: Int - get() = viewState.lastVisibleHour + fun goToHour(hour: Int) { + scrollToTime(hour = hour, minute = 0) + } /** - * Returns the last hour that is fully visible on the screen. + * Returns the first hour that is visible on the screen. */ @PublicApi - val lastFullyVisibleHour: Int - get() = viewState.lastFullyVisibleHour + val firstVisibleHour: Int + get() = viewState.minHour + (viewState.currentOrigin.y * -1 / viewState.hourHeight).toInt() /** - * Returns the current vertical offset (in pixels) from the top. This is 0 if WeekView is - * scrolled up all the way to [minHour]. + * Returns the first hour that is fully visible on the screen. */ @PublicApi - val verticalScrollOffset: Float - get() { - // Invert the current origin, as it feels more natural - // to have a positive verticalScrollOffset. - return viewState.currentOrigin.y * -1 - } + val firstFullyVisibleHour: Int + get() = viewState.minHour + ceil(viewState.currentOrigin.y * -1 / viewState.hourHeight).toInt() /* *********************************************************************************************** @@ -1345,6 +1394,22 @@ class WeekView @JvmOverloads constructor( invalidate() } + @PublicApi + @Deprecated( + message = "Use setDateFormatter() and setTimeFormatter() instead.", + level = DeprecationLevel.ERROR + ) + var dateTimeInterpreter: DateTimeInterpreter + get() = object : DateTimeInterpreter { + override fun interpretDate(date: Calendar): String = viewState.dateFormatter(date) + override fun interpretTime(hour: Int): String = viewState.timeFormatter(hour) + } + set(value) { + setDateFormatter { value.interpretDate(it) } + setTimeFormatter { value.interpretTime(it) } + invalidate() + } + @PublicApi fun setDateFormatter(formatter: DateFormatter) { viewState.dateFormatter = formatter @@ -1404,16 +1469,22 @@ class WeekView @JvmOverloads constructor( internal fun handleClick(x: Float, y: Float): Boolean { val eventChip = findHitEvent(x, y) ?: return false - val data = findEventData(id = eventChip.eventId) ?: return false + val data = findEventData(id = eventChip.originalEvent.id) ?: return false + + onEventClick(data) onEventClick(data, eventChip.bounds) + return true } - internal fun handleLongClick(x: Float, y: Float): LongClickResult? { - val eventChip = findHitEvent(x, y) ?: return null - val data = findEventData(id = eventChip.eventId) ?: return null - val handled = onEventLongClick(data, eventChip.bounds) - return LongClickResult(eventChip = eventChip, handled = handled) + internal fun handleLongClick(x: Float, y: Float): Boolean { + val eventChip = findHitEvent(x, y) ?: return false + val data = findEventData(id = eventChip.originalEvent.id) ?: return false + + onEventLongClick(data) + onEventLongClick(data, eventChip.bounds) + + return true } private fun findHitEvent(x: Float, y: Float): EventChip? { @@ -1437,11 +1508,13 @@ class WeekView @JvmOverloads constructor( internal fun onEventClick(id: Long, bounds: RectF) { val data = findEventData(id) ?: return + onEventClick(data) onEventClick(data, bounds) } - internal fun handleLongClick(id: Long, bounds: RectF) { + internal fun onEventLongClick(id: Long, bounds: RectF) { val data = findEventData(id) ?: return + onEventLongClick(data) onEventLongClick(data, bounds) } @@ -1459,7 +1532,11 @@ class WeekView @JvmOverloads constructor( * @param item The item of type [T] that was submitted to [WeekView] * @return A [WeekViewEntity] that will be rendered in [WeekView] */ - abstract fun onCreateEntity(item: T): WeekViewEntity + open fun onCreateEntity(item: T): WeekViewEntity { + throw RuntimeException( + "You called submitList() on WeekView's adapter, but didn't implement onCreateEntity()." + ) + } /** * Returns the data of the [WeekViewEntity.Event] that the user clicked on. @@ -1490,31 +1567,8 @@ class WeekView @JvmOverloads constructor( * * @param data The data of the [WeekViewEntity.Event] * @param bounds The [RectF] representing the bounds of the event's [EventChip] - * @return Whether the long click has been handled. If false, this will trigger drag-&-drop - * to activate. */ - open fun onEventLongClick(data: T, bounds: RectF): Boolean = false - - internal fun handleDragAndDrop(id: Long) { - val data = findEventData(id) ?: return - val match = eventsCache[id] ?: return - - onDragAndDropFinished( - data = data, - newStartTime = match.startTime, - newEndTime = match.endTime, - ) - } - - /** - * Called when a drag-&-drop gesture has finished to inform the caller of the dragged event's - * new start and end time. - * - * @param data The [T] entity that is associated with the dragged event - * @param newStartTime The new start time that the event was dragged to - * @param newEndTime THe new end time that the event was dragged to - */ - open fun onDragAndDropFinished(data: T, newStartTime: Calendar, newEndTime: Calendar) = Unit + open fun onEventLongClick(data: T, bounds: RectF) = Unit /** * Returns the date and time of the location that the user clicked on. @@ -1539,23 +1593,6 @@ class WeekView @JvmOverloads constructor( * @param lastVisibleDate A [Calendar] representing the last visible date */ open fun onRangeChanged(firstVisibleDate: Calendar, lastVisibleDate: Calendar) = Unit - - /** - * Called whenever the vertical scroll position in [WeekView] changes. A [distance] > 0 - * indicates that the user is scrolling down towards later hours; a [distance] < 0 that the - * user is scrolling up towards earlier hours. - * - * @param currentOffset The current vertical offset. - * @param distance The distance that the user scrolled vertically. - */ - open fun onVerticalScrollPositionChanged(currentOffset: Float, distance: Float) = Unit - - /** - * Called when the vertical scrolling in [WeekView] finished. - * - * @param currentOffset The current vertical offset. - */ - open fun onVerticalScrollFinished(currentOffset: Float) = Unit } /** @@ -1572,6 +1609,25 @@ class WeekView @JvmOverloads constructor( override val eventsCache = SimpleEventsCache() + /** + * Submits a new list of [WeekViewDisplayable] elements to the adapter. These events are + * processed on a background thread and then presented in [WeekView]. Previously submitted + * events are replaced completely. + * + * @param events The [WeekViewDisplayable] elements that are to be displayed in [WeekView] + */ + @PublicApi + @Deprecated( + message = "Use submitList() to submit a list of elements of type T instead. Then, overwrite the adapter's onCreateEntity() method to create a WeekViewEntity.", + replaceWith = ReplaceWith(expression = "submitList"), + level = DeprecationLevel.ERROR + ) + fun submit(events: List>) { + val viewState = weekView?.viewState ?: return + val entities = events.map { it.toWeekViewEntity(context) } + eventsProcessor.submit(entities, viewState, onFinished = this::updateObserver) + } + /** * Submits a new list of elements to the adapter. These events are processed on a background * thread and then presented in [WeekView]. Previously submitted events are replaced @@ -1605,6 +1661,24 @@ class WeekView @JvmOverloads constructor( override val eventsCache = PaginatedEventsCache() + /** + * Submits a new list of [WeekViewDisplayable] elements to the adapter. These events are + * processed on a background thread and then presented in [WeekView]. + * + * @param events The [WeekViewDisplayable] elements that are to be displayed in [WeekView] + */ + @PublicApi + @Deprecated( + message = "Use submitList() to submit a list of elements of type T instead. Then, overwrite the adapter's onCreateEntity() method to create a WeekViewEntity.", + replaceWith = ReplaceWith(expression = "submitList"), + level = DeprecationLevel.ERROR + ) + fun submit(events: List>) { + val viewState = weekView?.viewState ?: return + val entities = events.map { it.toWeekViewEntity(context) } + eventsProcessor.submit(entities, viewState, onFinished = this::updateObserver) + } + /** * Submits a new list of elements of type [T] to the adapter. These events are processed on * a background thread and then presented in [WeekView]. From b7df536a3b2422d427b52c061f68871694b9bb26 Mon Sep 17 00:00:00 2001 From: PekingSpades <180665176+PekingSpades@users.noreply.github.com> Date: Thu, 14 Apr 2022 17:45:36 +0800 Subject: [PATCH 2/4] feat: Implement custom week number calculation in HeaderRenderer - Add beginWeekCalendar parameter to HeaderRenderer constructor - Implement custom week number calculation based on beginWeekCalendar - Calculate week number from specified start date instead of default calendar week - Simplify date label drawing by removing single/multi-day view separation - Remove drawTimeColumnSeparatorExtension method (unused) - Add Java 8 time API dependencies for week calculation The week number calculation now supports custom start dates for academic or fiscal calendars. --- .../com/alamkanak/weekview/HeaderRenderer.kt | 115 +++++++++--------- 1 file changed, 60 insertions(+), 55 deletions(-) diff --git a/core/src/main/java/com/alamkanak/weekview/HeaderRenderer.kt b/core/src/main/java/com/alamkanak/weekview/HeaderRenderer.kt index 211aebe1..d0860871 100644 --- a/core/src/main/java/com/alamkanak/weekview/HeaderRenderer.kt +++ b/core/src/main/java/com/alamkanak/weekview/HeaderRenderer.kt @@ -6,11 +6,17 @@ import android.graphics.Paint import android.graphics.Rect import android.graphics.RectF import android.graphics.drawable.Drawable +import android.os.Build import android.text.StaticLayout import android.text.TextPaint import android.util.SparseArray +import androidx.annotation.RequiresApi import androidx.collection.ArrayMap import androidx.core.content.ContextCompat +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.temporal.ChronoUnit import java.util.Calendar import kotlin.math.roundToInt @@ -18,6 +24,7 @@ internal class HeaderRenderer( context: Context, viewState: ViewState, eventChipsCacheProvider: EventChipsCacheProvider, + beginWeekCalendar: Calendar? = null, onHeaderHeightChanged: () -> Unit ) : Renderer, DateFormatterDependent { @@ -48,7 +55,8 @@ internal class HeaderRenderer( private val headerDrawer = HeaderDrawer( context = context, - viewState = viewState + viewState = viewState, + beginWeekCalendar = beginWeekCalendar ) override fun onSizeChanged(width: Int, height: Int) { @@ -61,6 +69,7 @@ internal class HeaderRenderer( dateLabelLayouts.clear() } + @RequiresApi(Build.VERSION_CODES.O) override fun render(canvas: Canvas) { eventsUpdater.update() headerUpdater.update() @@ -139,43 +148,20 @@ private class DateLabelsDrawer( ) : Drawer { override fun draw(canvas: Canvas) { - if (viewState.numberOfVisibleDays > 1) { - canvas.drawDateLabelInMultiDayView() - } else { - canvas.drawDateLabelInSingleDayView() - } - } - - private fun Canvas.drawDateLabelInSingleDayView() { - val bounds = viewState.weekNumberBounds - val date = viewState.dateRange.first() - - val key = date.toEpochDays() - val textLayout = dateLabelLayouts[key] - - withTranslation( - x = bounds.centerX(), - y = viewState.headerPadding, - ) { - draw(textLayout) - } - } - - private fun Canvas.drawDateLabelInMultiDayView() { - drawInBounds(viewState.headerBounds) { + canvas.drawInBounds(viewState.headerBounds) { viewState.dateRangeWithStartPixels.forEach { (date, startPixel) -> drawLabel(date, startPixel) } } } - private fun Canvas.drawLabel(date: Calendar, startPixel: Float) { - val key = date.toEpochDays() + private fun Canvas.drawLabel(day: Calendar, startPixel: Float) { + val key = day.toEpochDays() val textLayout = dateLabelLayouts[key] withTranslation( x = startPixel + viewState.dayWidth / 2f, - y = viewState.headerPadding, + y = viewState.headerPadding ) { draw(textLayout) } @@ -197,7 +183,8 @@ private class AllDayEventsUpdater( get() { val didScrollHorizontally = previousHorizontalOrigin != viewState.currentOrigin.x val dateRange = viewState.dateRange - val eventChips = eventChipsCacheProvider()?.allDayEventChipsInDateRange(dateRange).orEmpty() + val eventChips = + eventChipsCacheProvider()?.allDayEventChipsInDateRange(dateRange).orEmpty() val containsNewChips = eventChips.any { it.bounds.isEmpty } return didScrollHorizontally || containsNewChips } @@ -330,10 +317,7 @@ internal class AllDayEventsDrawer( priorEventChip.bounds.right - viewState.eventPaddingHorizontal.toFloat() } - val y = priorEventChip.bounds.bottom + - viewState.eventMarginVertical + - viewState.eventPaddingVertical + - textPaint.textSize + val y = priorEventChip.bounds.bottom + viewState.eventMarginVertical + viewState.eventPaddingVertical + textPaint.textSize drawText(text, x, y, textPaint) } @@ -341,7 +325,8 @@ internal class AllDayEventsDrawer( private class HeaderDrawer( context: Context, - private val viewState: ViewState + private val viewState: ViewState, + private val beginWeekCalendar: Calendar? = null ) : Drawer { private val upArrow: Drawable by lazy { @@ -352,6 +337,7 @@ private class HeaderDrawer( checkNotNull(ContextCompat.getDrawable(context, R.drawable.ic_arrow_down)) } + @RequiresApi(Build.VERSION_CODES.O) override fun draw(canvas: Canvas) { val width = viewState.viewWidth.toFloat() @@ -363,12 +349,8 @@ private class HeaderDrawer( canvas.drawRect(0f, 0f, width, viewState.headerHeight, backgroundPaint) - if (viewState.showWeekNumber && viewState.numberOfVisibleDays > 1) { - canvas.drawWeekNumber() - } - - if (viewState.showTimeColumnSeparator) { - canvas.drawTimeColumnSeparatorExtension() + if (viewState.showWeekNumber) { + canvas.drawWeekNumber(beginWeekCalendar = this.beginWeekCalendar) } if (viewState.showAllDayEventsToggleArrow) { @@ -381,8 +363,44 @@ private class HeaderDrawer( } } - private fun Canvas.drawWeekNumber() { - val weekNumber = viewState.dateRange.first().weekOfYear.toString() + @RequiresApi(Build.VERSION_CODES.O) + private fun Canvas.drawWeekNumber(beginWeekCalendar: Calendar?) { + + var weekNumber = viewState.dateRange.first().weekOfYear.toString() + // 自己适配周数 + beginWeekCalendar?.apply { + var now = viewState.dateRange.first().copy() + now.apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + } + + var begin2 = Calendar.getInstance() + begin2.apply { + set(Calendar.YEAR, now.get(Calendar.YEAR)) + set(Calendar.DAY_OF_MONTH, beginWeekCalendar.get(Calendar.DAY_OF_MONTH)) + set(Calendar.MONTH, beginWeekCalendar.get(Calendar.MONTH)) + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + add(Calendar.DAY_OF_YEAR, (get(Calendar.DAY_OF_WEEK) - Calendar.SUNDAY) * -1) + } + if (begin2.compareTo(now) > 0) { + begin2.apply { + set(Calendar.YEAR, get(Calendar.YEAR) - 1) + } + } + val d1i: Instant = Instant.ofEpochMilli(begin2.getTimeInMillis()) + val d2i: Instant = Instant.ofEpochMilli(now.getTimeInMillis()) + + val startDate: LocalDateTime = LocalDateTime.ofInstant(d1i, ZoneId.systemDefault()) + val endDate: LocalDateTime = LocalDateTime.ofInstant(d2i, ZoneId.systemDefault()) + var i: Int = (ChronoUnit.DAYS.between(startDate, endDate) / 7).toInt() + 1 + weekNumber = i.toString() + } val bounds = viewState.weekNumberBounds val textPaint = viewState.weekNumberTextPaint @@ -409,19 +427,6 @@ private class HeaderDrawer( drawText(weekNumber, bounds.centerX(), bounds.centerY() + textOffset, textPaint) } - private fun Canvas.drawTimeColumnSeparatorExtension() { - val startX = if (viewState.isLtr) { - viewState.timeColumnWidth - viewState.timeColumnSeparatorPaint.strokeWidth / 2 - } else { - viewState.viewWidth - viewState.timeColumnWidth - } - - val startY = viewState.headerPadding - val stopY = viewState.headerHeight - - drawLine(startX, startY, startX, stopY, viewState.timeColumnSeparatorPaint) - } - private fun Canvas.drawAllDayEventsToggleArrow() = with(viewState) { val bottom = (headerHeight - headerPadding).roundToInt() val top = bottom - currentAllDayEventHeight From af6bf42a3342eaa434300a06d008772f9b59d7db Mon Sep 17 00:00:00 2001 From: PekingSpades <180665176+PekingSpades@users.noreply.github.com> Date: Thu, 14 Apr 2022 18:20:36 +0800 Subject: [PATCH 3/4] refactor: Expose beginWeekCalendar as public API - Add @PublicApi annotation to beginWeekCalendar property - Add explicit getter for beginWeekCalendar - Remove unnecessary @RequiresApi(Build.VERSION_CODES.Q) annotations - Clean up Build version code references - Pass beginWeekCalendar to HeaderRenderer using 'this' reference This change allows external access to configure custom week start dates for week number calculation. --- core/src/main/java/com/alamkanak/weekview/WeekView.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/alamkanak/weekview/WeekView.kt b/core/src/main/java/com/alamkanak/weekview/WeekView.kt index 5273bcca..9027356c 100644 --- a/core/src/main/java/com/alamkanak/weekview/WeekView.kt +++ b/core/src/main/java/com/alamkanak/weekview/WeekView.kt @@ -6,7 +6,6 @@ import android.content.res.Configuration import android.graphics.Canvas import android.graphics.RectF import android.graphics.Typeface -import android.os.Build import android.os.Parcelable import android.util.AttributeSet import android.view.MotionEvent @@ -28,8 +27,9 @@ class WeekView @JvmOverloads constructor( private val viewState: ViewState by lazy { ViewStateFactory.create(context, attrs) } - - var beginWeekCalendar: Calendar ? = null + @PublicApi + var beginWeekCalendar: Calendar?= null + get() = field set(value) { field = value invalidate() @@ -76,7 +76,7 @@ class WeekView @JvmOverloads constructor( private val renderers: List = listOf( TimeColumnRenderer(viewState), CalendarRenderer(viewState, eventChipsCacheProvider), - HeaderRenderer(context, viewState, eventChipsCacheProvider, onHeaderHeightChanged = this::invalidate, beginWeekCalendar = beginWeekCalendar) + HeaderRenderer(context, viewState, eventChipsCacheProvider, onHeaderHeightChanged = this::invalidate, beginWeekCalendar = this.beginWeekCalendar) ) // We use width and height instead of view.isLaidOut(), because the latter seems to @@ -312,7 +312,6 @@ class WeekView @JvmOverloads constructor( var headerBottomShadowColor: Int @RequiresApi(api = 29) get() = viewState.headerBackgroundWithShadowPaint.shadowLayerColor - @RequiresApi(Build.VERSION_CODES.Q) set(value) { val paint = viewState.headerBackgroundWithShadowPaint paint.setShadowLayer(headerBottomShadowRadius.toFloat(), 0f, 0f, value) @@ -326,7 +325,6 @@ class WeekView @JvmOverloads constructor( var headerBottomShadowRadius: Int @RequiresApi(api = 29) get() = viewState.headerBackgroundWithShadowPaint.shadowLayerRadius.roundToInt() - @RequiresApi(Build.VERSION_CODES.Q) set(value) { val paint = viewState.headerBackgroundWithShadowPaint paint.setShadowLayer(value.toFloat(), 0f, 0f, headerBottomShadowColor) From 39e2dff8f3c47e55ad8eadadfaf949b5d4f1b7ea Mon Sep 17 00:00:00 2001 From: PekingSpades <180665176+PekingSpades@users.noreply.github.com> Date: Thu, 14 Apr 2022 18:21:08 +0800 Subject: [PATCH 4/4] refactor: Replace Java 8 time API with Calendar API for week calculation - Replace Java 8 time API (Instant, LocalDateTime, ChronoUnit) with Calendar API - Simplify week number calculation using milliseconds instead of ChronoUnit.DAYS - Remove @RequiresApi(Build.VERSION_CODES.O) annotations - Reorder constructor parameters (move beginWeekCalendar to end) - Format code for better readability - Fix import statements (use java.util.* instead of specific Calendar import) This change improves compatibility with older Android versions by removing Java 8 time API dependencies. --- .../com/alamkanak/weekview/HeaderRenderer.kt | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/com/alamkanak/weekview/HeaderRenderer.kt b/core/src/main/java/com/alamkanak/weekview/HeaderRenderer.kt index d0860871..8a10a125 100644 --- a/core/src/main/java/com/alamkanak/weekview/HeaderRenderer.kt +++ b/core/src/main/java/com/alamkanak/weekview/HeaderRenderer.kt @@ -6,26 +6,24 @@ import android.graphics.Paint import android.graphics.Rect import android.graphics.RectF import android.graphics.drawable.Drawable -import android.os.Build import android.text.StaticLayout import android.text.TextPaint import android.util.SparseArray -import androidx.annotation.RequiresApi import androidx.collection.ArrayMap import androidx.core.content.ContextCompat import java.time.Instant import java.time.LocalDateTime import java.time.ZoneId import java.time.temporal.ChronoUnit -import java.util.Calendar +import java.util.* import kotlin.math.roundToInt internal class HeaderRenderer( context: Context, viewState: ViewState, eventChipsCacheProvider: EventChipsCacheProvider, - beginWeekCalendar: Calendar? = null, - onHeaderHeightChanged: () -> Unit + onHeaderHeightChanged: () -> Unit, + private var beginWeekCalendar: Calendar? = null ) : Renderer, DateFormatterDependent { private val allDayEventLabels = ArrayMap() @@ -56,7 +54,7 @@ internal class HeaderRenderer( private val headerDrawer = HeaderDrawer( context = context, viewState = viewState, - beginWeekCalendar = beginWeekCalendar + beginWeekCalendar = this.beginWeekCalendar ) override fun onSizeChanged(width: Int, height: Int) { @@ -69,7 +67,6 @@ internal class HeaderRenderer( dateLabelLayouts.clear() } - @RequiresApi(Build.VERSION_CODES.O) override fun render(canvas: Canvas) { eventsUpdater.update() headerUpdater.update() @@ -183,8 +180,7 @@ private class AllDayEventsUpdater( get() { val didScrollHorizontally = previousHorizontalOrigin != viewState.currentOrigin.x val dateRange = viewState.dateRange - val eventChips = - eventChipsCacheProvider()?.allDayEventChipsInDateRange(dateRange).orEmpty() + val eventChips = eventChipsCacheProvider()?.allDayEventChipsInDateRange(dateRange).orEmpty() val containsNewChips = eventChips.any { it.bounds.isEmpty } return didScrollHorizontally || containsNewChips } @@ -317,7 +313,10 @@ internal class AllDayEventsDrawer( priorEventChip.bounds.right - viewState.eventPaddingHorizontal.toFloat() } - val y = priorEventChip.bounds.bottom + viewState.eventMarginVertical + viewState.eventPaddingVertical + textPaint.textSize + val y = priorEventChip.bounds.bottom + + viewState.eventMarginVertical + + viewState.eventPaddingVertical + + textPaint.textSize drawText(text, x, y, textPaint) } @@ -326,7 +325,7 @@ internal class AllDayEventsDrawer( private class HeaderDrawer( context: Context, private val viewState: ViewState, - private val beginWeekCalendar: Calendar? = null + private val beginWeekCalendar: Calendar? ) : Drawer { private val upArrow: Drawable by lazy { @@ -337,7 +336,6 @@ private class HeaderDrawer( checkNotNull(ContextCompat.getDrawable(context, R.drawable.ic_arrow_down)) } - @RequiresApi(Build.VERSION_CODES.O) override fun draw(canvas: Canvas) { val width = viewState.viewWidth.toFloat() @@ -363,8 +361,7 @@ private class HeaderDrawer( } } - @RequiresApi(Build.VERSION_CODES.O) - private fun Canvas.drawWeekNumber(beginWeekCalendar: Calendar?) { + private fun Canvas.drawWeekNumber(beginWeekCalendar : Calendar ? = null) { var weekNumber = viewState.dateRange.first().weekOfYear.toString() // 自己适配周数 @@ -393,12 +390,9 @@ private class HeaderDrawer( set(Calendar.YEAR, get(Calendar.YEAR) - 1) } } - val d1i: Instant = Instant.ofEpochMilli(begin2.getTimeInMillis()) - val d2i: Instant = Instant.ofEpochMilli(now.getTimeInMillis()) - val startDate: LocalDateTime = LocalDateTime.ofInstant(d1i, ZoneId.systemDefault()) - val endDate: LocalDateTime = LocalDateTime.ofInstant(d2i, ZoneId.systemDefault()) - var i: Int = (ChronoUnit.DAYS.between(startDate, endDate) / 7).toInt() + 1 + val between_days: Long = (now.timeInMillis - begin2.timeInMillis) / (1000 * 3600 * 24) + var i: Int = (between_days / 7).toInt() + 1 weekNumber = i.toString() } @@ -427,6 +421,7 @@ private class HeaderDrawer( drawText(weekNumber, bounds.centerX(), bounds.centerY() + textOffset, textPaint) } + private fun Canvas.drawAllDayEventsToggleArrow() = with(viewState) { val bottom = (headerHeight - headerPadding).roundToInt() val top = bottom - currentAllDayEventHeight