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.lastIndex and currentPage > 0 prevent over-scrolling past the first or last page.
  • For production: Consider using VerticalPager from accompanist or the Compose Foundation pager for built-in fling physics and snap behavior.

Minimum SDK: API 24+ with Compose BOM 2024.01+

Get New Tutorials by Email

No spam. Just clear, practical breakdowns you can apply right away.

Enjoy this tutorial?

Get new practical tech tutorials in your inbox.