What You Will Build
A vertical page-snapping interface where swiping up or down transitions to the next or previous page with a slide+fade animation. Each page is a full-screen colored view. This is the TikTok/Instagram Reels scrolling pattern applied to any paged content.
Why Snap Transitions Are a Core Mobile Pattern
Vertical paging has become the dominant content consumption model in mobile apps. Understanding how to detect vertical drag gestures and trigger page transitions with directional animations is essential for short-form video feeds, onboarding flows, and story-style content.
Key Compose Concepts
- detectVerticalDragGestures for detecting swipe direction.
- AnimatedContent with directional transitionSpec for context-aware animations.
- Threshold-based page switching (50px drag threshold) for intentional navigation.
Step 1: Define Pages and State
val pages = listOf(
Color(0xFFFF6B6B) to "Page 1",
Color(0xFF4ECDC4) to "Page 2",
Color(0xFF45B7D1) to "Page 3",
Color(0xFFFFA07A) to "Page 4"
)
var currentPage by remember { mutableIntStateOf(0) }
Step 2: Detect Vertical Swipes
Box(
Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectVerticalDragGestures { _, dragAmount ->
if (dragAmount < -50 && currentPage < pages.lastIndex)
currentPage++
else if (dragAmount > 50 && currentPage > 0)
currentPage--
}
}
)
Step 3: Directional AnimatedContent
The key insight is that the transition direction should match the swipe direction. Swiping up should slide the new page in from the bottom and the old page out to the top:
AnimatedContent(
targetState = currentPage,
transitionSpec = {
if (targetState > initialState) {
// Swiping up: new page slides in from bottom
slideInVertically { it } + fadeIn() togetherWith
slideOutVertically { -it } + fadeOut()
} else {
// Swiping down: new page slides in from top
slideInVertically { -it } + fadeIn() togetherWith
slideOutVertically { it } + fadeOut()
}
},
label = "snap"
) { page ->
Box(
Modifier
.fillMaxSize()
.background(pages[page].first),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
pages[page].second,
color = Color.White,
style = MaterialTheme.typography.displaySmall
)
Spacer(Modifier.height(16.dp))
Text(
"Swipe up/down",
color = Color.White.copy(alpha = 0.6f)
)
}
}
}
Tips and Pitfalls
- Drag threshold of 50 prevents accidental page switches from small touches. Adjust based on your content sensitivity.
- Directional transition is crucial: Without checking
targetState > initialState, pages would always animate in the same direction regardless of swipe direction. - Boundary checking: The conditions
currentPage < pages.lastIndexandcurrentPage > 0prevent over-scrolling past the first or last page. - For production: Consider using
VerticalPagerfrom accompanist or the Compose Foundation pager for built-in fling physics and snap behavior.
Minimum SDK: API 24+ with Compose BOM 2024.01+