Creating Magical Shared Element Transitions with Jetpack Compose

Creating Magical Shared Element Transitions with Jetpack Compose

Hi Android enthusiasts!

Have you ever admired the beautiful animations when navigating between screens in apps like Instagram, Spotify, or Airbnb? Ever wondered how those smooth, delightful transitions happen? Well, the magic you're seeing is often the Shared Element Transition!

A few days ago, I experimented with Jetpack Compose to build a quick sample app demonstrating Shared Element Transitions without relying on external libraries. I shared a video snippet on LinkedIn, and many of you loved it! Today, let's dive deeper into how you can implement this magical experience yourself, purely with Compose, and understand what's happening under the hood.


The Idea

Imagine an e-commerce app displaying a list of stylish shoes. When your user selects a shoe, the shoe image elegantly enlarges and transitions smoothly into the detail view. Let's break down how this happens step-by-step:


Implementing Shared Element Transitions (Compose-Only!)

🏗 1. SharedTransitionLayout

SharedTransitionLayout acts as a container that manages the shared element transitions between composables. It creates a transition scope, ensuring all shared elements animate seamlessly between screens:

SharedTransitionLayout {
    NavHost(navController = navController, startDestination = "list") {
       composable("list") { ListScreen() }
       composable("detail") { DetailScreen() }
    }
}        

Internal Mechanics:

  • Captures the initial and final bounds (position and size) of UI elements.
  • Coordinates these bounds using Compose’s powerful recomposition mechanism, ensuring smooth transitions.


🎭 2. with(sharedTransitionScope)

Inside SharedTransitionLayout, you gain access to a powerful scope: sharedTransitionScope. This scope allows you to precisely define how transitions between bounds (positions and sizes of elements) will animate:

with(sharedTransitionScope) {
    val boundsTransform = BoundsTransform { initialBounds, targetBounds ->
        keyframes {
            durationMillis = 1000
            initialBounds at 0 using ArcMode.ArcBelow using FastOutSlowInEasing
            targetBounds at 1000
        }
    }
}        

Detailed Explanation of BoundsTransform:

  • BoundsTransform calculates and animates transitions between two sets of bounds (positions and sizes of UI elements).
  • It uses interpolation to generate intermediate points smoothly transitioning from the initial state to the final state.
  • With keyframes, you explicitly define animation states at specific timestamps:
  • The arc calculation involves mathematical functions (trigonometry) to generate curved paths instead of straight lines, enhancing visual appeal.


🔗 3. .sharedElement Modifier

The .sharedElement() modifier is critical—it tells Compose exactly which UI element is shared across screens and should animate between them:

Image(
    painter = painterResource(shoe.imageRes),
    contentDescription = shoe.name,
    modifier = Modifier
        .sharedElement(
            rememberSharedContentState(key = "$KEY_BACKGROUND-$currentPage"),
            animatedVisibilityScope = animatedVisibilityScope,
            boundsTransform = boundsTransform
        )
)        

Internal Mechanics:

  • Links UI components between screens by matching their state keys.
  • Tracks element position, size, and visibility, automatically managing recompositions and state updates.


🧠 4. rememberSharedContentState

This helper function, rememberSharedContentState, helps Compose uniquely track each shared element's transition state:

rememberSharedContentState(key = "$KEY_BACKGROUND-$currentPage")        

Internal Mechanics:

  • Stores and retrieves transition states consistently, ensuring animations remain smooth even through recompositions or layout updates.
  • Provides stable references for efficient transition management.


🎬 5. animatedVisibilityScope

The animatedVisibilityScope manages visibility animations, ensuring smooth appearance and disappearance of elements:

.sharedElement(
    rememberSharedContentState(key = "${KEY_BACKGROUND}-$currentPage"),
    animatedVisibilityScope = animatedVisibilityScope,
    boundsTransform = boundsTransform
)        

Internal Mechanics:

  • Controls opacity and visibility transitions, employing linear interpolation for smooth fading.
  • Ensures visual continuity and enhances user experience by reducing abrupt UI changes.


Why Does This Matter?

Shared Element Transitions significantly enhance user experience by providing visual continuity, making your app feel polished, professional, and delightful. It brings life to your interface, captivating your users and encouraging deeper engagement.


Want More Such Content?

Join my Substack community!


I'm continually sharing practical tutorials, tips, and insights from my Android development journey. Plus, we've launched a referral leaderboard to rewards our most active subscribers. Invite your friends, colleagues, and let's learn and grow together!


Join our WhatsApp Community to get regular updates


Try implementing this yourself, share your results, tag me, and let's spread the magic of Jetpack Compose together!

Happy coding! 🎉

Book 1:1 Mentorship Session

Akshay Nandwana 🇮🇳

Technical Consultant - Google • Helping Devs Land Jobs 🚀 • 114M+ Impression • Android Engineer & Mentor • Ex-GSoC Mentor & GDSC Lead • Ex-Zee5, Doubtnut • Kotlin, Jetpack Compose, CMP

1mo
Like
Reply

To view or add a comment, sign in

More articles by Akshay Nandwana 🇮🇳

  • 🚀 Jetpack Compose Cohort: May 1

    🚀 Jetpack Compose Cohort: May 1

    I'm thrilled to announce our upcoming intensive 7-day workshop designed to give you a hands-on, project-based journey…

  • Infix Notation in Kotlin

    Infix Notation in Kotlin

    Kotlin provides many features that enhance the readability and expressiveness of code. One such feature is infix…

  • Mastering Coding Patterns in Kotlin

    Mastering Coding Patterns in Kotlin

    As an Android Kotlin developer, understanding coding patterns is essential for writing clean, efficient, and scalable…

    2 Comments
  • Analytics Logging System in NowInAndroid App

    Analytics Logging System in NowInAndroid App

    This blog will walk you through the step-by-step process of building a scalable and testable analytics logging…

    1 Comment
  • lateinit vs lazy in Kotlin

    lateinit vs lazy in Kotlin

    Kotlin offers two ways to initialize properties lazily: and . While both are used to delay initialization, they serve…

    3 Comments
  • GSoC Orgs for Android Enthusiasts 🚀

    GSoC Orgs for Android Enthusiasts 🚀

    🚀 Looking to contribute to Android-related projects and be part of Google Summer of Code (GSoC)? Here’s a curated list…

    1 Comment
  • Difference Between const val and val in Kotlin

    Difference Between const val and val in Kotlin

    Kotlin provides two keywords, and , for defining constants. While both are used to declare immutable values, there are…

    1 Comment
  • Design Patterns in Android Development

    Design Patterns in Android Development

    Design patterns are proven solutions to common problems in software design. They help structure code, improve…

  • Mastering LRU Cache in Kotlin

    Mastering LRU Cache in Kotlin

    As a Senior Engineer, I’ve found that mastering algorithms isn’t just about memorizing their definitions but truly…

  • Android Engineer Interview Questions

    Android Engineer Interview Questions

    Kotlin Difference Between const val and val? Answer What is Data Class in Kotlin? What are the different Coroutine…

    3 Comments

Insights from the community

Others also viewed

Explore topics