Post

Shared Element Transitions in Android: A Simple Example

Shared Element Transitions in Android: A Simple Example

Shared element transitions are a powerful way to create smooth, visually appealing navigation between screens in Android apps. They allow you to animate a UI element (like an image or a card) seamlessly from one Component to another, making your app feel more dynamic and modern.

What is a Shared Element Transition?

A shared element transition animates a view (such as an image or text) from one state to another, maintaining continuity and context for the user. This is commonly used in gallery apps, profile screens, or anywhere you want to highlight a particular element during navigation.

Jetpack Compose Example

Jetpack Compose makes shared element transitions easy with the SharedTransitionLayout, Modifier.sharedElement(), and Modifier.sharedBounds() APIs.

Key Components:

  • SharedTransitionLayout: The outermost layout required to implement shared element transitions. Items that want to share the animation need to be inside this scope.
  • Modifier.sharedElement(): Marks the element to be animated between screens.
  • Modifier.sharedBounds(): Defines the bounds of the component to be shared. This can be visually different in both states.

Simple Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun SharedElementTransitionExample() {
    var showDetails by remember { mutableStateOf(false) }

    SharedTransitionLayout {
        AnimatedContent(
            showDetails,
            label = "basic_transition"
        ) { targetState ->
            if (!targetState) {
                MainContent(
                    onShowDetails = {
                        showDetails = true
                    },
                    animatedVisibilityScope = this@AnimatedContent,
                    sharedTransitionScope = this@SharedTransitionLayout
                )
            } else {
                DetailsContent(
                    onBack = {
                        showDetails = false
                    },
                    animatedVisibilityScope = this@AnimatedContent,
                    sharedTransitionScope = this@SharedTransitionLayout
                )
            }
        }
    }
}

@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun MainContent(
    onShowDetails: () -> Unit,
    animatedVisibilityScope: AnimatedVisibilityScope,
    sharedTransitionScope: SharedTransitionScope
) {
    with(sharedTransitionScope) {
        Box(
            modifier = Modifier
                .sharedBounds(
                    rememberSharedContentState(key = "bounds"),
                    animatedVisibilityScope = animatedVisibilityScope
                )
                .background(Color(0xFF6200EE))
                .size(150.dp)
                .clickable { onShowDetails() },
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = "Tap Me!",
                modifier = Modifier
                    .sharedElement(
                        rememberSharedContentState(key = "text"),
                        animatedVisibilityScope = animatedVisibilityScope
                    ),
                color = Color.White,
                fontSize = 18.sp
            )
        }
    }
}

@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun DetailsContent(
    onBack: () -> Unit,
    animatedVisibilityScope: AnimatedVisibilityScope,
    sharedTransitionScope: SharedTransitionScope
) {
    with(sharedTransitionScope) {
        Box(
            modifier = Modifier
                .sharedBounds(
                    rememberSharedContentState(key = "bounds"),
                    animatedVisibilityScope = animatedVisibilityScope
                )
                .fillMaxWidth()
                .height(300.dp)
                .clip(CircleShape)

                .background(Color(0xFF03DAC5))

                .clickable { onBack() },
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = "Details View\nTap to go back",
                modifier = Modifier
                    .sharedElement(
                        rememberSharedContentState(key = "text"),
                        animatedVisibilityScope = animatedVisibilityScope
                    ),
                color = Color.Black,
                fontSize = 24.sp
            )
        }
    }
}

This code shows a simple row with a shared image. When you navigate between screens, the image animates smoothly using shared element transitions.

Video Preview

Here’s a quick demo of shared element transitions in action:

Shared Element Transition Demo

Click the image above to watch the video on YouTube.


Links:


This post is licensed under CC BY 4.0 by the author.