Android's transition framework allows you to animate all kinds of motion in your UI by simply providing the starting layout and the ending layout. You can select what type of animation you want (such to fade the views in/out or change the view sizes) and the transition framework figures out how to animate from the starting layout to the ending layout.
The transition framework includes the following features:
- Group-level animations: Apply one or more animation effects to all of 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 both the starting layout and the ending layout. 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.
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. For example, this feature is useful for cleaning up view settings after you transition to a scene.
Note: The framework can animate changes in a single view hierarchy without using scenes, as described in Apply a transition without scenes. However, understanding scenes is essential to work with transitions.
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, you have to
recreate the scene. The framework creates the scene from the entire view hierarchy in the
file; you can not 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
instance and 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 you 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 layout.
- A relative layout for the first scene with two text fields.
- A relative layout 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 layout for the scene root. The layout for the first scene is included in the main layout file. This allows the app to display it as part of the initial user interface and also to load it into a scene, since the framework can load only a whole layout file into a scene.
The layout for the first scene is defined as follows:
res/layout/a_scene.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/scene_container" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/text_view1" android:text="Text Line 1" /> <TextView android:id="@+id/text_view2" android:text="Text Line 2" /> </RelativeLayout>
The layout for the second scene contains the same two text fields (with the same IDs) placed in a different order and is defined as follows:
res/layout/another_scene.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/scene_container" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/text_view2" android:text="Text Line 2" /> <TextView android:id="@+id/text_view1" android:text="Text Line 1" /> </RelativeLayout>
Generate scenes from layouts
After you create definitions for the two relative layouts, you can obtain a scene for each of them. This enables you to later 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 you 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
have 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, mViewHierarchy)
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 enables you to define custom scene actions that the system runs when entering or exiting a scene. In many cases, defining custom scene actions is not necessary, since the framework animates the change between scenes automatically.
Scene actions are useful for handling these cases:
- Animate views that are not in the same hierarchy. You can animate views for both the starting and ending scenes using exit and entry scene actions.
- Animate views that the transitions framework cannot animate automatically, such as
ListView
objects. For more information, see 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.
Note: Do not use scene actions to pass data between views in the starting and ending scenes. For more information, see Define transition lifecycle callbacks.
Apply a transition
The transition framework represents the style of animation between scenes with a
Transition
object. You can instantiate a
Transition
using several 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 make adjustments to your user interface at different phases of the transition.
Create a transition
In the previous section, you learned how to create scenes that represent the state of
different view hierarchies. Once you have defined the starting scene and the ending scene you
want to change between, you need to create a Transition
object
that defines an animation. The framework enables you to specify a built-in transition in a
resource file and inflate it in your code or to create an instance of a built-in transition
directly in your code.
Table 1. Built-in transition types.
Class | Tag | Attributes | Effect |
---|---|---|---|
AutoTransition |
<autoTransition/> | - | Default transition. Fade out, move and resize, and fade in views, in that order. |
Fade |
<fade/> | android:fadingMode="[fade_in | |
fade_in fades in viewsfade_out fades out viewsfade_in_out (default) does a fade_out followed by a fade_in .
|
ChangeBounds |
<changeBounds/> | - | Moves and resizes views. |
Create a transition instance from a resource file
This technique enables you to modify your transition definition without having to change the code of your activity. This technique is also useful to separate complex transition definitions from your application code, as shown in Specify 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 clicks the search button, the app changes to the 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 some 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 was no previous transition, the starting scene is determined automatically from the current state of the user interface.
If you do not 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 may only want to apply an animation to a subset of views in a
scene. For example, the framework does not support animating changes to
ListView
objects, so you should not try to animate them during a
transition. The framework enables you to select specific views you want to animate.
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, you should 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/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, a better choice would be to animate the movement so that users notice the new location of the views.
You do not have to choose only one animation, since the transitions framework enables you to 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 is not 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 just 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 clicks it by calling the ViewGroup.removeView()
function, and add the search results by
calling ViewGroup.addView()
function.
You may want to use this approach if the alternative is to have two hierarchies that are nearly identical. Rather than having to create 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 do not 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:
res/layout/activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 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" /> ... </RelativeLayout>
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 will animate 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 time 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
cannot 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 completed.
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 get notified when the transition
is completed, you can 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
may not appear correctly.SurfaceView
instances are updated from a non-UI thread, so the updates may be out of sync with the animations of other views. - Some specific transition types may 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 may hang. - If you try to resize a
TextView
with an animation, the text will pop to a new location before the object has completely resized. To avoid this problem, do not animate the resizing of views that contain text.