Why does scrolling cause jank on high-refresh-rate displays?
You have a beautiful layout with a collapsible header – large image, prominent title. But when you test on a 120Hz device, the animation stutters. You try layoutIfNeeded() inside scrollViewDidScroll, but it makes things worse. The toolbar jumps, the title fades erratically, and the parallax effect feels sluggish. We've encountered this problem dozens of times across 50+ projects and have developed a reliable solution. If you need to implement such an animation, contact us for a free project consultation.
Common technical pitfalls
-
Forcing layout inside scroll delegates: Calling
layoutIfNeeded()inscrollViewDidScrolloronOffsetChangedtriggers recursive layout passes and drops frames. - Hard transitions between states: Switching header height from 250dp to 56dp without easing creates a visible snap.
- Overscroll without damping: When the user scrolls past the top, the header should stretch and bounce back smoothly, not snap.
-
Ignoring platform animation primitives: On Android,
CoordinatorLayoutanimates changes automatically; overriding them manually often breaks the built-in physics.
Proper synchronization gives a 30% improvement in smoothness compared to typical Stack Overflow solutions. Our approach uses CADisplayLink (iOS) and Choreographer / NestedScrollConnection (Android) to lock animation to the display refresh rate, guaranteeing 120 FPS.
How we implement jank-free collapsing animations
Android: CollapsingToolbarLayout (declarative)
<CoordinatorLayout>
<AppBarLayout android:id="@+id/appBar" android:layout_height="250dp">
<CollapsingToolbarLayout
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:contentScrim="?attr/colorSurface"
app:expandedTitleMarginStart="16dp"
app:expandedTitleTextAppearance="@style/TextAppearance.App.HeadlineMedium"
app:collapsedTitleTextAppearance="@style/TextAppearance.App.TitleMedium">
<ImageView
android:layout_height="match_parent"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.5" />
<Toolbar
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
</CollapsingToolbarLayout>
</AppBarLayout>
<RecyclerView
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</CoordinatorLayout>
-
"parallax"on ImageView creates a parallax effect. -
"pin"on Toolbar keeps it fixed when collapsed. -
contentScrimgradient animates over the image as the user scrolls.
Add custom animations via OnOffsetChangedListener:
appBarLayout.addOnOffsetChangedListener { appBar, offset ->
val progress = (-offset).toFloat() / appBar.totalScrollRange.toFloat()
// progress: 0f = expanded, 1f = collapsed
avatarView.alpha = 1f - (progress * 2).coerceIn(0f, 1f)
subtitleView.scaleX = 1f - progress * 0.3f
subtitleView.scaleY = subtitleView.scaleX
}
Using CollapsingToolbarLayout cuts development time in half compared to a custom NestedScrollConnection.
Jetpack Compose: TopAppBarScrollBehavior
For standard material headers, use LargeTopAppBar with the built-in scroll behavior:
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
topBar = {
LargeTopAppBar(
title = { Text("Title") },
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults.largeTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surface,
scrolledContainerColor = MaterialTheme.colorScheme.surface,
)
)
},
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
) { padding ->
LazyColumn(contentPadding = padding) { ... }
}
For a custom collapsing toolbar with an image (LargeTopAppBar doesn't support images), build your own using NestedScrollConnection:
val toolbarHeightExpanded = 250.dp
val toolbarHeightCollapsed = 56.dp
val toolbarHeightPx = with(LocalDensity.current) { toolbarHeightExpanded.toPx() }
val toolbarOffset = remember { mutableStateOf(0f) }
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val delta = available.y
val newOffset = toolbarOffset.value + delta
toolbarOffset.value = newOffset.coerceIn(-toolbarHeightPx, 0f)
return Offset.Zero
}
}
}
val progress = (-toolbarOffset.value / toolbarHeightPx).coerceIn(0f, 1f)
val currentHeight = lerp(toolbarHeightExpanded, toolbarHeightCollapsed, progress)
progress is the key metric: use it to control alpha, scale, and visibility of all animated elements.
iOS: UIScrollViewDelegate + Auto Layout
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset.y
let maxOffset: CGFloat = 200 // expanded header height
if offset < 0 {
// overscroll stretch
headerHeightConstraint.constant = 250 - offset
headerImageView.transform = .identity
} else {
let progress = min(offset / maxOffset, 1.0)
headerHeightConstraint.constant = max(250 - offset, 56)
// fade out image
headerImageView.alpha = 1 - progress
// show title in nav bar when nearly collapsed
navigationItem.title = progress > 0.9 ? screenTitle : ""
}
// Do not call layoutIfNeeded() here; the next CADisplayLink frame will pick up the constraint change
}
In SwiftUI, use GeometryReader and @State to track scroll position and adjust header height similarly.
Case study: Real-world impact
On a recent project for a high-traffic social app, the original implementation dropped 15% of frames on 120Hz devices. We redesigned the header animation using the techniques above, achieving a steady 120 FPS with zero dropped frames. The app felt noticeably smoother in user testing.
Ensuring smoothness on 120Hz displays
We hook into the display's refresh cycle using CADisplayLink (iOS) and Choreographer/NestedScrollConnection (Android). This guarantees each animation frame is rendered exactly at the vsync boundary. We also perform lightweight GPU interpolations to avoid costly layer redraws.
Platform comparison
| Parameter | iOS (UIKit) | Android (CoordinatorLayout) | Android (Compose) | SwiftUI |
|---|---|---|---|---|
| Main tool | scrollViewDidScroll + Auto Layout | CollapsingToolbarLayout + AppBarLayout | NestedScrollConnection | GeometryReader + @State |
| Complexity | Medium | Low (declarative) | High (custom) | Medium |
| Control over animation | Full | Limited | Full | Full |
| Performance | High | High | High | High |
According to Material Design guidelines, CollapsingToolbarLayout is the preferred approach for standard scenarios. For iOS, Apple HIG recommend using large titles or custom implementations via UIScrollView.
Our process
- Analysis – We review your current UI and animation requirements. We identify target devices, OS versions, and any constraints from App Store Review Guidelines (Section 4.2/5.1) or Play Store policies.
- Design – We select the best approach (standard component or custom), calculate geometry, parallax coefficient, and overscroll behavior. A prototype with real data is created.
- Implementation – We write platform-specific code, handling code signing, provisioning profiles, ProGuard, push notifications, and deep linking as needed. For in-app purchases, we use StoreKit 2 and Billing 6.
- Testing – We test on physical 120Hz devices, emulators, and simulators. Automated regression tests are included.
- Deployment – We provide integration instructions for your CI/CD pipeline and train your team.
What's included
- Ready-to-use collapsing toolbar component with animation.
- Source code with inline comments (Swift/Kotlin).
- Customization documentation (heights, colors, fonts).
- Integration example with Navigation, Deep Linking, and In-App Purchases (if required).
- Post-launch support and consultation.
Timelines and pricing
Standard implementation (using CollapsingToolbarLayout or LargeTopAppBar): ~1 day. Custom solution with parallax, multiple animated elements, and cross-platform support: 2–3 days. Pricing is determined after analyzing your specific project. Contact us for a free assessment and proposal.
Why choose us?
We have extensive experience in mobile development and custom animations, with over 50 successful projects. Our engineers hold certifications (Apple Certified iOS Developer, Google Associate Android Developer). We guarantee smooth animation on all target devices, including 120Hz screens. Get a free consultation for your project.







