SharedTransitionScope
@ExperimentalSharedTransitionApi
public interface SharedTransitionScope : LookaheadScope
SharedTransitionScope
provides a coordinator space in which shared elements/ shared bounds
(when matched) will transform their bounds from one to another. Their position animation is
always relative to the origin defined by where SharedTransitionScope
is in the tree.
SharedTransitionScope
also creates an overlay, in which all shared elements and shared bounds
are rendered by default, so that they are not subject to their parent's fading or clipping, and
can therefore transform the bounds without alpha jumps or being unintentionally clipped.
It is also SharedTransitionScope
's responsibility to do the SharedContentState
key match for
all the sharedElement
or sharedBounds
defined in this scope. Note: key match will not work
for SharedContentState
created in different SharedTransitionScope
s.
SharedTransitionScope
oversees all the animations in its scope. When any of the animations is
active, isTransitionActive
will be true. Once a bounds transform starts, by default the shared
element or shared bounds will render the content in the overlay. The rendering will remain in the
overlay until all other animations in the SharedTransitionScope
are finished (i.e. when
isTransitionActive
== false).
Functions
public fun scaleInSharedContentToBounds(
contentScale: ContentScale = ContentScale.Fit,
alignment: Alignment = Alignment.Center,
): EnterTransition
public fun scaleOutSharedContentToBounds(
contentScale: ContentScale = ContentScale.Fit,
alignment: Alignment = Alignment.Center,
): ExitTransition
public fun Modifier.skipToLookaheadSize(): Modifier
skipToLookaheadSize
enables a layout to measure its child with the lookahead constraints,
therefore laying out the child as if the transition has finished. This is particularly
helpful for layouts where re-flowing content based on animated constraints is undesirable,
such as texts.
In the sample below, try remove the skipToLookaheadSize
modifier and observe the
difference:
public fun Modifier.renderInSharedTransitionScopeOverlay(
renderInOverlay: () -> Boolean = { isTransitionActive },
zIndexInOverlay: Float = 0f,
clipInOverlayDuringTransition: (LayoutDirection, Density) -> Path? =
DefaultClipInOverlayDuringTransition,
): Modifier
Renders the content in the SharedTransitionScope
's overlay, where shared content (i.e.
shared elements and shared bounds) is rendered by default. This is useful for rendering
content that is not shared on top of shared content to preserve a specific spatial
relationship.
renderInOverlay
dynamically controls whether the content should be rendered in the
SharedTransitionScope
's overlay. By default, it returns the same value as
SharedTransitionScope.isTransitionActive
. This means the default behavior is to render the
child layout of this modifier in the overlay only when the transition is active.
IMPORTANT: When elevating layouts into the overlay, the layout is no longer subjected
to 1) its parent's clipping, and 2) parent's layer transform (e.g. alpha, scale, etc).
Therefore, it is recommended to create an enter/exit animation (e.g. using
AnimatedVisibilityScope.animateEnterExit
) for the child layout to avoid any abrupt visual
changes.
clipInOverlayDuringTransition
supports a custom clip path if clipping is desired. By
default, no clipping is applied. Manual management of clipping can often be avoided by
putting layouts with clipping as children of this modifier (i.e. to the right side of this
modifier).
public fun Modifier.sharedElement(
sharedContentState: SharedContentState,
animatedVisibilityScope: AnimatedVisibilityScope,
boundsTransform: BoundsTransform = DefaultBoundsTransform,
placeHolderSize: PlaceHolderSize = contentSize,
renderInOverlayDuringTransition: Boolean = true,
zIndexInOverlay: Float = 0f,
clipInOverlayDuringTransition: OverlayClip = ParentClip,
): Modifier
sharedElement
is a modifier that tags a layout with a SharedContentState.key
, such that
entering and exiting shared elements of the same key share the animated and continuously
changing bounds during the layout change. The bounds will be animated from the initial bounds
defined by the exiting shared element to the target bounds calculated based on the incoming
shared element. The animation for the bounds can be customized using boundsTransform
.
During the bounds transform, sharedElement
will re-measure and relayout its child layout
using fixed constraints derived from its animated size, similar to RemeasureToBounds
resizeMode in sharedBounds
.
In contrast to sharedBounds
, sharedElement
is designed for shared content that has the
exact match in terms of visual content and layout when the measure constraints are the same.
Such examples include image assets, icons,
MovableContent
etc. Only the shared element that
is becoming visible will be rendered during the transition. The bounds for shared element are
determined by the bounds of the shared element becoming visible based on the target state of
animatedVisibilityScope
.
Important: When a shared element finds its match and starts a transition, it will be
rendered into the overlay of the SharedTransitionScope
in order to avoid being faded in/out
along with its parents or clipped by its parent as it transforms to the target size and
position. This also means that any clipping or fading for the shared elements will need to be
applied explicitly as the child of sharedElement
(i.e. after sharedElement
modifier in
the modifier chain). For example: Modifier.sharedElement(...).clip(shape = RoundedCornerShape(20.dp)).animateEnterExit(...)
By default, the sharedElement
is clipped by the clipInOverlayDuringTransition
of its
parent sharedBounds
. If the sharedElement
has no parent sharedBounds
or if the parent
sharedBounds
has no clipping defined, it'll not be clipped. If additional clipping is
desired to ensure sharedElement
doesn't move outside of a visual bounds,
clipInOverlayDuringTransition
can be used to specify the clipping for when the shared
element is going through an active transition towards a new target bounds.
While the shared elements are rendered in overlay during the transition, its
zIndexInOverlay
can be specified to allow shared elements to render in a different order
than their placement/zOrder when not in the overlay. For example, the title of a page is
typically placed and rendered before the content below. During the transition, it may be
desired to animate the title over on top of the other shared elements on that page to
indicate significance or a point of interest. zIndexInOverlay
can be used to facilitate
such use cases. zIndexInOverlay
is 0f by default.
renderInOverlayDuringTransition
is true by default. In some rare use cases, there may be no
clipping or layer transform (fade, scale, etc) in the application that prevents shared
elements from transitioning from one bounds to another without any clipping or sudden alpha
change. In such cases, renderInOverlayDuringTransition
could be specified to false.
During a shared element transition, the space that was occupied by the exiting shared element
and the space that the entering shared element will take up are considered place holders.
Their sizes during the shared element transition can be configured through placeHolderSize
.
By default, it will be the same as the content size of the respective shared element. It can
also be set to animatedSize
or any other PlaceHolderSize
to report to their parent layout
an animated size to create a visual effect where the parent layout dynamically adjusts the
layout to accommodate the animated size of the shared elements.
public fun Modifier.sharedBounds(
sharedContentState: SharedContentState,
animatedVisibilityScope: AnimatedVisibilityScope,
enter: EnterTransition = fadeIn(),
exit: ExitTransition = fadeOut(),
boundsTransform: BoundsTransform = DefaultBoundsTransform,
resizeMode: ResizeMode = ScaleToBounds(ContentScale.FillWidth, Center),
placeHolderSize: PlaceHolderSize = contentSize,
renderInOverlayDuringTransition: Boolean = true,
zIndexInOverlay: Float = 0f,
clipInOverlayDuringTransition: OverlayClip = ParentClip,
): Modifier
sharedBounds
is a modifier that tags a layout with a SharedContentState.key
, such that
entering and exiting shared bounds of the same key share the animated and continuously
changing bounds during the layout change. The bounds will be animated from the initial bounds
defined by the exiting shared bounds to the target bounds calculated based on the incoming
shared shared bounds. The animation for the bounds can be customized using boundsTransform
.
The target bounds for sharedBounds
are determined by the bounds of the sharedBounds
becoming visible based on the target state of animatedVisibilityScope
.
In contrast to sharedElement
, sharedBounds
is designed for shared content that has the
visually different content. While the sharedBounds
keeps the continuity of the bounds, the
incoming and outgoing content within the sharedBounds
will enter and exit in an enter/exit
transition using enter
/exit
. By default, fadeIn
and fadeOut
are used to fade the
content in or out.
resizeMode
defines how the child layout of sharedBounds
should be resized during
boundsTransform
. By default, ScaleToBounds
will be used to measure the child content with
lookahead constraints to arrive at the stable layout. Then the stable layout will be scaled
to fit or fill (depending on the content scale used) the transforming bounds of
sharedBounds
. If there's a need to relayout content (rather than scaling) based on the
animated bounds size (e.g. dynamically resizing a Row), it's recommended to use
RemeasureToBounds
as the resizeMode
.
Important: When a shared bounds finds its match and starts a transition, it will be
rendered into the overlay of the SharedTransitionScope
in order to avoid being faded in/out
along with its parents or clipped by its parent as it transforms to the target size and
position. This also means that any clipping or fading for the shared elements will need to be
applied explicitly as the child of sharedBounds
(i.e. after sharedBounds
modifier in
the modifier chain). For example: Modifier.sharedBounds(...).clip(shape = RoundedCornerShape(20.dp))
By default, the sharedBounds
is clipped by the clipInOverlayDuringTransition
of its
parent sharedBounds
in the layout tree. If the sharedBounds
has no parent sharedBounds
or if the parent sharedBounds
has no clipping defined, it'll not be clipped. If additional
clipping is desired to ensure child sharedBounds
or child sharedElement
don't move
outside of the this sharedBounds
's visual bounds in the overlay,
clipInOverlayDuringTransition
can be used to specify the clipping.
While the shared bounds are rendered in overlay during the transition, its zIndexInOverlay
can be specified to allow them to render in a different order than their placement/zOrder
when not in the overlay. For example, the title of a page is typically placed and rendered
before the content below. During the transition, it may be desired to animate the title over
on top of the other shared elements on that page to indicate significance or a point of
interest. zIndexInOverlay
can be used to facilitate such use cases. zIndexInOverlay
is 0f
by default.
renderInOverlayDuringTransition
is true by default. In some rare use cases, there may be no
clipping or layer transform (fade, scale, etc) in the application that prevents shared
elements from transitioning from one bounds to another without any clipping or sudden alpha
change. In such cases, renderInOverlayDuringTransition
could be specified to false.
During a shared bounds transition, the space that was occupied by the exiting shared bounds
and the space that the entering shared bounds will take up are considered place holders.
Their sizes during the shared element transition can be configured through placeHolderSize
.
By default, it will be the same as the content size of the respective shared bounds. It can
also be set to animatedSize
or any other PlaceHolderSize
to report to their parent layout
an animated size to create a visual effect where the parent layout dynamically adjusts the
layout to accommodate the animated size of the shared elements.
Since sharedBounds
show both incoming and outgoing content in its bounds, it affords
opportunities to do interesting transitions where additional sharedElement
and
sharedBounds
can be nested in a parent sharedBounds
. See the sample code below for a more
complex example with nested shared bounds/elements.
public fun Modifier.sharedElementWithCallerManagedVisibility(
sharedContentState: SharedContentState,
visible: Boolean,
boundsTransform: BoundsTransform = DefaultBoundsTransform,
placeHolderSize: PlaceHolderSize = contentSize,
renderInOverlayDuringTransition: Boolean = true,
zIndexInOverlay: Float = 0f,
clipInOverlayDuringTransition: OverlayClip = ParentClip,
): Modifier
sharedElementWithCallerManagedVisibility
is a modifier that tags a layout with a
SharedContentState.key
, such that entering and exiting shared elements of the same key
share the animated and continuously changing bounds during the layout change. The bounds will
be animated from the initial bounds defined by the exiting shared element to the target
bounds calculated based on the incoming shared element. The animation for the bounds can be
customized using boundsTransform
.
Compared to sharedElement
, sharedElementWithCallerManagedVisibility
is designed for
shared element transitions where the shared element is not a part of the content that is
being animated out by AnimatedVisibility
. Therefore, it is the caller's responsibility to
explicitly remove the exiting shared element (i.e. shared elements where visible
== false)
from the tree as appropriate. Typically this is when the transition is finished (i.e.
SharedTransitionScope.isTransitionActive
== false). The target bounds is derived from the
sharedElementWithCallerManagedVisibility
with visible
being true.
In contrast to sharedBounds
, this modifier is intended for shared content that has the
exact match in terms of visual content and layout when the measure constraints are the same.
Such examples include image assets, icons,
MovableContent
etc. Only the shared element that
is becoming visible will be rendered during the transition.
Important: When a shared element finds its match and starts a transition, it will be
rendered into the overlay of the SharedTransitionScope
in order to avoid being faded in/out
along with its parents or clipped by its parent as it transforms to the target size and
position. This also means that any clipping or fading for the shared elements will need to be
applied explicitly as the child of sharedElementWithCallerManagedVisibility
(i.e. after
sharedElementWithCallerManagedVisibility
modifier in the modifier chain). For example:
Modifier.sharedElementWithCallerManagedVisibility(...) .clip(shape = RoundedCornerShape(20.dp))
By default, the sharedElementWithCallerManagedVisibility
is clipped by the
clipInOverlayDuringTransition
of its parent sharedBounds
. If the
sharedElementWithCallerManagedVisibility
has no parent sharedBounds
or if the parent
sharedBounds
has no clipping defined, it'll not be clipped. If additional clipping is
desired to ensure sharedElementWithCallerManagedVisibility
doesn't move outside of a visual
bounds, clipInOverlayDuringTransition
can be used to specify the clipping for when the
shared element is going through an active transition towards a new target bounds.
While the shared elements are rendered in overlay during the transition, its
zIndexInOverlay
can be specified to allow shared elements to render in a different order
than their placement/zOrder when not in the overlay. For example, the title of a page is
typically placed and rendered before the content below. During the transition, it may be
desired to animate the title over on top of the other shared elements on that page to
indicate significance or a point of interest. zIndexInOverlay
can be used to facilitate
such use cases. zIndexInOverlay
is 0f by default.
renderInOverlayDuringTransition
is true by default. In some rare use cases, there may be no
clipping or layer transform (fade, scale, etc) in the application that prevents shared
elements from transitioning from one bounds to another without any clipping or sudden alpha
change. In such cases, renderInOverlayDuringTransition
could be specified to false.
During a shared element transition, the space that was occupied by the exiting shared element
and the space that the entering shared element will take up are considered place holders.
Their sizes during the shared element transition can be configured through placeHolderSize
.
By default, it will be the same as the content size of the respective shared element. It can
also be set to animatedSize
or any other PlaceHolderSize
to report to their parent layout
an animated size to create a visual effect where the parent layout dynamically adjusts the
layout to accommodate the animated size of the shared elements.
public fun OverlayClip(clipShape: Shape): OverlayClip
Creates an OverlayClip
based on a specific clipShape
.
@Composable public fun rememberSharedContentState(key: Any): SharedContentState
Creates and remembers a SharedContentState
with a given key
.