SharedTransitionLayout
@ExperimentalSharedTransitionApi
@Composable
public fun SharedTransitionLayout(
modifier: Modifier = Modifier,
content: @Composable SharedTransitionScope.() -> Unit,
)
SharedTransitionLayout
creates a layout and a SharedTransitionScope
for the child layouts in
content
. Any child (direct or indirect) of the SharedTransitionLayout
can use the receiver
scope SharedTransitionScope
to create shared element or shared bounds transitions.
Note: SharedTransitionLayout
creates a new Layout. For use cases where it's preferable to
not introduce a new layout between content
and the parent layout, consider using
SharedTransitionScope
instead.
Below is an example of using SharedTransitionLayout
to create a shared element transition and a
shared bounds transition at the same time. Please see the API docs for
SharedTransitionScope.sharedElement
and SharedTransitionScope.sharedBounds
for more
simplified examples of using these APIs separately.
Parameters
modifier | Modifiers to be applied to the layout. |
content | The children composable to be laid out. |
Code Examples
SharedElementInAnimatedContentSample
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun SharedElementInAnimatedContentSample() {
// This is the Image that we will add shared element modifier on. It's important to make sure
// modifiers that are not shared between the two shared elements (such as size modifiers if
// the size changes) are the parents (i.e. on the left side) of Modifier.sharedElement.
// Meanwhile, the modifiers that are shared between the shared elements (e.g. Modifier.clip
// in this case) are on the right side of the Modifier.sharedElement.
@Composable
fun Cat(modifier: Modifier = Modifier) {
Image(
painterResource(id = R.drawable.yt_profile),
contentDescription = "cute cat",
contentScale = ContentScale.FillHeight,
modifier = modifier.clip(shape = RoundedCornerShape(10)),
)
}
// Shared element key is of type `Any`, which means it can be id, string, etc. The only
// requirement for the key is that it should be the same for shared elements that you intend
// to match. Here we use the image resource id as the key.
val sharedElementKey = R.drawable.yt_profile
var showLargeImage by remember { mutableStateOf(true) }
// First, we need to create a SharedTransitionLayout, this Layout will provide the coordinator
// space for shared element position animation, as well as an overlay for shared elements to
// render in. Children content in this Layout will be able to create shared element transition
// using the receiver scope: SharedTransitionScope
SharedTransitionLayout(
Modifier.clickable { showLargeImage = !showLargeImage }.fillMaxSize().padding(10.dp)
) {
// In the SharedTransitionLayout, we will be able to access the receiver scope (i.e.
// SharedTransitionScope) in order to create shared element transition.
AnimatedContent(targetState = showLargeImage) { showLargeImageMode ->
if (showLargeImageMode) {
Cat(
Modifier.fillMaxSize()
.aspectRatio(1f)
// Creating a shared element. Note that this modifier is *after*
// the size modifier and aspectRatio modifier, because those size specs
// are not shared between the two shared elements.
.sharedElement(
rememberSharedContentState(sharedElementKey),
// Using the AnimatedVisibilityScope from the AnimatedContent
// defined above.
this@AnimatedContent,
)
)
Text(
"Cute Cat YT",
fontSize = 40.sp,
color = Color.Blue,
// Prefer Modifier.sharedBounds for text, unless the texts in both initial
// content and target content are exactly the same (i.e. same
// size/font/color)
modifier =
Modifier.fillMaxWidth()
// IMPORTANT: Prefer using wrapContentWidth/wrapContentSize over
// textAlign
// for shared text transition. This allows the layout system sees actual
// position and size of the text to facilitate bounds animation.
.wrapContentWidth(Alignment.CenterHorizontally)
.sharedBounds(
rememberSharedContentState(key = "text"),
this@AnimatedContent,
),
)
} else {
Column {
Row(verticalAlignment = Alignment.CenterVertically) {
Cat(
Modifier.size(100.dp)
// Creating another shared element with the same key.
// Note that this modifier is *after* the size modifier,
// The size changes between these two shared elements, i.e. the size
// is not shared between the two shared elements.
.sharedElement(
rememberSharedContentState(sharedElementKey),
this@AnimatedContent,
)
)
Text(
"Cute Cat YT",
// Change text color & size
fontSize = 20.sp,
color = Color.DarkGray,
// Prefer Modifier.sharedBounds for text, unless the texts in both
// initial content and target content are exactly the same (i.e. same
// size/font/color)
modifier =
Modifier
// The modifier that is not a part of the shared content, but
// rather
// for positioning and sizes should be on the *left* side of
// sharedBounds/sharedElement.
.padding(start = 20.dp)
.sharedBounds(
// Here we use a string-based key, in contrast to the key
// above.
rememberSharedContentState(key = "text"),
this@AnimatedContent,
),
)
}
Box(
Modifier.fillMaxWidth()
.height(100.dp)
.background(Color(0xffffcc5c), RoundedCornerShape(5.dp))
)
Box(
Modifier.fillMaxWidth()
.height(100.dp)
.background(Color(0xff2a9d84), RoundedCornerShape(5.dp))
)
}
}
}
}
}