reduce theme-transition and album scroll recomposition

This commit is contained in:
austinried
2026-10-04 00:29:53 +00:00
parent eb6383115e
commit f32135d8d2
3 changed files with 64 additions and 17 deletions
@@ -48,11 +48,12 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.lerp import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.onSizeChanged
@@ -122,14 +123,15 @@ fun AlbumDetailScreen(
playingSongId: String? = null, playingSongId: String? = null,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
ArtworkTheme(artwork) { CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) { ArtworkTheme(artwork) {
BoxWithConstraints(modifier.fillMaxSize().background(Color.Black)) { BoxWithConstraints(modifier.fillMaxSize().background(Color.Black)) {
val listState = rememberLazyListState() val listState = rememberLazyListState()
val fill = rememberViewportFill(listState) val fill = rememberViewportFill(listState)
val density = LocalDensity.current val density = LocalDensity.current
val gradientHeight = maxHeight * GRADIENT_SCREENS val gradientHeight = maxHeight * GRADIENT_SCREENS
val gradientHeightPx = with(density) { gradientHeight.toPx() } val gradientHeightPx = with(density) { gradientHeight.toPx() }
val screenHeightPx = with(density) { maxHeight.toPx() }
val statusBarTop = WindowInsets.statusBars.getTop(density) val statusBarTop = WindowInsets.statusBars.getTop(density)
val navBarBottom = WindowInsets.navigationBars.getBottom(density) val navBarBottom = WindowInsets.navigationBars.getBottom(density)
val statusBarDp = with(density) { statusBarTop.toDp() } val statusBarDp = with(density) { statusBarTop.toDp() }
@@ -155,16 +157,20 @@ fun AlbumDetailScreen(
} }
} }
} }
val barColor = val barColor by
if (artwork == null || gradientHeightPx <= 0f) { remember(artwork, barHeightPx, gradientHeightPx, screenHeightPx) {
Color.Black derivedStateOf {
} else { if (artwork == null || gradientHeightPx <= 0f) {
val mid = scrollPx + barHeightPx / 2f Color.Black
lerp( } else {
artwork.gradientColorAt(mid / gradientHeightPx), val mid = scrollPx + barHeightPx / 2f
artwork.darkPrimary, lerp(
heroDarkenAt(mid, with(density) { maxHeight.toPx() }), artwork.gradientColorAt(mid / gradientHeightPx),
) artwork.darkPrimary,
heroDarkenAt(mid, screenHeightPx),
)
}
}
} }
HeroGradient( HeroGradient(
@@ -220,7 +226,7 @@ fun AlbumDetailScreen(
Brush.verticalGradient( Brush.verticalGradient(
listOf(Color.Black.copy(alpha = 0.55f), Color.Transparent), listOf(Color.Black.copy(alpha = 0.55f), Color.Transparent),
), ),
).alpha(1f - barFraction), ).graphicsLayer { alpha = 1f - barFraction },
) )
TopAppBar( TopAppBar(
@@ -230,7 +236,7 @@ fun AlbumDetailScreen(
style = MaterialTheme.typography.headlineMedium, style = MaterialTheme.typography.headlineMedium,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.alpha(barFraction), modifier = Modifier.graphicsLayer { alpha = barFraction },
) )
}, },
navigationIcon = { navigationIcon = {
@@ -242,7 +248,7 @@ fun AlbumDetailScreen(
modifier = modifier =
Modifier Modifier
.align(Alignment.TopStart) .align(Alignment.TopStart)
.background(barColor.copy(alpha = barFraction)) .drawBehind { drawRect(barColor.copy(alpha = barFraction)) }
.clickable( .clickable(
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
indication = null, indication = null,
@@ -10,9 +10,12 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.lerp import androidx.compose.ui.graphics.lerp
import kotlin.math.roundToInt
const val ARTWORK_THEME_TRANSITION_MS = 600 const val ARTWORK_THEME_TRANSITION_MS = 600
private const val TRANSITION_STEPS = 24
private val baseArtworkColors = private val baseArtworkColors =
ArtworkColors( ArtworkColors(
scheme = SubtracksColorScheme, scheme = SubtracksColorScheme,
@@ -42,7 +45,8 @@ fun rememberAnimatedArtworkColors(
} }
} }
val fraction = progress.value if (from === to) return to
val fraction = (progress.value * TRANSITION_STEPS).roundToInt() / TRANSITION_STEPS.toFloat()
return remember(from, to, fraction) { lerpArtworkColors(from, to, fraction) } return remember(from, to, fraction) { lerpArtworkColors(from, to, fraction) }
} }
@@ -79,4 +83,39 @@ private fun lerpScheme(
onTertiary = lerp(a.onTertiary, b.onTertiary, t), onTertiary = lerp(a.onTertiary, b.onTertiary, t),
tertiaryContainer = lerp(a.tertiaryContainer, b.tertiaryContainer, t), tertiaryContainer = lerp(a.tertiaryContainer, b.tertiaryContainer, t),
onTertiaryContainer = lerp(a.onTertiaryContainer, b.onTertiaryContainer, t), onTertiaryContainer = lerp(a.onTertiaryContainer, b.onTertiaryContainer, t),
background = lerp(a.background, b.background, t),
onBackground = lerp(a.onBackground, b.onBackground, t),
surface = lerp(a.surface, b.surface, t),
onSurface = lerp(a.onSurface, b.onSurface, t),
surfaceVariant = lerp(a.surfaceVariant, b.surfaceVariant, t),
onSurfaceVariant = lerp(a.onSurfaceVariant, b.onSurfaceVariant, t),
surfaceTint = lerp(a.surfaceTint, b.surfaceTint, t),
inverseSurface = lerp(a.inverseSurface, b.inverseSurface, t),
inverseOnSurface = lerp(a.inverseOnSurface, b.inverseOnSurface, t),
error = lerp(a.error, b.error, t),
onError = lerp(a.onError, b.onError, t),
errorContainer = lerp(a.errorContainer, b.errorContainer, t),
onErrorContainer = lerp(a.onErrorContainer, b.onErrorContainer, t),
outline = lerp(a.outline, b.outline, t),
outlineVariant = lerp(a.outlineVariant, b.outlineVariant, t),
scrim = lerp(a.scrim, b.scrim, t),
surfaceBright = lerp(a.surfaceBright, b.surfaceBright, t),
surfaceDim = lerp(a.surfaceDim, b.surfaceDim, t),
surfaceContainer = lerp(a.surfaceContainer, b.surfaceContainer, t),
surfaceContainerHigh = lerp(a.surfaceContainerHigh, b.surfaceContainerHigh, t),
surfaceContainerHighest = lerp(a.surfaceContainerHighest, b.surfaceContainerHighest, t),
surfaceContainerLow = lerp(a.surfaceContainerLow, b.surfaceContainerLow, t),
surfaceContainerLowest = lerp(a.surfaceContainerLowest, b.surfaceContainerLowest, t),
primaryFixed = lerp(a.primaryFixed, b.primaryFixed, t),
primaryFixedDim = lerp(a.primaryFixedDim, b.primaryFixedDim, t),
onPrimaryFixed = lerp(a.onPrimaryFixed, b.onPrimaryFixed, t),
onPrimaryFixedVariant = lerp(a.onPrimaryFixedVariant, b.onPrimaryFixedVariant, t),
secondaryFixed = lerp(a.secondaryFixed, b.secondaryFixed, t),
secondaryFixedDim = lerp(a.secondaryFixedDim, b.secondaryFixedDim, t),
onSecondaryFixed = lerp(a.onSecondaryFixed, b.onSecondaryFixed, t),
onSecondaryFixedVariant = lerp(a.onSecondaryFixedVariant, b.onSecondaryFixedVariant, t),
tertiaryFixed = lerp(a.tertiaryFixed, b.tertiaryFixed, t),
tertiaryFixedDim = lerp(a.tertiaryFixedDim, b.tertiaryFixedDim, t),
onTertiaryFixed = lerp(a.onTertiaryFixed, b.onTertiaryFixed, t),
onTertiaryFixedVariant = lerp(a.onTertiaryFixedVariant, b.onTertiaryFixedVariant, t),
) )
@@ -5,6 +5,7 @@ import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
@@ -31,6 +32,7 @@ private const val DARKEN_START_SCREENS = 0.5f
private const val DARKEN_END_SCREENS = 1.5f private const val DARKEN_END_SCREENS = 1.5f
private const val DARKEN_KNEE = 0.2f private const val DARKEN_KNEE = 0.2f
@Immutable
data class ArtworkColors( data class ArtworkColors(
val scheme: ColorScheme, val scheme: ColorScheme,
val gradientHigh: Color, val gradientHigh: Color,