Material motion is a set of transition patterns that help users understand and navigate an app. For more information on the patterns and how to choose between them, check out the Material motion system spec.
Before you can use the motion library, you need to add a dependency on the
Material Components for Android library (version 1.2.0
or later). For more
information, go to the
Getting started
page.
Note: Motion theming will only be available in Material
Components for Android version 1.4.0-alpha01
and above.
Material Components for Android provides support for all four motion patterns defined in the Material spec.
The library offers transition classes for these patterns, built on top of both
the
AndroidX Transition library
(androidx.transition
) and the
Android Transition Framework
(android.transition
):
AndroidX (preferred)
- Available in the
com.google.android.material.transition
package - Supports API Level 14+
- Supports Fragments and Views, but not Activities or Windows
- Contains backported bug fixes and consistent behavior across API Levels
Platform
- Available in the
com.google.android.material.transition.platform
package - Supports API Level 21+
- Supports Fragments, Views, Activities, and Windows
- Bug fixes not backported and may have different behavior across API Levels
- Design Guidelines
- Codelab
- Blog Post
- Reply (example email app)
- Javadocs (AndroidX)
- Javadocs (Platform)
- Catalog Demos
- Library Source
- Android Platform Transition Guide
- AndroidX Transition Library
The container transform pattern is designed for transitions between UI elements that include a container. This pattern creates a visible connection between two UI elements.
MaterialContainerTransform
is a
shared element transition.
Unlike traditional Android shared elements, it is not designed around a singular
piece of shared content, such as an image, to be moved between two scenes.
Instead, the shared element here refers to the bounding container of a start
View
or ViewGroup
(e.g. the entire row layout of an item in a list)
transforming its size and shape into that of an end View
or ViewGroup
(e.g.
the root ViewGroup
of a full screen Fragment). These start and end container
Views are the “shared element” of a container transform. While these containers
are being transformed, their contents are swapped to create the transition.
Examples of the container transform:
- A card into a details page
- A list item into a details page
- A FAB into a details page
- A search bar into expanded search
A container transform can be configured to transition between a number of Android structures including Fragments, Activities and Views.
In Fragment A and Fragment B's layouts, identify the start and end Views (as
described in the container transform overview)
which will be shared. Add a matching transitionName
to each of these Views.
<!--fragment_a.xml-->
<View
android:id="@+id/start_view"
android:transitionName="shared_element_container" />
<!--fragment_b.xml-->
<View
android:id="@+id/end_view"
android:transitionName="shared_element_container" />
Note: There cannot be more than a 1:1 mapping of transitionNames
between
the start and end layouts. If you have multiple Views in your start layout that
could be mapped to an end View in your end layout (e.g. each RecyclerView
item
to a details screen), read about shared element mapping at
Continuous Shared Element Transitions: RecyclerView to ViewPager.
Set Fragment B's sharedElementEnterTransition
to a new
MaterialContainerTransform
. This can be done either before adding/replacing
Fragment B into your Fragment container or in Fragment B's onCreate
method.
// FragmentA.kt
val fragmentB = FragmentB()
fragmentB.sharedElementEnterTransition = MaterialContainerTransform()
/*** OR ***/
// FragmentB.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedElementEnterTransition = MaterialContainerTransform()
}
Add or replace Fragment B, adding the shared element from your start scene to your Fragment transaction.
childFragmentManager
.beginTransaction()
// Map the start View in FragmentA and the transitionName of the end View in FragmentB
.addSharedElement(view, "shared_element_container")
.replace(R.id.fragment_container, fragmentB, FragmentB.TAG)
.addToBackStack(FragmentB.TAG)
.commit()
If using the Navigation Architecture Component, use the following.
// Map the start View in FragmentA and the transitionName of the end View in FragmentB
val extras = FragmentNavigatorExtras(view to "shared_element_container")
findNavController().navigate(R.id.action_fragmentA_to_fragmentB, null, null, extras)
Completing these steps should give you a working enter and return container transform when navigating from Fragment A to Fragment B and popping from Fragment B to Fragment A.
Note: Fragments are able to define enter and return shared element
transitions. When only an enter shared element transition is set, it will be
re-used when the Fragment is popped (returns). MaterialContainerTransform
internally configures the transition’s properties based on whether or not it’s
entering or returning. If you need to customize either the enter or return style
of the transition, see Customizing the container transform.
When running this new transition, you might notice that Fragment A (everything
besides the shared element) disappears as soon as the container transform
starts. This is because FragmentA has been removed from its container. To “hold”
FragmentA in place as the container transform plays, set FragmentA's exit
transition to the the provided Hold
transition.
// FragmentA.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Fragment A’s exitTransition can be set any time before Fragment A is
// replaced with Fragment B. Ensure Hold's duration is set to the same
// duration as your MaterialContainerTransform.
exitTransition = Hold()
}
Note: When setting a Hold
or MaterialElevationScale
transition, it’s
important that the transition matches the duration of the
MaterialContainerTransform
it’s paired with. If explicitly setting a duration
on MaterialContainerTransform
with setDuration
, use the same value.
Otherwise, prefer the MaterialContainerTransform(Context, boolean)
constructor
which loads theme-based values upfront so Hold
or MaterialElevationScale
’s
duration can be accurately set using MaterialContainerTransform.getDuration
.
Alternatively, to subtly scale and fade Fragment A while the container transform
is playing, set FragmentA's exit and reenter transitions to a
MaterialElevationScale
transition. This will help to reinforce the spatial
relationship and navigational hierarchy of the two screens along the z-axis.
// FragmentA.kt
fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
exitTransition = MaterialElevationScale(/* growing= */ false)
reenterTransition = MaterialElevationScale(/* growing= */ true)
}
We pass in false
for the exit MaterialElevationScale
growing
constructor
param, to scale down or shrink Fragment A when it is exiting during the enter
container transform. Whereas we pass in true
for the reenter
MaterialElevationScale
to scale up or expand Fragment A when it is reentering
during the return container transform.
Note: When using MaterialElevationScale
, make sure to mark the root view
of your Fragment as a
transition group,
either with android:transitionGroup="true"
for API level 21+ or
ViewGroupCompat#setTransitionGroup
for all API levels. This will ensure that
the animation is applied to the Fragment view as a whole, as opposed to each
child view individually, which is the default behavior of the Android
Transitions system.
Note: Activity and Window transitions require using Android Framework
Transitions provided in the com.google.android.material.transition.platform
package and are only available on API level 21 and above.
In Activity A’s layout, identify the start View to be used as the “shared
element” as described in the
container transform overview. Give the start
view a transitionName
.
<!--activity_a.xml-->
<View
android:id="@+id/start_view"
android:transitionName="shared_element_container" />
Configure Activity A for an exit shared element transition as follows:
override fun onCreate(savedInstanceState: Bundle?) {
// Enable Activity Transitions. Optionally enable Activity transitions in your
// theme with <item name=”android:windowActivityTransitions”>true</item>.
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
// Attach a callback used to capture the shared elements from this Activity to be used
// by the container transform transition
setExitSharedElementCallback(MaterialContainerTransformSharedElementCallback())
// Keep system bars (status bar, navigation bar) persistent throughout the transition.
window.sharedElementsUseOverlay = false
super.onCreate(bundle)
setContentView(R.layout.activity_a)
...
}
In Activity B, configure the Activity for transitions in a similar fashion.
override fun onCreate(savedInstanceState: Bundle?) {
// Enable Activity Transitions. Optionally enable Activity transitions in your
// theme with <item name=”android:windowActivityTransitions”>true</item>.
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
// Set the transition name, which matches Activity A’s start view transition name, on
// the root view.
findViewById<View>(android.R.id.content).transitionName = "shared_element_container"
// Attach a callback used to receive the shared elements from Activity A to be
// used by the container transform transition.
setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback())
// Set this Activity’s enter and return transition to a MaterialContainerTransform
window.sharedElementEnterTransition = MaterialContainerTransform().apply {
addTarget(android.R.id.content)
duration = 300L
}
window.sharedElementReturnTransition = MaterialContainerTransform().apply {
addTarget(android.R.id.content)
duration = 250L
}
super.onCreate(bundle)
setContentView(R.layout.activity_b)
...
}
Note: We are using android.R.id.content
(the window’s root) as the shared
element “container” in Activity B. This will cause the start view from Activity
A to transition into the full screen of Activity B. If you have views in
Activity A and Activity B that you do not want included as part of the
transform, you can alternatively set the transition name on a View
/ViewGroup
in Activity B’s layout or include/exclude View
s with helper methods on the
Transition class (Transition#addTarget
, Transition#excludeChildren
, etc).
From Activity A, start the container transform by constructing an Intent with the following options.
val intent = Intent(this, ActivityB::class.java)
val options = ActivityOptions.makeSceneTransitionAnimation(
this,
startView,
"shared_element_container" // The transition name to be matched in Activity B.
)
startActivity(intent, options.toBundle())
In the Activity or Fragment where you are transitioning between two views,
trigger a MaterialContainerTransform
by manually setting the transition’s
start and end View
s.
val transform = MaterialContainerTransform().apply {
// Manually tell the container transform which Views to transform between.
startView = fab
endView = bottomToolbar
// Ensure the container transform only runs on a single target
addTarget(endView)
// Optionally add a curved path to the transform
pathMotion = MaterialArcMotion()
// Since View to View transforms often are not transforming into full screens,
// remove the transition's scrim.
scrimColor = Color.TRANSPARENT
}
// Begin the transition by changing properties on the start and end views or
// removing/adding them from the hierarchy.
TransitionManager.beginDelayedTransition(container, transform)
fab.visibility = View.GONE
bottomToolbar.visibility = View.VISIBLE
This will perform a container transform from the start view transitioning to the
end view. To return, set up the same transform, switching the start and end
Views and undoing any property changes (setting the FAB back to View.VISIBLE
and the bottomToolbar
back to View.GONE
) done by the first transform.
While the out-of-the-box container transform should work in most cases, you can
manually set the following properties on MaterialContainerTransform
to
customize the look and feel of the animation:
Element | Attribute | Related method(s) | Default value |
---|---|---|---|
Shape | transitionShapeAppearance |
getStartShapeAppearanceModel setStartShapeAppearanceModel getEndShapeAppearanceModel setEndShapeAppearanceModel |
null |
Duration (incoming) | motionDurationLong1 |
getDuration setDuration |
300ms |
Duration (outgoing) | motionDurationMedium2 |
getDuration setDuration |
250ms |
Easing | motionEasingStandard |
getInterpolator setInterpolator |
cubic-bezier(0.4, 0.0, 0.2, 1) FastOutSlowIn |
Motion path | motionPath |
getPathMotion setPathMotion |
linear |
Note: By default, MaterialContainerTransform
uses different durations
when incoming vs. outgoing. Calling setDuration
on an instance of
MaterialContainerTransform
will override this behavior, causing the passed
duration to be used both when incoming and outgoing. If you would like different
durations for incoming and outgoing animations, you should create and set
separate instances of MaterialContainerTransform
for entering and returning
transitions with the desired values. Alternatively, update the duration theme
attributes.
MaterialContainerTransform
provides two constructors - an empty parameter
constructor and a (Context, boolean)
constructor. The (Context, boolean)
constructor is used to load theme-values upfront, making it possible to query
for duration, interpolation and motion path if other transitions or animations,
such as Hold
and MaterialElevationScale
, depend on these values from
MaterialContainerTransform
.
See the Motion Theming section for details on how to systematically update motion.
Element | Related method(s) | Default value |
---|---|---|
Duration | getDuration setDuration |
300 |
Interpolation | getInterpolation setInterpolation |
R.interpolator.fast_out_slow_in |
Path Motion | getPathMotion setPathMotion |
null (Linear) |
Z Order | getDrawingViewId setDrawingViewId |
android.R.id.content |
Container Background Color | getContainerColor setContainerColor |
Color.TRANSPARENT |
Container Start View Background Color | getStartContainerColor setStartContainerColor |
Color.TRANSPARENT |
Container End View Background Color | getEndContainerColor setEndContainerColor |
Color.TRANSPARENT |
Scrim Color | getScrimColor setScrimColor |
#52000000 (32% opacity, black) |
Direction | getTransitionDirection setTransitionDirection |
MaterialContainerTransform.TRANSITION_DIRECTION_AUTO |
Fade Mode | getFadeMode setFadeMode |
MaterialContainerTransform.FADE_MODE_IN |
Fit Mode | getFitMode setFitMode |
MaterialContainerTransform.FIT_MODE_AUTO |
Fade Thresholds | getFadeProgressThresholds setFadeProgressThresholds |
[0.0 - 0.25] enter [0.6 - 0.9] return [0.1 - 0.4] enter w. arc [0.6 - 0.9] return w. arc |
Scale Thresholds | getScaleProgressThresholds setScaleProgressThresholds |
[0.0 - 1.0] enter [0.0 - 1.0] return [0.1 - 1.0] enter w. arc [0.0 - 0.9] return w. arc |
Scale Mask Thresholds | getScaleMaskProgressThresholds setScaleMaskProgressThresholds |
[0.0 - 1.0] enter [0.0 - 0.9] return [0.1 - 1.0] enter w. arc [0.0 - 0.9] return w. arc |
Shape Mask Thresholds | getShapeMaskProgressThresholds setShapeMaskProgressThresholds |
[0.0 - 0.75] enter [0.3 - 0.9] return [0.1 - 0.9] enter w. arc [0.2 - 0.9] return w. arc |
Debug Drawing | isDrawDebugEnabled() setDrawDebugEnabled() |
false |
Note: All of these properties have defaults. In most cases, each property has a different default value depending on whether or not the transition is entering or returning.
When you manually set any of the above properties, the value set will be used
when the transition is both entering and returning (including when an enter
transition is being re-used due to no return being set). If you need to manually
set properties which differ depending on whether or not the transition is
entering or returning, create two MaterialContainerTransforms
and set both the
sharedElementEnterTransition
and sharedElementReturnTransition
.
MaterialContainerTransform
supports the "Container transform: fade through
variant" mentioned in the
spec,
which will sequentially fade out and in the outgoing and incoming views,
respectively, in order to minimize the visual overlap of these views. This
variant can be achieved with the following Fade Mode configuration:
containerTransform.fadeMode = MaterialContainerTransform.FADE_MODE_THROUGH
If using the fade through variant, consider also tweaking the
MaterialContainerTransform
's Fade Thresholds property. For example, to make
the container transform's fade through happen over the full duration of the
transition, use the following Fade Thresholds configuration:
containerTransform.fadeProgressThresholds = MaterialContainerTransform.ProgressThresholds(0f, 1f)
The shared axis pattern is used for transitions between UI elements that have a spatial or navigational relationship. This pattern uses a shared transformation on the x, y, or z axis to reinforce the relationship between elements.
Examples of the shared axis pattern:
- An onboarding flow transitions along the x-axis
- A stepper transitions along the y-axis
- A parent-child navigation transitions along the z-axis
MaterialSharedAxis
is a Visibility
transition. A Visibility
transition is
triggered when the target View
's visibility is changed or when the View
is
added or removed. This means MaterialSharedAxis
requires a View to be changing
in visibility or to be added or removed to trigger its animation.
MaterialSharedAxis
uses the concept of moving in the forward or backward
direction. Below are the directions in which a MaterialSharedAxis
will move
for both the forward and backward directions along each axis.
Axis | Forward | Backward |
---|---|---|
X | Left on x-axis | Right on x-axis |
Y | Up on y-axis | Down on y-axis |
Z | Forward on z-axis | Backward on z-axis |
Note: Since a shared axis' direction is independent of whether its target
is appearing or dissapearing (an appearing target will sometimes be moving
forward when entering and forward when exiting), MaterialSharedAxis
is not
able to automatically reverse when only a target's enter transition is set. For
this reason, you should manually configure and set a target's transitions (enter
,exit, return, reenter) with the correct direction.
A shared axis transition can be configured to transition between a number of Android structures including Fragments, Activities and Views.
In the following example, we’re creating a shared axis Z transition between FragmentA and FragmentB. Moving from FragmentA to FragmentB should be a “forward” movement and returning from FragmentB to FragmentA should be a “backward” movement.
In Fragment A, configure an enter and exit transition.
// FragmentA.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, /* forward= */ true)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, /* forward= */ false)
}
In Fragment B, again configure an enter and exit transition.
// FragmentB.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, /* forward= */ true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, /* forward= */ false)
}
It’s important to note here how these two fragments move together. When Fragment
A is exiting, Fragment B will be entering. This is why, in Fragment A, the exit
transition is forward = true
and in Fragment B the enter transition is also
forward = true
. This will ensure that both Fragments are moving in the same direction
when these transition pairs are running. The opposite is true in the backwards
direction. When Fragment B is exiting, Fragment A will be reentering. For this
reason, Fragment B is configured to exit in the backward direction and Fragment
A is configured to reenter in the backward direction.
When you're ready to move from Fragment A to B, replace Fragment A with Fragment B.
supportFragmentManager
.beginTransaction()
.replace(R.id.fragment_container, FragmentB())
.commit()
The above should give you a working shared axis transition between Fragment A
and Fragment B. Changing the axis to MaterialSharedAxis.X
or
MaterialSharedAxis.Y
will create the same, coordinated interaction in their
respective axis. Alternatively, try replacing MaterialSharedAxis
with a
MaterialFadeThrough
for a transition between destinations or layouts that are
not spatially related.
Note: Activity and Window transitions require using Android Framework
Transitions provided in the com.google.android.material.transition.platform
package and are only available on API level 21 and above.
Enable Activity transitions by either setting
android:windowActivityTransitions
to true in your theme or enabling them on an
Activity by Activity basis by setting the Window.FEATURE_ACTIVITY_TRANSITIONS
flag.
<style name="MyTheme" parent="Theme.MaterialComponents.DayNight">
...
<item name="android:windowActivityTransitions">true</item>
</style>
Or in your Activities:
override fun onCreate(savedInstanceState: Bundle?) {
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
...
}
To get started, configure a new shared axis transition in Activity A and set it
as the Activity's exitTransition
.
// ActivityA.kt
override fun onCreate(savedInstanceState: Bundle?) {
val exit = MaterialSharedAxis(MaterialSharedAxis.X, true).apply {
// Only run the transition on the contents of this activity, excluding
// system bars or app bars if provided by the app’s theme.
addTarget(R.id.a_container)
}
window.exitTransition = exit
// TODO: Add a reenter transition in the backwards direction to animate
// ActivityB out and ActivityA back in in the opposite direction.
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_a)
}
You can optionally add or exclude targets to have the transition affect or ignore Views. Use the combination you need to have the transition applied where desired. For example:
val exit = MaterialSharedAxis(MaterialSharedAxis.X, true).apply {
// Only run the transition on the root ViewGroup of this activity. This will exclude
// other views except what is specified by this method.
addTarget(R.id.a_container)
// OR
// Run the transition on everything except the system status and navigation bars. All
// other Views, besides those explicitly excluded, will be affected by the transition.
excludeTarget(android.R.id.statusBarBackground, true)
excludeTarget(android.R.id.navigationBarBackground, true)
}
Next, configure a new MaterialSharedAxis
enter transition in Activity B.
// ActivityB.kt
override fun onCreate(savedInstanceState: Bundle?) {
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
val enter = MaterialSharedAxis(MaterialSharedAxis.X, true).apply {
addTarget(R.id.b_container)
}
window.enterTransition = enter
// TODO: Configure a return transition in the backwards direction.
// Allow Activity A’s exit transition to play at the same time as this Activity’s
// enter transition instead of playing them sequentially.
window.allowEnterTransitionOverlap = true
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_b)
...
}
When you're ready to navigate from Activity A to Activity B, start Activity B
like your normally would, passing in an ActivityOptions
Bundle
.
val bundle = ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
startActivity(Intent(this, ActivityB::class.java), bundle)
In your Activity or Fragment’s layout, identify the two views which will be
“swapped”. The outgoing View should be added to the layout and visible. The
incoming View's visibility should either be set to View.GONE
or the View
should not yet be added to the layout. When you’re ready to replace the outgoing
view with the incoming View, do so with a shared axis transition as follows.
// Set up a new MaterialSharedAxis in the specified axis and direction.
val sharedAxis = MaterialSharedAxis(MaterialSharedAxis.Y, true)
// Begin watching for changes in the View hierarchy.
TransitionManager.beginDelayedTransition(container, sharedAxis)
// Make any changes to the hierarchy to be animated by the shared axis transition.
outgoingView.visibility = View.GONE
incomingView.visibility = View.VISIBLE
This will transition between your outgoing and incoming Views with a shared axis
transition. To reverse the animation, set up a new shared axis in the opposite
direction and set your outgoing View back to View.VISIBLE
and your incoming
View back to View.GONE
.
MaterialSharedAxis
is an extension of MaterialVisibility
.
MaterialVisibility
is a Visibility
transition composed of smaller, "atomic"
VisibilityAnimatorProvider
s. These providers are classes which can be
configured and are able to construct an animator depending on whether a target
is appearing or disappearing. By default, a MaterialVisibility
implementation
has a primary and secondary VisibilityAnimatorProvider
. The primary provider
can be modified while the secondary provider can be either modified, replaced or
removed. This allows for the customization of Material motion while still
adhering to a pattern's foundation and is refered to as a variant.
Element | Primary transition | Secondary transition |
---|---|---|
MaterialSharedAxis | X -SlideDistance Y - SlideDistance Z - Scale |
FadeThrough |
The following is a MaterialSharedAxis
Z transition between Activities which
fades Activity B in and over Activity A while leaving Activity A’s alpha
unchanged. This can be accomplished by removing the secondary
FadeThroughProvider
from Activity A's exit transition.
// ActivityA.kt
override fun onCreate(savedInstanceState: Bundle?) {
val exit = MaterialSharedAxis(MaterialSharedAxis.Z, true).apply {
// Remove the exit transitions secondary animator provider completely so this Activity
// only scales instead of scaling and fading out. Alternatively, this could be
// set to a modified FadeThroughProvider or any other VisibilityAnimatorProvider.
secondaryAnimatorProvider = null
addTarget(R.id.main_container)
}
window.exitTransition = exit
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
...
}
Element | Attribute | Related method(s) | Default value |
---|---|---|---|
Duration | motionDurationLong1 |
getDuration setDuration |
300ms |
Easing | motionEasingStandard |
getInterpolator setInterpolator |
cubic-bezier(0.4, 0.0, 0.2, 1) FastOutSlowIn |
See the Motion Theming section for details on how to systematically update motion.
The fade through pattern is used for transitions between UI elements that do not have a strong relationship to each other.
Examples of the fade through pattern:
- Tapping destinations in a bottom navigation bar
- Tapping a refresh icon
- Tapping an account switcher
MaterialFadeThrough
is a Visibility
transition. A Visibility
transition is
triggered when the target View
's visibility is changed or when the View
is
added or removed. This means MaterialFadeThrough
requires a View to be
changing in visibility or to be added or removed to trigger its animation.
A fade through can be configured to transition between a number of Android structures including Fragments, Activities and Views.
In Fragment A, configure an exit MaterialFadeThrough
transition and in
Fragment B configure an enter MaterialFadeThrough
transition. Both of these
will be used (and reused) when navigating from Fragment A to Fragment B and from
Fragment B to Fragment A.
// FragmentA.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
exitTransition = MaterialFadeThrough()
}
// FragmentB.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialFadeThrough()
}
Note: Since MaterialFadeThrough
extends Visibility
,
MaterialFadeThrough
is able to appropriately animate targets depending on
whether they are apperaing or disappearing.
When you're ready to navigate between Fragment A and Fragment B, use a standard Fragment transaction or use the Navigation Component.
supportFragmentManager
.beginTransaction()
.replace(R.id.fragment_container, FragmentB())
.commit()
Note: Activity and Window transitions require using Android Framework
Transitions provided in the com.google.android.material.transition.platform
package and are only available on API level 21 and above.
Enable Activity transitions by either setting
android:windowActivityTransitions
to true in your theme or enabling them on an
Activity-by-Activity basis by setting the Window.FEATURE_ACTIVITY_TRANSITIONS
flag.
<style name="MyTheme" parent="Theme.MaterialComponents.DayNight">
...
<item name="android:windowActivityTransitions">true</item>
</style>
Or in your Activities:
override fun onCreate(savedInstanceState: Bundle?) {
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
...
}
To get started, configure a new MaterialFadeThrough
in Activity A and set it
as the Activity's exitTransition.
// ActivityA.kt
override fun onCreate(savedInstanceState: Bundle?) {
val exit = MaterialFadeThrough().apply {
// Only run the transition on the contents of this activity, excluding
// system bars or app bars if provided by the app’s theme.
addTarget(R.id.a_container)
}
window.exitTransition = exit
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_a)
}
You can optionally add or exclude targets to have the transition affect or ignore Views. Use the combination you need to have the transition applied where you’d like. For example:
val exit = MaterialFadeThrough().apply {
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
// Only run the transition on the root ViewGroup of this activity. This will exclude
// other Views except what is specified by this method.
addTarget(R.id.a_container)
// OR
// Run the transition on everything except the system status and navigation bars. All
// other Views, besides those explicitly excluded, will be affected by the transition.
excludeTarget(android.R.id.statusBarBackground, true)
excludeTarget(android.R.id.navigationBarBackground, true)
}
Next, configure a new MaterialFadeThrough
enter transition in Activity B.
// ActivityB.kt
override fun onCreate(savedInstanceState: Bundle?) {
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
val enter = MaterialFadeThrough().apply {
addTarget(R.id.b_container)
}
window.enterTransition = enter
// Allow Activity A’s exit transition to play at the same time as this Activity’s
// enter transition instead of playing them sequentially.
window.allowEnterTransitionOverlap = true
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_b)
...
}
When you're ready to navigate from Activity A to Activity B, start Activity B as
your normally would, passing in an ActivityOptions
Bundle
.
val bundle = ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
startActivity(Intent(this, ActivityB::class.java), bundle)
In your Activity or Fragment’s layout, identify the two Views which will be
“swapped”. The outgoing View should be added to the layout and visible. The
incoming View should either be set to View.GONE
or not yet added to the
layout. When you’re ready to replace the outgoing View with the incoming View,
do so with a fade through transition as follows.
val fadeThrough = MaterialFadeThrough()
// Begin watching for changes in the View hierarchy.
TransitionManager.beginDelayedTransition(container, fadeThrough)
// Make any changes to the hierarchy to be animated by the fade through transition.
outgoingView.visibility = View.GONE
incomingView.visibility = View.VISIBLE
This will transition between your outgoing and incoming Views with a fade
through transition. To reverse the animation, follow the same steps, setting
your outgoing View back to View.VISIBLE
and your incoming View back to
View.GONE
.
MaterialFadeThrough
is an extension of MaterialVisibility
.
MaterialVisibility
is a Visibility
transition composed of smaller, "atomic"
VisibilityAnimatorProvider
s. These providers are classes which can be
configured and are able to construct an animator depending on whether a target
is appearing or disappearing. By default, a MaterialVisibility
implementation
has a primary and secondary VisibilityAnimatorProvider
. The primary provider
can be modified while the secondary provider can be either modified, replaced or
removed. This allows for the customization of Material motion while still
adhering to a pattern's foundation and is refered to as a variant.
Element | Primary transition | Secondary transition |
---|---|---|
MaterialFadeThrough | FadeThrough |
Scale |
The below will create a fade through between Fragments which fades Fragment A out (without a scale) and fades Fragment B in with a slide instead of a scale.
// FragmentA.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
exitTransition = MaterialFadeThrough().apply {
// Remove the exit fade through's secondary scale so this Fragment simply fades out.
secondaryAnimatorProvider = null
}
}
// FragmentA.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialFadeThrough().apply {
// Replace the enter fade through's secondary animator provider with a SlideDistanceProvider.
secondaryAnimatorProvider = SlideDistanceProvider(Gravity.BOTTOM)
}
}
Element | Attribute | Related method(s) | Default value |
---|---|---|---|
Duration | motionDurationLong1 |
getDuration setDuration |
300ms |
Easing | motionEasingStandard |
getInterpolator setInterpolator |
cubic-bezier(0.4, 0.0, 0.2, 1) FastOutSlowIn |
See the Motion Theming section for details on how to systematically update motion.
The fade pattern is used for UI elements that enter or exit within the bounds of the screen, such as a dialog that fades in the center of the screen.
- A dialog
- A menu
- A snackbar
- A FAB
MaterialFade
is a Visibility
transition. A Visibility
transition is
triggered when the target View
's visibility is changed or when the View
is
added or removed. This means MaterialFade
requires a View to be changing in
visibility or to be added or removed to trigger its animation.
In your Activity or Fragment, toggle the visibility of your target View, in this
case a Floating Action Button, using a MaterialFade
to animate the change.
// FragmentA.kt
showButton.setOnClickListener {
val materialFade = MaterialFade().apply {
duration = 150L
}
TransitionManager.beginDelayedTransition(container, materialFade)
fab.visibility = View.VISIBLE
}
When reversing the transition, configure and trigger a MaterialFade
in the
same manner, making any adjustments to the transition that differ when entering
versus exiting.
// FragmentA.kt
hideButton.setOnClickListener {
val materialFade = MaterialFade().apply {
duration = 84L
}
TransitionManager.beginDelayedTransition(container, materialFade)
fab.visibility = View.GONE
}
MaterialFade
is an extension of MaterialVisibility
. MaterialVisibility
is
a Visibility
transition composed of smaller, "atomic"
VisibilityAnimatorProvider
s. These providers are classes which can be
configured and are able to construct an animator depending on whether a target
is appearing or disappearing. By default, a MaterialVisibility
implementation
has a primary and secondary VisibilityAnimatorProvider
. The primary provider
can be modified while the secondary provider can be either modified, replaced or
removed. This allows for the customization of Material motion while still
adhering to a pattern's foundation and is refered to as a variant.
Element | Primary transition | Secondary transition |
---|---|---|
MaterialFade | Fade |
Scale |
Element | Attribute | Related method(s) | Default value |
---|---|---|---|
Duration (incoming) | motionDurationShort2 |
getDuration setDuration |
150ms |
Duration (outgoing) | motionDurationShort1 |
getDuration setDuration |
75ms |
Easing | motionEasingLinear |
getInterpolator setInterpolator |
cubic-bezier(0 0, 1, 1) Linear |
See the Motion Theming section for details on how to systematically update motion.
Motion theming will only be available in Material Components for Android version
1.4.0-alpha01
and above.
The Material motion system is backed by a limited number of slots which transitions use by default to create a consistent, branded feel. These slots are implemented as theme attributes, similar to color or shape attributes.
Easing theme attributes define a set of curves that can be inflated and used as Interpolators.
Attribute | Default value | Description |
---|---|---|
?attr/motionEasingStandard | cubic-bezier(0.4, 0.0, 0.2, 1) FastOutSlowIn |
Easing used for elements that begin and end at rest. |
?attr/motionEasingEmphasized | path(M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1) FastOutExtraSlowIn |
Easing used for elements that begin and end at rest but want extra attention drawn to the end of the animation. |
?attr/motionEasingDecelerated | cubic-bezier(0.0, 0.0, 0.2, 1) LinearOutSlowIn |
Easing used for incoming elements; motion begins at peak velocity and ends at rest. |
?attr/motionEasingAccelerated | cubic-bezier(0.4, 0.0, 1, 1) FastOutLinearIn |
Easing used for outgoing elements; motion begins at rest and ends at peak velocity. |
?attr/motionEasingLinear | cubic-bezier(0, 0, 1, 1) Linear |
Easing for simple motion such as fading. |
Easing attributes are able to accept two types of curves - cubic beziers and vector paths. Cubic bezier curves are in the standard (x1, y1, x2, y2) format. For vector path curves, the curve must start at 0,0 and end at 1, 1. Vector path curves can be beneficial if you’d like to introduce 3-point (quintic) easing curves to your app in a backwards compatible way.
To update an easing value override any of the attributes in your app’s theme
following the <type>(value)
string format.
<style name="Theme.MyTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
....
<item name="motionEasingEmphasized">path(M 0,0 C .25, 0, .266666, .333334, .333334, .7166664 C .366667, .9666664, .3333334, 1, 1, 1)</item>
</style>
For more information on easing, see material.io/design/motion/customization.html#applying-customizations.
Duration attributes are a set of durations in milliseconds that can be used for animations.
Attribute | Default value | Description |
---|---|---|
?attr/motionDurationShort1 | 75ms |
Duration for use with small motion areas such as icons and selection controls. |
?attr/motionDurationShort2 | 150ms |
|
?attr/motionDurationMedium1 | 200ms |
Duration for use with large motion areas such as bottom sheets and expanding chips. |
?attr/motionDurationMedium2 | 250ms |
|
?attr/motionDurationLong1 | 300ms |
Duration for use with elements that traverse a large portion of the screen, such as page transitions. |
?attr/motionDurationLong2 | 350ms |
In general, durations should increase in duration as the area/traversal of an animation increases. Maintaining this rule when customizing duration attributes will ensure your transitions have a consistent sense of speed.
To override a duration attribute, assign the attribute to your desired millisecond integer value.
<style name="Theme.MyTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
....
<item name="motionDurationLong2">450</item>
</style>
For more information on duration, see material.io/design/motion/customization.html#speed
Path attributes are values which control the behavior of animating elements.
Attribute | Default value | Description |
---|---|---|
?attr/motionPath | linear |
An enum that controls the path along which animating elements move.linear : Elements move along a straight path from their current position to their new position. A linear path corresponds to a null PathMotion .arc : Elements move along a curved/arced path. An arc path corresponds to a MaterialArcMotion PathMotion . |
For more information of motionPath, see material.io/design/motion/customization.html#motion-paths