Coordinated Shimmer¶
By default, every placeholder runs its own animation clock. In a list of skeleton items the highlights drift out of phase, and the screen reads as a noisy patchwork of independently-shimmering boxes.
PlaceholderSurface shares a single PlaceholderCoordinator with every descendant so the shimmer reads as one coordinated wave across the entire region.
PlaceholderSurface¶
PlaceholderSurface {
LazyColumn {
items(20) {
UserListItem(user = null, isLoading = true) // every row shimmers in sync
}
}
}
While inside a PlaceholderSurface, each highlight's own animationSpec is ignored — the surface's spec drives every placeholder. You can override the shared spec via the animationSpec parameter:
PlaceholderSurface(
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 2400, easing = LinearEasing),
repeatMode = RepeatMode.Restart,
),
) {
// ... content ...
}
How It Works¶
A PlaceholderSurface does three things:
- Creates a
PlaceholderCoordinatorthat owns a singleAnimatable<Float>running the suppliedanimationSpecindefinitely. - Publishes that coordinator via
LocalPlaceholderCoordinator. - Each
Modifier.placeholder(orModifier.placeholderText) inside the scope reads the coordinator and uses its progress instead of starting a per-instance animation.
The result: every placeholder in the scope reads exactly the same progress at any frame, and per-instance highlight animations are suppressed entirely (saving CPU).
Default spec
DefaultPlaceholderCoordinatorSpec is a 1700ms linear restart cycle that loosely matches the visual feel of PlaceholderDefaults.shimmer. Override it whenever your design calls for a different cadence.
Composing with Theming¶
PlaceholderSurface composes cleanly with ProvidePlaceholderTheme — wrap them in either order to get themed, coordinated placeholders for an entire screen:
ProvidePlaceholderTheme(materialPlaceholderTheme()) {
PlaceholderSurface {
// Themed defaults + synchronized shimmer
UserList()
}
}