Fixes two row bouncing UI issues (#1543)

## Description

Fixes row bounce using `SeasonCard` when are two exactly two items. This
was done by calculating the y-offset in the `layout` modifier instead of
`offset`. This has the side effect of probably being very slightly more
efficient since only 1 animation state is needed now.

Fixes row bounce on the second row of the home page if the first row is
empty and not shown.

### Related issues
Fixes #1528
Fixes #1271

### Testing
Emulator in different situations. Also checked the layout inspector to
make sure the optimizations from #1184 weren't affected

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-06-08 17:23:09 -04:00 • committed by GitHub
parent 93d601e7bf
commit 01bd3249fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 14 additions and 12 deletions

View file

@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
@ -24,7 +23,6 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.tv.material3.Card
import androidx.tv.material3.CardDefaults
@ -104,6 +102,9 @@ fun SeasonCard(
)
}
private val spaceBetweenFocused = 4.dp
private val spaceBetweenUnfocused = 12.dp
/**
* A Card for a TV Show Season, but can generically show most items
*/
@ -129,8 +130,11 @@ fun SeasonCard(
) {
val focused by interactionSource.collectIsFocusedAsState()
// Do not use `by` here, this way we are Defer reads and recompositions to only when modifier calculates
val spaceBetween = animateDpAsState(if (focused) 12.dp else 4.dp, label = "spaceBetween")
val spaceBelow = animateDpAsState(if (focused) 4.dp else 12.dp, label = "spaceBelow")
val spaceBetween =
animateDpAsState(
if (focused) spaceBetweenFocused else spaceBetweenUnfocused,
label = "spaceBetween",
)
val focusedAfterDelay by rememberFocusedAfterDelay(interactionSource)
val aspectRationToUse = aspectRatio.coerceAtLeast(AspectRatios.MIN)
@ -179,14 +183,12 @@ fun SeasonCard(
verticalArrangement = Arrangement.spacedBy(0.dp),
modifier =
Modifier
// Optimization: move animation reads to layout/draw phase
.offset {
IntOffset(0, (spaceBetween.value - 4.dp).roundToPx())
}.layout { measurable, constraints ->
val paddingPx = spaceBelow.value.roundToPx()
.layout { measurable, constraints ->
val topPaddingPx = (spaceBetweenUnfocused - spaceBetween.value).roundToPx()
val bottomPaddingPx = spaceBetween.value.roundToPx()
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height + paddingPx) {
placeable.placeRelative(0, 0)
layout(placeable.width, placeable.height + bottomPaddingPx + topPaddingPx) {
placeable.placeRelative(0, topPaddingPx)
}
}.fillMaxWidth(),
) {

View file

@ -374,7 +374,7 @@ fun HomePageContent(
itemsIndexed(homeRows) { rowIndex, row ->
val rowModifier =
Modifier
.animateItem()
.animateItem(placementSpec = null)
.padding(bottom = 8.dp)
CompositionLocalProvider(
LocalBringIntoViewSpec provides defaultBringIntoViewSpec,