Animate layout changes using a transition

Try the Compose way
Jetpack Compose is the recommended UI toolkit for Android. Learn how to use Animations in Compose.

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:

  1. Create a Scene object for the starting and ending layouts. However, the starting layout's scene is often determined automatically from the current layout.
  2. Create a Transition object to define what type of animation you want.
  3. 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.

Figure 1. Basic illustration of how the transition framework creates an 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:

res/layout/activity_main.xml

<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:

res/layout/a_scene.xml

<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:

res/layout/another_scene.xml

<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.

Table 1. Built-in transition types.

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:

res/transition/fade_transition.xml

<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:

  1. 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.
  2. 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.
  3. 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:

res/layout/activity_main.xml

<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:

MainActivity

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 as ListView, manage their child views in ways that are incompatible with the transitions framework. If you try to animate a view based on AdapterView, 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.