Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix alignment regression for some configurations #174

Merged
merged 6 commits into from
Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -416,4 +416,32 @@ class HorizontalAlignmentTest : DpadRecyclerViewTest() {
}
assertThat(getItemViewBounds(position = 0)).isEqualTo(childBounds)
}

@Test
fun testFirstItemIsAlignedCorrectlyWhenScrollingBack() {
launchFragment(
layoutConfiguration = getDefaultLayoutConfiguration().copy(
parentAlignment = ParentAlignment(
edge = Edge.MAX,
offset = 200,
fraction = 0f,
preferKeylineOverEdge = false
),
childAlignment = ChildAlignment(
offset = 0,
fraction = 0f
)
),
adapterConfiguration = getDefaultAdapterConfiguration()
)

val childBounds = getItemViewBounds(position = 0)
assertThat(childBounds.left).isEqualTo(200)
KeyEvents.pressRight()
waitForIdleScrollState()
KeyEvents.pressLeft()
waitForIdleScrollState()
waitForIdleScrollState()
assertThat(getItemViewBounds(position = 0)).isEqualTo(childBounds)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -251,14 +251,15 @@ class VerticalAlignmentTest : DpadRecyclerViewTest() {
fraction = 0f
)
)
KeyEvents.pressDown(times = 5)
val recyclerViewBounds = getRecyclerViewBounds()
val startPosition = 5
selectPosition(startPosition)
repeat(5) {
val viewBounds = getItemViewBounds(position = startPosition + it)
assertThat(viewBounds.top)
.isEqualTo(recyclerViewBounds.top + containerOffset + abs(itemOffset))
KeyEvents.pressDown()
waitForIdleScrollState()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -287,7 +288,7 @@ internal class LayoutAlignment(
private fun isStartAvailable(
adapterPosition: Int,
maxLayoutPosition: Int,
minLayoutPosition: Int
minLayoutPosition: Int,
): Boolean {
return if (!reverseLayout) {
adapterPosition == minLayoutPosition
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,58 +82,56 @@ 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
|| alignment.edge == Edge.NONE
) {
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
|| alignment.edge == Edge.NONE
) {
calculateScrollOffsetToKeyline(viewAnchor, keyline)
} else {
0
}
private fun shouldAlignStartToKeyline(alignment: ParentAlignment): Boolean {
return !shouldAlignToStartEdge(alignment.edge) || preferKeylineOverEdge(alignment)
}

private fun calculateScrollOffsetToEndEdge(edge: Int): Int {
return edge - getLayoutEndEdge()
private fun shouldAlignEndToKeyline(alignment: ParentAlignment): Boolean {
return !shouldAlignToEndEdge(alignment.edge) || preferKeylineOverEdge(alignment)
}

private fun calculateScrollOffsetToStartEdge(edge: Int): Int {
return edge - getLayoutStartEdge()
private fun calculateScrollOffsetToEndEdge(anchor: Int): Int {
return anchor - getLayoutAbsoluteEnd()
}

private fun calculateScrollOffsetToStartEdge(anchor: Int): Int {
return anchor - getLayoutAbsoluteStart()
}

/**
Expand Down Expand Up @@ -192,10 +190,10 @@ internal class ParentAlignmentCalculator {
if (isStartUnknown || !shouldAlignToStartEdge(alignment.edge)) {
return false
}
if (!isLayoutIncomplete()) {
return viewAnchor + getLayoutStartEdge() <= startEdge + keyline
if (isLayoutComplete()) {
return viewAnchor + getLayoutAbsoluteStart() <= startEdge + keyline
}
return isLayoutIncomplete() && !alignment.preferKeylineOverEdge
return isLayoutStartKnown() && !preferKeylineOverEdge(alignment)
}

private fun shouldAlignViewToEnd(
Expand All @@ -206,41 +204,46 @@ internal class ParentAlignmentCalculator {
if (isEndUnknown || !shouldAlignToEndEdge(alignment.edge)) {
return false
}
if (!isLayoutIncomplete()) {
return viewAnchor + getLayoutEndEdge() >= endEdge + keyline
if (isLayoutComplete()) {
return viewAnchor + getLayoutAbsoluteEnd() >= endEdge + keyline
}
return isLayoutIncomplete() && !alignment.preferKeylineOverEdge
return isLayoutStartKnown() && !preferKeylineOverEdge(alignment)
}

private fun calculateScrollOffsetToKeyline(anchor: Int, keyline: Int): Int {
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 {
if (isEndUnknown || isStartUnknown) {
return false
if (isEndUnknown && isStartUnknown) {
return true
}
return if (!reverseLayout) {
(startEdge <= getLayoutAbsoluteStart()
&& (endEdge >= getLayoutAbsoluteEnd() || isEndUnknown))
} else {
(endEdge >= getLayoutAbsoluteEnd()
&& (startEdge <= getLayoutAbsoluteStart() || isStartUnknown))
}
return endEdge - startEdge >= size - paddingEnd - paddingStart
&& endEdge <= size - paddingEnd
&& startEdge >= paddingStart
}

private fun isLayoutIncomplete(): Boolean {
if (isEndUnknown || isStartUnknown) {
return false
}
private fun preferKeylineOverEdge(alignment: ParentAlignment): Boolean {
return alignment.preferKeylineOverEdge || alignment.edge == Edge.NONE
}

private fun isLayoutStartKnown(): Boolean {
return if (!reverseLayout) {
endEdge < size - paddingEnd
!isStartUnknown
} else {
startEdge > paddingStart
!isEndUnknown
}
}

Expand Down
Loading