Riri
07/08/2026, 2:07 AMChrimaeon
07/08/2026, 2:08 PMThomas Ytterdal
07/10/2026, 7:48 PMBringIntoViewSpec? The default can sometimes make it look janky (or at least it did in earlier versions of compose)
package com.example.scrolltest.ui.theme
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.spring
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.BringIntoViewSpec
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
// Stiffness decides how smooth the scroll is (lower value = smoother and slower scroll)
private const val SCROLL_STIFFNESS = 100f
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun rememberBringIntoViewSpec(
parentFraction: Float,
childFraction: Float
): BringIntoViewSpec {
// It's important that we reuse the same BringIntoViewSpec instance if these value changes, otherwise Compose seems to get confused and calls
// both the old and the new BringIntoSpec in a loop. So pass updated values through rememberUpdatedState() instead
val updatedParentFraction by rememberUpdatedState(parentFraction)
val updatedChildFraction by rememberUpdatedState(childFraction)
return remember {
object : BringIntoViewSpec {
// Taken from <https://issuetracker.google.com/issues/348896032>
override fun calculateScrollDistance(
// initial position of item requesting focus
offset: Float,
// size of item requesting focus
size: Float,
// size of the lazy container
containerSize: Float
): Float {
val childSmallerThanParent = size <= containerSize
val initialTargetForLeadingEdge = updatedParentFraction * containerSize - (updatedChildFraction * size)
val spaceAvailableToShowItem = containerSize - initialTargetForLeadingEdge
val targetForLeadingEdge = if (childSmallerThanParent && spaceAvailableToShowItem < size) {
containerSize - size
} else {
initialTargetForLeadingEdge
}
return offset - targetForLeadingEdge
}
override val scrollAnimationSpec: AnimationSpec<Float>
get() = spring(stiffness = SCROLL_STIFFNESS)
}
}
}
Then apply it to your theme:
CompositionLocalProvider(
LocalBringIntoViewSpec provides rememberBringIntoViewSpec(
parentFraction = 0.5f,
childFraction = 0.5f
)
) {
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}