Mobile Development 19 min read

Mastering Shared Element Transitions in Jetpack Compose: From Basics to Advanced Tricks

Explore the complete guide to implementing shared element transitions in Jetpack Compose, covering essential APIs like SharedTransitionLayout, Modifier.sharedElement, and Modifier.sharedBounds, with detailed code examples, common pitfalls, container transforms, resize modes, and practical solutions for smooth, hero‑style animations across Android screens.

AndroidPub
AndroidPub
AndroidPub
Mastering Shared Element Transitions in Jetpack Compose: From Basics to Advanced Tricks

Clear Introduction to Compose Shared Element Transitions

Shared element transitions smoothly connect identical content across different screens, improving navigation continuity. In Compose they rely on three main APIs:

SharedTransitionLayout

,

Modifier.sharedElement()

, and

Modifier.sharedBounds()

.

Because the related APIs are still in beta, remember to add the dependency implementation("androidx.compose.animation:animation:1.7.0-beta06") .

SharedTransitionLayout and sharedElement Modifier

Both pages must contain the same content; otherwise there is nothing to transition.

@Composable
fun BasicSharedElementDemo() {
    var showDetails by remember { mutableStateOf(false) }
    SharedTransitionLayout {
        if (showDetails) {
            DetailsScreen(onBack = { showDetails = false })
        } else {
            MainScreen(onShowDetails = { showDetails = true })
        }
    }
}

To mark shared elements, use

Modifier.sharedElement()

with a

SharedContentState

created by

rememberSharedContentState(key = "unique_key")

. The modifier also requires an

AnimatedVisibilityScope

which is usually supplied by

AnimatedVisibility

or

AnimatedContent

.

SharedBounds Modifier

Modifier.sharedBounds()

works like

sharedElement()

but is intended for container‑level transitions where the visual content differs while the shared area remains the same.

@Composable
private fun SharedTransitionScope.MainScreen(onShowDetails: () -> Unit, animatedVisibilityScope: AnimatedVisibilityScope, modifier: Modifier = Modifier) {
    Box {
        Row(
            modifier = Modifier
                .padding(16.dp)
                .sharedBounds(
                    sharedContentState = rememberSharedContentState(key = "bounds"),
                    animatedVisibilityScope = animatedVisibilityScope
                )
        ) {
            Image(
                painter = painterResource(id = R.drawable.avatar),
                contentDescription = null,
                modifier = Modifier
                    .size(100.dp)
                    .sharedElement(
                        state = rememberSharedContentState(key = "image"),
                        animatedVisibilityScope = animatedVisibilityScope
                    )
            )
            Text(
                text = "bqliang",
                fontSize = 21.sp,
                modifier = Modifier.sharedElement(
                    state = rememberSharedContentState(key = "title"),
                    animatedVisibilityScope = animatedVisibilityScope
                )
            )
        }
    }
}

Using

sharedBounds()

solves two common problems: container background transitions and abrupt text size changes. The modifier provides

enter

and

exit

transitions (default fade‑in/out) and a

resizeMode

that controls how content scales. The default

ScaleToBounds(ContentScale.FillWidth, Center)

can produce odd animations; switching to

RemeasureToBounds

forces the content to be re‑measured to fill the evolving bounds, yielding smoother results.

@Composable
fun BasicSharedElementDemo() {
    var showDetails by remember { mutableStateOf(false) }
    SharedTransitionLayout {
        AnimatedContent(targetState = showDetails) { inDetails ->
            if (inDetails) {
                DetailsScreen(onBack = { showDetails = false }, animatedVisibilityScope = this@AnimatedContent)
            } else {
                MainScreen(onShowDetails = { showDetails = true }, animatedVisibilityScope = this@AnimatedContent)
            }
        }
    }
}

When the default

resizeMode

stretches content only horizontally, the start and end elements may not align. Using

RemeasureToBounds

or

ScaleToBounds(ContentScale.FillBounds, Center)

changes the scaling behavior. For text,

sharedBounds()

keeps the original font size while fading, avoiding sudden size jumps.

Summary

Shared element and shared bounds modifiers must be used inside a

SharedTransitionScope

and typically together with

AnimatedVisibility

or

AnimatedContent

.

sharedElement()

is for identical content;

sharedBounds()

is for container‑level transitions.

During transition,

sharedElement()

renders only the target content, while

sharedBounds()

renders both start and target, applying default fade‑in/out.

sharedBounds()

can be customized with

enter

,

exit

, and

resizeMode

to achieve the desired animation.

animationAndroid UIJetpack Composeshared element transition
AndroidPub
Written by

AndroidPub

Senior Android Developer & Interviewer, regularly sharing original tech articles, learning resources, and practical interview guides. Welcome to follow and contribute!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.