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

View file

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