diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/alignment/LayoutAlignment.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/alignment/LayoutAlignment.kt index c21571cb..1b8907bf 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/alignment/LayoutAlignment.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/alignment/LayoutAlignment.kt @@ -27,7 +27,7 @@ import kotlin.math.sign internal class LayoutAlignment( private val layoutManager: LayoutManager, - private val layoutInfo: LayoutInfo + private val layoutInfo: LayoutInfo, ) { companion object { @@ -242,40 +242,41 @@ internal class LayoutAlignment( startViewAnchor = Int.MIN_VALUE } if (!reverseLayout) { + parentAlignmentCalculator.updateScrollLimits( + startEdge = startEdge, + endEdge = endEdge, + startViewAnchor = startViewAnchor, + endViewAnchor = endViewAnchor, + alignment = parentAlignment + ) if (layoutInfo.isLoopingAllowed) { // If we're looping, there's no end scroll limit parentAlignmentCalculator.invalidateEndLimit() - } else { - parentAlignmentCalculator.updateEndLimit(endEdge, endViewAnchor, parentAlignment) } if (layoutInfo.isLoopingStart) { parentAlignmentCalculator.invalidateStartLimit() - } else { - parentAlignmentCalculator.updateStartLimit( - startEdge, startViewAnchor, parentAlignment - ) } } else { + parentAlignmentCalculator.updateScrollLimits( + startEdge = endEdge, + endEdge = startEdge, + startViewAnchor = endViewAnchor, + endViewAnchor = startViewAnchor, + alignment = parentAlignment + ) if (layoutInfo.isLoopingAllowed) { parentAlignmentCalculator.invalidateStartLimit() - } else { - parentAlignmentCalculator.updateStartLimit(endEdge, endViewAnchor, parentAlignment) } if (layoutInfo.isLoopingStart) { parentAlignmentCalculator.invalidateEndLimit() - } else { - parentAlignmentCalculator.updateEndLimit( - startEdge, startViewAnchor, parentAlignment - ) } - } } private fun isEndAvailable( adapterPosition: Int, maxLayoutPosition: Int, - minLayoutPosition: Int + minLayoutPosition: Int, ): Boolean { return if (!reverseLayout) { adapterPosition == maxLayoutPosition @@ -287,7 +288,7 @@ internal class LayoutAlignment( private fun isStartAvailable( adapterPosition: Int, maxLayoutPosition: Int, - minLayoutPosition: Int + minLayoutPosition: Int, ): Boolean { return if (!reverseLayout) { adapterPosition == minLayoutPosition @@ -343,7 +344,7 @@ internal class LayoutAlignment( private fun calculateAdjustedAlignedScrollDistance( offset: Int, view: View, - childView: View + childView: View, ): Int { var scrollValue = offset val subPosition = getSubPositionOfView(view, childView) diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/alignment/ParentAlignmentCalculator.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/alignment/ParentAlignmentCalculator.kt index f7fe3739..80bd763a 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/alignment/ParentAlignmentCalculator.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/alignment/ParentAlignmentCalculator.kt @@ -82,58 +82,60 @@ internal class ParentAlignmentCalculator { endScrollLimit = Int.MAX_VALUE } - fun updateStartLimit( - edge: Int, - viewAnchor: Int, + fun updateScrollLimits( + startEdge: Int, + endEdge: Int, + startViewAnchor: Int, + endViewAnchor: Int, alignment: ParentAlignment, ) { - startEdge = edge - if (isStartUnknown) { - startScrollLimit = Int.MIN_VALUE - return + this.startEdge = startEdge + this.endEdge = endEdge + val keyline = calculateKeyline(alignment) + startScrollLimit = when { + isStartUnknown -> Int.MIN_VALUE + shouldAlignViewToStart(startViewAnchor, keyline, alignment) -> { + calculateScrollOffsetToStartEdge(startEdge) + } + + shouldAlignStartToKeyline(alignment) -> { + calculateScrollOffsetToKeyline(startViewAnchor, keyline) + } + + else -> 0 } - val keyLine = calculateKeyline(alignment) - startScrollLimit = if (shouldAlignViewToStart(viewAnchor, keyLine, alignment)) { - calculateScrollOffsetToStartEdge(edge) - } else if (isLayoutComplete() - || alignment.preferKeylineOverEdge - || !shouldAlignToStartEdge(alignment.edge) - ) { - calculateScrollOffsetToKeyline(viewAnchor, keyLine) - } else { - 0 + endScrollLimit = when { + isEndUnknown -> Int.MAX_VALUE + shouldAlignViewToEnd(endViewAnchor, keyline, alignment) -> { + calculateScrollOffsetToEndEdge(endEdge) + } + + shouldAlignEndToKeyline(alignment) -> { + calculateScrollOffsetToKeyline(endViewAnchor, keyline) + } + + else -> 0 } } - fun updateEndLimit( - edge: Int, - viewAnchor: Int, - alignment: ParentAlignment, - ) { - endEdge = edge - if (isEndUnknown) { - endScrollLimit = Int.MAX_VALUE - return - } - val keyline = calculateKeyline(alignment) - endScrollLimit = if (shouldAlignViewToEnd(viewAnchor, keyline, alignment)) { - calculateScrollOffsetToEndEdge(edge) - } else if (isLayoutComplete() - || alignment.preferKeylineOverEdge - || !shouldAlignToEndEdge(alignment.edge) - ) { - calculateScrollOffsetToKeyline(viewAnchor, keyline) - } else { - 0 - } + private fun shouldAlignStartToKeyline(alignment: ParentAlignment): Boolean { + return isLayoutComplete() + || alignment.preferKeylineOverEdge + || !shouldAlignToStartEdge(alignment.edge) + } + + private fun shouldAlignEndToKeyline(alignment: ParentAlignment): Boolean { + return isLayoutComplete() + || alignment.preferKeylineOverEdge + || !shouldAlignToEndEdge(alignment.edge) } private fun calculateScrollOffsetToEndEdge(edge: Int): Int { - return edge - getLayoutEndEdge() + return edge - getLayoutAbsoluteEnd() } private fun calculateScrollOffsetToStartEdge(edge: Int): Int { - return edge - getLayoutStartEdge() + return edge - getLayoutAbsoluteStart() } /** @@ -192,8 +194,8 @@ internal class ParentAlignmentCalculator { if (isStartUnknown || !shouldAlignToStartEdge(alignment.edge)) { return false } - if (isLayoutComplete()) { - return viewAnchor + getLayoutStartEdge() <= startEdge + keyline + if (isLayoutComplete() || isLayoutCompleteInOppositeDirection()) { + return viewAnchor + getLayoutAbsoluteStart() <= startEdge + keyline } return isLayoutIncomplete() && !alignment.preferKeylineOverEdge } @@ -206,8 +208,8 @@ internal class ParentAlignmentCalculator { if (isEndUnknown || !shouldAlignToEndEdge(alignment.edge)) { return false } - if (isLayoutComplete()) { - return viewAnchor + getLayoutEndEdge() >= endEdge + keyline + if (isLayoutComplete() || isLayoutCompleteInOppositeDirection()) { + return viewAnchor + getLayoutAbsoluteEnd() >= endEdge + keyline } return isLayoutIncomplete() && !alignment.preferKeylineOverEdge } @@ -216,16 +218,24 @@ internal class ParentAlignmentCalculator { return anchor - keyline } - private fun getLayoutEndEdge(): Int { + private fun getLayoutAbsoluteEnd(): Int { return size - paddingEnd } - private fun getLayoutStartEdge(): Int { + private fun getLayoutAbsoluteStart(): Int { return paddingStart } private fun isLayoutComplete(): Boolean { - return endEdge >= getLayoutEndEdge() && startEdge <= getLayoutStartEdge() + return endEdge >= getLayoutAbsoluteEnd() && startEdge <= getLayoutAbsoluteStart() + } + + private fun isLayoutCompleteInOppositeDirection(): Boolean { + return if (!reverseLayout) { + startEdge <= getLayoutAbsoluteStart() + } else { + endEdge >= getLayoutAbsoluteEnd() + } } private fun isLayoutIncomplete(): Boolean { @@ -233,9 +243,9 @@ internal class ParentAlignmentCalculator { return false } return if (!reverseLayout) { - endEdge < getLayoutEndEdge() + endEdge < getLayoutAbsoluteEnd() } else { - startEdge > getLayoutStartEdge() + startEdge > getLayoutAbsoluteStart() } } diff --git a/dpadrecyclerview/src/test/java/com/rubensousa/dpadrecyclerview/test/layoutmanager/alignment/ParentAlignmentCalculatorTest.kt b/dpadrecyclerview/src/test/java/com/rubensousa/dpadrecyclerview/test/layoutmanager/alignment/ParentAlignmentCalculatorTest.kt index 3e60c4e2..b189df04 100644 --- a/dpadrecyclerview/src/test/java/com/rubensousa/dpadrecyclerview/test/layoutmanager/alignment/ParentAlignmentCalculatorTest.kt +++ b/dpadrecyclerview/src/test/java/com/rubensousa/dpadrecyclerview/test/layoutmanager/alignment/ParentAlignmentCalculatorTest.kt @@ -64,9 +64,11 @@ class ParentAlignmentCalculatorTest { fun `keyline for child near start edge is the start edge`() { setLayoutProperties(orientation = RecyclerView.VERTICAL, reverseLayout = false) - alignmentCalculator.updateStartLimit( - edge = 0, - viewAnchor = verticalViewHeight / 2, + alignmentCalculator.updateScrollLimits( + startEdge = 0, + endEdge = height, + startViewAnchor = verticalViewHeight / 2, + endViewAnchor = verticalViewHeight / 2, alignment = centerParentAlignment ) @@ -82,9 +84,11 @@ class ParentAlignmentCalculatorTest { fun `keyline for child near end edge is the end edge`() { setLayoutProperties(orientation = RecyclerView.VERTICAL, reverseLayout = false) - alignmentCalculator.updateEndLimit( - edge = height, - viewAnchor = height - verticalViewHeight / 2, + alignmentCalculator.updateScrollLimits( + startEdge = 0, + endEdge = height, + startViewAnchor = 0, + endViewAnchor = height - verticalViewHeight / 2, alignment = centerParentAlignment ) @@ -137,14 +141,16 @@ class ParentAlignmentCalculatorTest { fun `child is aligned to end edge in regular layout direction`() { setLayoutProperties(orientation = RecyclerView.VERTICAL, reverseLayout = false) - alignmentCalculator.updateEndLimit( - edge = height / 2 + verticalViewHeight / 2, - viewAnchor = verticalViewHeight / 2, + alignmentCalculator.updateScrollLimits( + startEdge = 0, + endEdge = height + verticalViewHeight, + startViewAnchor = verticalViewHeight / 2, + endViewAnchor = height + verticalViewHeight / 2, alignment = centerParentAlignment ) assertThat( alignmentCalculator.calculateScrollOffset( - viewAnchor = height / 2, + viewAnchor = height + verticalViewHeight / 2, alignment = centerParentAlignment ) ).isEqualTo(alignmentCalculator.endScrollLimit) @@ -155,9 +161,11 @@ class ParentAlignmentCalculatorTest { fun `child is aligned to start edge in regular layout direction`() { setLayoutProperties(orientation = RecyclerView.VERTICAL, reverseLayout = false) - alignmentCalculator.updateStartLimit( - edge = height / 2 - verticalViewHeight / 2, - viewAnchor = verticalViewHeight / 2, + alignmentCalculator.updateScrollLimits( + startEdge = height / 2 - verticalViewHeight / 2, + endEdge = height / 2 + verticalViewHeight / 2, + startViewAnchor = verticalViewHeight / 2, + endViewAnchor = verticalViewHeight / 2, alignment = centerParentAlignment ) @@ -173,9 +181,11 @@ class ParentAlignmentCalculatorTest { fun `child is aligned to end edge in reverse layout`() { setLayoutProperties(orientation = RecyclerView.VERTICAL, reverseLayout = true) - alignmentCalculator.updateEndLimit( - edge = height / 2 + verticalViewHeight / 2, - viewAnchor = verticalViewHeight / 2, + alignmentCalculator.updateScrollLimits( + startEdge = height / 2 - verticalViewHeight / 2, + endEdge = height / 2 + verticalViewHeight / 2, + startViewAnchor = verticalViewHeight / 2, + endViewAnchor = verticalViewHeight / 2, alignment = centerParentAlignment ) @@ -194,18 +204,27 @@ class ParentAlignmentCalculatorTest { fun `child is aligned to start edge in reverse layout`() { setLayoutProperties(orientation = RecyclerView.VERTICAL, reverseLayout = true) - alignmentCalculator.updateStartLimit( - edge = height / 2 - verticalViewHeight / 2, - viewAnchor = verticalViewHeight / 2, - alignment = centerParentAlignment + val alignment = ParentAlignment( + edge = ParentAlignment.Edge.MAX, + fraction = 1.0f ) - assertThat(alignmentCalculator.startScrollLimit).isEqualTo(height / 2 - verticalViewHeight / 2) + alignmentCalculator.updateScrollLimits( + startEdge = height, + endEdge = verticalViewHeight, + startViewAnchor = verticalViewHeight, + endViewAnchor = verticalViewHeight, + alignment = alignment + ) + + assertThat(alignmentCalculator.startScrollLimit).isEqualTo( + verticalViewHeight + ) assertThat( alignmentCalculator.calculateScrollOffset( - viewAnchor = height / 2, - alignment = centerParentAlignment + viewAnchor = verticalViewHeight, + alignment = alignment ) ).isEqualTo(alignmentCalculator.startScrollLimit) } @@ -216,17 +235,13 @@ class ParentAlignmentCalculatorTest { val alignment = ParentAlignment( edge = ParentAlignment.Edge.MIN, offset = 0, - fraction = 0.5f, - preferKeylineOverEdge = false - ) - alignmentCalculator.updateStartLimit( - edge = verticalCenterKeyline - verticalViewHeight / 2, - viewAnchor = verticalCenterKeyline, - alignment = alignment + fraction = 0.5f ) - alignmentCalculator.updateEndLimit( - edge = verticalCenterKeyline + verticalViewHeight / 2, - viewAnchor = verticalCenterKeyline, + alignmentCalculator.updateScrollLimits( + startEdge = verticalCenterKeyline - verticalViewHeight / 2, + startViewAnchor = verticalCenterKeyline, + endEdge = verticalCenterKeyline + verticalViewHeight / 2, + endViewAnchor = verticalCenterKeyline, alignment = alignment ) @@ -249,14 +264,12 @@ class ParentAlignmentCalculatorTest { fraction = 0.5f, preferKeylineOverEdge = false ) - alignmentCalculator.updateStartLimit( - edge = -verticalViewHeight / 2, - viewAnchor = verticalViewHeight / 2, - alignment = alignment - ) - alignmentCalculator.updateEndLimit( - edge = verticalCenterKeyline + verticalViewHeight, - viewAnchor = verticalCenterKeyline + verticalViewHeight / 2, + + alignmentCalculator.updateScrollLimits( + startEdge = -verticalViewHeight / 2, + endEdge = verticalCenterKeyline + verticalViewHeight, + startViewAnchor = verticalViewHeight / 2, + endViewAnchor = verticalCenterKeyline + verticalViewHeight / 2, alignment = alignment ) @@ -277,25 +290,19 @@ class ParentAlignmentCalculatorTest { fraction = 0.5f, preferKeylineOverEdge = true ) - alignmentCalculator.updateStartLimit( - edge = verticalCenterKeyline - verticalViewHeight / 2, - viewAnchor = verticalCenterKeyline, + alignmentCalculator.updateScrollLimits( + startEdge = verticalCenterKeyline - verticalViewHeight / 2, + endEdge = verticalCenterKeyline + verticalViewHeight / 2, + startViewAnchor = verticalCenterKeyline, + endViewAnchor = verticalCenterKeyline, alignment = alignment ) - alignmentCalculator.updateEndLimit( - edge = verticalCenterKeyline + verticalViewHeight / 2, - viewAnchor = verticalCenterKeyline, - alignment = alignment - ) - - val distanceToKeyline = 0 - assertThat( alignmentCalculator.calculateScrollOffset( viewAnchor = height / 2, alignment = alignment ) - ).isEqualTo(distanceToKeyline) + ).isEqualTo(0) } @Test @@ -307,14 +314,11 @@ class ParentAlignmentCalculatorTest { fraction = 0.5f, preferKeylineOverEdge = true ) - alignmentCalculator.updateStartLimit( - edge = 0, - viewAnchor = verticalCenterKeyline, - alignment = alignment - ) - alignmentCalculator.updateEndLimit( - edge = verticalCenterKeyline + verticalViewHeight / 2, - viewAnchor = verticalCenterKeyline, + alignmentCalculator.updateScrollLimits( + startEdge = 0, + startViewAnchor = verticalCenterKeyline, + endEdge = verticalCenterKeyline + verticalViewHeight / 2, + endViewAnchor = verticalCenterKeyline, alignment = alignment ) @@ -338,16 +342,11 @@ class ParentAlignmentCalculatorTest { fraction = 0f, preferKeylineOverEdge = true ) - - alignmentCalculator.updateStartLimit( - edge = 0, - viewAnchor = 0, - alignment = alignment - ) - - alignmentCalculator.updateEndLimit( - edge = horizontalViewWidth * 3, - viewAnchor = 0, + alignmentCalculator.updateScrollLimits( + startEdge = 0, + endEdge = horizontalViewWidth * 3, + startViewAnchor = 0, + endViewAnchor = 0, alignment = alignment ) @@ -364,22 +363,17 @@ class ParentAlignmentCalculatorTest { fraction = 0.5f, ) - val viewAnchor = verticalCenterKeyline + verticalViewHeight / 2 - alignmentCalculator.updateStartLimit( - edge = 0, - viewAnchor = viewAnchor, - alignment = alignment - ) + val keyline = verticalCenterKeyline - alignmentCalculator.updateEndLimit( - edge = height - verticalViewHeight, - viewAnchor = height - verticalViewHeight / 2, + alignmentCalculator.updateScrollLimits( + startEdge = keyline - verticalViewHeight, + startViewAnchor = keyline - verticalViewHeight / 2, + endEdge = keyline, + endViewAnchor = keyline - verticalViewHeight / 2, alignment = alignment ) - assertThat(alignmentCalculator.startScrollLimit).isEqualTo( - viewAnchor - verticalCenterKeyline, - ) + assertThat(alignmentCalculator.startScrollLimit).isEqualTo(-verticalViewHeight / 2) } private fun setLayoutProperties(orientation: Int, reverseLayout: Boolean) {