Android's transition framework lets you animate all kinds of motion in your UI by providing the starting and ending layouts. You can select what type of animation you want—such as to fade views in or out, or to change view sizes—and the transition framework determines how to animate from the starting layout to the ending layout.
The transition framework includes the following features:
- Group-level animations: apply animation effects to all the views in a view hierarchy.
- Built-in animations: use predefined animations for common effects such as fade out or movement.
- Resource file support: load view hierarchies and built-in animations from layout resource files.
- Lifecycle callbacks: receive callbacks that provide control over the animation and hierarchy change process.
For sample code that animates between layout changes, see BasicTransition.
The basic process to animate between two layouts is as follows:
- Create a
Scene
object for the starting and ending layouts. However, the starting layout's scene is often determined automatically from the current layout. - Create a
Transition
object to define what type of animation you want. - Call
TransitionManager.go()
, and the system runs the animation to swap the layouts.
The diagram in figure 1 illustrates the relationship between your layouts, the scenes, the transition, and the final animation.
Create a scene
Scenes store the state of a view hierarchy, including all its views and their property values. The transitions framework can run animations between a starting and an ending scene.
You can create your scenes from a layout resource file or from a group of views in your code. However, the starting scene for your transition is often determined automatically from the current UI.
A scene can also define its own actions that run when you make a scene change. This feature is useful for cleaning up view settings after you transition to a scene.
Create a scene from a layout resource
You can create a Scene
instance directly from a layout resource
file. Use this technique when the view hierarchy in the file is mostly static.
The resulting scene represents the state of the view hierarchy at the time you
created the Scene
instance. If you change the view hierarchy,
recreate the scene. The framework creates the scene from the entire view
hierarchy in the file. You can't create a scene from part of a layout file.
To create a Scene
instance from a layout resource file, retrieve
the scene root from your layout as a
ViewGroup
. Then, call the
Scene.getSceneForLayout()
function with the scene root and the resource ID of the layout file that
contains the view hierarchy for the scene.
Define layouts for scenes
The code snippets in the rest of this section show how to create two
different scenes with the same scene root element. The snippets also demonstrate
that you can load multiple unrelated Scene
objects without implying that they
are related to each other.
The example consists of the following layout definitions:
- The main layout of an activity with a text label and a child
FrameLayout
. - A
ConstraintLayout
for the first scene with two text fields. - A
ConstraintLayout
for the second scene with the same two text fields in different order.
The example is designed so that all of the animation occurs within the child layout of the main layout for the activity. The text label in the main layout remains static.
The main layout for the activity is defined as follows:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/master_layout"> <TextView android:id="@+id/title" ... android:text="Title"/> <FrameLayout android:id="@+id/scene_root"> <include layout="@layout/a_scene" /> </FrameLayout> </LinearLayout>
This layout definition contains a text field and a child FrameLayout
for the
scene root. The layout for the first scene is included in the main layout file.
This lets the app display it as part of the initial user interface and also load
it into a scene, since the framework can only load a whole layout file into a
scene.
The layout for the first scene is defined as follows:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/scene_container" android:layout_width="match_parent" android:layout_height="match_parent" ></androidx.constraintlayout.widget.ConstraintLayout>
The layout for the second scene contains the same two text fields—with the same IDs—placed in a different order. It is defined as follows:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/scene_container" android:layout_width="match_parent" android:layout_height="match_parent" ></androidx.constraintlayout.widget.ConstraintLayout>
Generate scenes from layouts
After you create definitions for the two constraint layouts, you can obtain a scene for each of them. This lets you transition between the two UI configurations. To obtain a scene, you need a reference to the scene root and the layout resource ID.
The following code snippet shows how to get a reference to the scene root and
create two Scene
objects from the layout files:
Kotlin
val sceneRoot: ViewGroup = findViewById(R.id.scene_root) val aScene: Scene = Scene.getSceneForLayout(sceneRoot, R.layout.a_scene, this) val anotherScene: Scene = Scene.getSceneForLayout(sceneRoot, R.layout.another_scene, this)
Java
Scene aScene; Scene anotherScene; // Create the scene root for the scenes in this app. sceneRoot = (ViewGroup) findViewById(R.id.scene_root); // Create the scenes. aScene = Scene.getSceneForLayout(sceneRoot, R.layout.a_scene, this); anotherScene = Scene.getSceneForLayout(sceneRoot, R.layout.another_scene, this);
In the app, there are now two Scene
objects based on view
hierarchies. Both scenes use the scene root defined by the
FrameLayout
element in res/layout/activity_main.xml
.
Create a scene in your code
You can also create a Scene
instance in your code from a
ViewGroup
object. Use this technique when you modify the view hierarchies
directly in your code or when you generate them dynamically.
To create a scene from a view hierarchy in your code, use the
Scene(sceneRoot, viewHierarchy)
constructor. Calling this constructor is equivalent to calling the
Scene.getSceneForLayout()
function when you already inflated a layout file.
The following code snippet demonstrates how to create a Scene
instance from the scene root element and the view hierarchy for the scene in
your code:
Kotlin
val sceneRoot = someLayoutElement as ViewGroup val viewHierarchy = someOtherLayoutElement as ViewGroup val scene: Scene = Scene(sceneRoot, viewHierarchy)
Java
Scene mScene; // Obtain the scene root element. sceneRoot = (ViewGroup) someLayoutElement; // Obtain the view hierarchy to add as a child of // the scene root when this scene is entered. viewHierarchy = (ViewGroup) someOtherLayoutElement; // Create a scene. mScene = new Scene(sceneRoot, mViewHierarchy);
Create scene actions
The framework lets you define custom scene actions that the system runs when entering or exiting a scene. In many cases, defining custom scene actions is unnecessary, since the framework animates the change between scenes automatically.
Scene actions are useful for handling these cases:
- To animate views that are not in the same hierarchy. You can animate views for the starting and ending scenes using exit and entry scene actions.
- To animate views that the transitions framework can't animate automatically,
such as
ListView
objects. For more information, see the section about limitations.
To provide custom scene actions, define your actions as
Runnable
objects and pass them to the
Scene.setExitAction()
or Scene.setEnterAction()
functions. The framework calls the setExitAction()
function on the starting
scene before running the transition animation and the setEnterAction()
function on the ending scene after running the transition animation.
Apply a transition
The transition framework represents the style of animation between scenes with a
Transition
object. You can instantiate a Transition
using built-in
subclasses, such as
AutoTransition
and
Fade
, or
define your own transition.
Then, you can run the
animation between scenes by passing your end Scene
and the Transition
to
TransitionManager.go()
.
The transition lifecycle is similar to the activity lifecycle, and it represents the transition states that the framework monitors between the start and the completion of an animation. At important lifecycle states, the framework invokes callback functions that you can implement to adjust your user interface at different phases of the transition.
Create a transition
The previous section shows how to create scenes that represent the state of
different view hierarchies. Once you define the starting and ending scenes you
want to change between, create a Transition
object that defines an animation.
The framework lets you either specify a built-in transition in a resource file
and inflate it in your code or create an instance of a built-in transition
directly in your code.
Class | Tag | Effect |
---|---|---|
AutoTransition |
<autoTransition/> |
Default transition. Fades out, moves and resizes, and fades in views, in that order. |
ChangeBounds |
<changeBounds/> |
Moves and resizes views. |
ChangeClipBounds |
<changeClipBounds/> |
Captures the View.getClipBounds() before and after the scene
change and animates those changes during the transition. |
ChangeImageTransform |
<changeImageTransform/> |
Captures the matrix of an ImageView before and after the scene
change and animates it during the transition. |
ChangeScroll |
<changeScroll/> |
Captures the scroll properties of targets before and after the scene change and animates any changes. |
ChangeTransform |
<changeTransform/> |
Captures scale and rotation of views before and after the scene change and animates those changes during the transition. |
Explode |
<explode/> |
Tracks changes to the visibility of target views in the start and end scenes and moves views in or out from the edges of the scene. |
Fade |
<fade/> |
fade_in fades in views.fade_out fades out views.fade_in_out (default) does a fade_out followed by
a fade_in .
|
Slide |
<slide/> |
Tracks changes to the visibility of target views in the start and end scenes and moves views in or out from one of the edges of the scene. |
Create a transition instance from a resource file
This technique lets you modify your transition definition without changing the code of your activity. This technique is also useful to separate complex transition definitions from your application code, as shown in the section about specifying multiple transitions.
To specify a built-in transition in a resource file, follow these steps:
- Add the
res/transition/
directory to your project. - Create a new XML resource file inside this directory.
- Add an XML node for one of the built-in transitions.
For example, the following resource file specifies the Fade
transition:
<fade xmlns:android="http://schemas.android.com/apk/res/android" />
The following code snippet shows how to inflate a Transition
instance inside
your activity from a resource file:
Kotlin
var fadeTransition: Transition = TransitionInflater.from(this) .inflateTransition(R.transition.fade_transition)
Java
Transition fadeTransition = TransitionInflater.from(this). inflateTransition(R.transition.fade_transition);
Create a transition instance in your code
This technique is useful for creating transition objects dynamically if you modify the user interface in your code and to create simple built-in transition instances with few or no parameters.
To create an instance of a built-in transition, invoke one of the public
constructors in the subclasses of the Transition
class. For example, the
following code snippet creates an instance of the Fade
transition:
Kotlin
var fadeTransition: Transition = Fade()
Java
Transition fadeTransition = new Fade();
Apply a transition
You typically apply a transition to change between different view hierarchies in response to an event, such as a user action. For example, consider a search app: when the user enters a search term and taps the search button, the app changes to a scene that represents the results layout while applying a transition that fades out the search button and fades in the search results.
To make a scene change while applying a transition in response to an event in
your activity, call the TransitionManager.go()
class function with the ending
scene and the transition instance to use for the animation, as shown in the
following snippet:
Kotlin
TransitionManager.go(endingScene, fadeTransition)
Java
TransitionManager.go(endingScene, fadeTransition);
The framework changes the view hierarchy inside the scene root with the view hierarchy from the ending scene while running the animation specified by the transition instance. The starting scene is the ending scene from the last transition. If there is no previous transition, the starting scene is determined automatically from the current state of the user interface.
If you don't specify a transition instance, the transition manager can apply an
automatic transition that does something reasonable for most situations. For
more information, see the API reference for the
TransitionManager
class.
Choose specific target views
The framework applies transitions to all views in the starting and ending scenes
by default. In some cases, you might only want to apply an animation to a subset
of views in a scene. The framework lets you select specific views you want to
animate. For example, the framework doesn't support animating changes to
ListView
objects, so don't try to animate them during a transition.
Each view that the transition animates is called a target. You can only select targets that are part of the view hierarchy associated with a scene.
To remove one or more views from the list of targets, call the
removeTarget()
method before starting the transition. To add only the views you specify to the
list of targets, call the
addTarget()
function. For more information, see the API reference for the
Transition
class.
Specify multiple transitions
To get the most impact from an animation, match it to the type of changes that occur between the scenes. For example, if you are removing some views and adding others between scenes, a fade out or fade in animation provides a noticeable indication that some views are no longer available. If you are moving views to different points on the screen, it's better to animate the movement so that users notice the new location of the views.
You don't have to choose only one animation, since the transitions framework lets you combine animation effects in a transition set that contains a group of individual built-in or custom transitions.
To define a transition set from a collection of transitions in XML, create a
resource file in the res/transitions/
directory and list the transitions under
the TransitionSet
element. For example, the following snippet shows how to
specify a transition set that has the same behavior as the AutoTransition
class:
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:transitionOrdering="sequential"> <fade android:fadingMode="fade_out" /> <changeBounds /> <fade android:fadingMode="fade_in" /> </transitionSet>
To inflate the transition set into a
TransitionSet
object in
your code, call the
TransitionInflater.from()
function in your activity. The TransitionSet
class extends from the
Transition
class, so you can use it with a transition manager just like any
other Transition
instance.
Apply a transition without scenes
Changing view hierarchies isn't the only way to modify your user interface. You can also make changes by adding, modifying, and removing child views within the current hierarchy.
For example, you can implement a search interaction with
a single layout. Start with the layout showing a search entry field and a search
icon. To change the user interface to show the results, remove the search button
when the user taps it by calling the
ViewGroup.removeView()
function and add the search results by calling
ViewGroup.addView()
function.
You can use this approach if the alternative is to have two hierarchies that are nearly identical. Rather than creating and maintain two separate layout files for a minor difference in the user interface, you can have one layout file containing a view hierarchy that you modify in code.
If you make changes within the current view hierarchy in this fashion, you don't need to create a scene. Instead, you can create and apply a transition between two states of a view hierarchy using a delayed transition. This feature of the transitions framework starts with the current view hierarchy state, records changes you make to its views, and applies a transition that animates the changes when the system redraws the user interface.
To create a delayed transition within a single view hierarchy, follow these steps:
- When the event that triggers the transition occurs, call the
TransitionManager.beginDelayedTransition()
function, providing the parent view of all the views you want to change and the transition to use. The framework stores the current state of the child views and their property values. - Make changes to the child views as required by your use case. The framework records the changes you make to the child views and their properties.
- When the system redraws the user interface according to your changes, the framework animates the changes between the original state and the new state.
The following example shows how to animate the addition of a text view to a view hierarchy using a delayed transition. The first snippet shows the layout definition file:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/mainLayout" android:layout_width="match_parent" android:layout_height="match_parent" > <EditText android:id="@+id/inputText" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> ... </androidx.constraintlayout.widget.ConstraintLayout>
The next snippet shows the code that animates the addition of the text view:
Kotlin
setContentView(R.layout.activity_main) val labelText = TextView(this).apply { text = "Label" id = R.id.text } val rootView: ViewGroup = findViewById(R.id.mainLayout) val mFade: Fade = Fade(Fade.IN) TransitionManager.beginDelayedTransition(rootView, mFade) rootView.addView(labelText)
Java
private TextView labelText; private Fade mFade; private ViewGroup rootView; ... // Load the layout. setContentView(R.layout.activity_main); ... // Create a new TextView and set some View properties. labelText = new TextView(this); labelText.setText("Label"); labelText.setId(R.id.text); // Get the root view and create a transition. rootView = (ViewGroup) findViewById(R.id.mainLayout); mFade = new Fade(Fade.IN); // Start recording changes to the view hierarchy. TransitionManager.beginDelayedTransition(rootView, mFade); // Add the new TextView to the view hierarchy. rootView.addView(labelText); // When the system redraws the screen to show this update, // the framework animates the addition as a fade in.
Define transition lifecycle callbacks
The transition lifecycle is similar to the activity lifecycle. It represents the
transition states that the framework monitors during the period between a call
to the TransitionManager.go()
function and the completion of
the animation. At important lifecycle states, the framework invokes callbacks
defined by the TransitionListener
interface.
Transition lifecycle callbacks are useful, for example, for copying a view
property value from the starting view hierarchy to the ending view hierarchy
during a scene change. You can't simply copy the value from its starting view to
the view in the ending view hierarchy, because the ending view hierarchy is not
inflated until the transition is complete. Instead, you need to store the value
in a variable and then copy it into the ending view hierarchy when the framework
has finished the transition. To be notified when the transition is completed,
implement the
TransitionListener.onTransitionEnd()
function in your activity.
For more information, see the API reference for the
TransitionListener
class.
Limitations
This section lists some known limitations of the transitions framework:
- Animations applied to a
SurfaceView
might not appear correctly.SurfaceView
instances are updated from a non-UI thread, so the updates might be out of sync with the animations of other views. - Some specific transition types might not produce the desired animation effect
when applied to a
TextureView
. - Classes that extend
AdapterView
, such asListView
, manage their child views in ways that are incompatible with the transitions framework. If you try to animate a view based onAdapterView
, the device display might stop responding. - If you try to resize a
TextView
with an animation, the text pops to a new location before the object is completely resized. To avoid this problem, don't animate the resizing of views that contain text.