fbpx

Blogs from the Ranch

< Back to Our Blog

Building Animations with the android.transition Framework, Part 1

Avatar

Bolot Kerimbaev

With KitKat, Google brought a number of welcome additions to Android. Today, we’ll focus on one of them, the android.transition framework.

Over the years, Android has gradually improved the animation tools available to developers. Property animations introduced in Honeycomb provide a fantastic API for creating rich and complex animations. In KitKat, android.transition builds on top of that and makes it possible to define animations in a more declarative manner.

Scenes and transitions

It all starts with the notions of the scene and transitions. A scene defines a given state of the UI, whereas a transition defines the change from one scene to another.

One way to define a scene is by loading it from a layout file:

scene = Scene.getSceneForLayout(container, R.layout.example, context);

container is a ViewGroup that holds the views in the scene. In a fragment it would be the parameter passed into the onCreateView() method. The simplest way to perform a transition is to let the TransitionManager perform a default one:

TransitionManager.go(scene);

If a transition is not specified, the TransitionManager will use the AutoTransition, which we will talk about later. It is also possible to create scenes using the views that have already been inflated:

View view = inflater.inflate(R.layout.example, container, false);
Scene scene = new Scene(container, (ViewGroup)view);

Putting android.transition into practice

Let’s look at this simple example in more detail. Start by downloading the code from our AndroidTransitionExample. Since this is a “finished” example, you can use git checkout to go to the specific points in this project’s evolution (this is explained in more detail below).

We begin by creating a simple single-fragment project. To make it easier to keep track of things, we named the TransitionFragment’s layout fragment_transition_scene_1.xml and just added a Button below the TextView.

fragment_transition_scene_1.xml

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scene"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/goButton"
        android:text="@string/button_go"
        android:layout_below="@id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

You can probably already guess that we’ll create another layout, and you’re right. fragment_transition_scene_2.xml is nearly identical, but we move the goButton to the bottom of the parent:

    ...
    <Button
        android:id="@+id/goButton"
        android:text="@string/button_go"
        <del>android:layout_below="@id/textView"</del>
        <ins>android:layout_alignParentBottom="true"</ins>
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    ...

These are the screenshots of the two layouts:

transition_01
transition_02

To see the effect of the transition, create the scene from the second layout file and perform the transition when the goButton is clicked. Let’s update the TransitionFragment.onCreateView() method.

@Override
public View onCreateView(LayoutInflater inflater,
        ViewGroup container, Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.fragment_transition_scene_1,
            container, false);
    final Scene scene = Scene.getSceneForLayout(container,
            R.layout.fragment_transition_scene_2, getActivity());
    Button goButton = (Button)rootView.findViewById(R.id.goButton);
    goButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            TransitionManager.go(scene);
        }
    });
    return rootView;
}

This is what happens when the button is clicked.

transition_03

How does this work? Views that have the same id will be assumed to be the same view and will transition via changing bounds. In other words, the position and size of the views will be animated when going from one scene to the next. Note that the RelativeLayouts in the two layout files also have to have the same id.

Following along with git

If you are interested in trying out the example project, you can take advantage of git. Clone the project from AndroidTransitionExample. You could use a GUI git client to list the project history and to switch to a specific commit.

Alternatively, you could just use the command line. Open the terminal, cd into the project folder and run the following command:

git log --oneline

You will get the listing of the commits to this project, which will look like this:

cc40873 load the transition manager from the XML file
c2a25d4 inflate the transition from the XML file
9871bfa add the return transition
1de57f0 use AnticipateOvershootInterpolator
fbcc465 slow motion transitions
6ea37f7 extract method goToScene
34e0f8f restore transition by adding LinearLayout with id
2b000b3 example of change bounds not working when changing hierarchy
6b4629c example of transitioning from Button to ImageView
092ebe0 added layout_weight to button
1e7c5be modified layout for scene 2
d94b907 Android Studio updated IML files
24f9a74 Create README.md
0667c36 simple transition
4265f50 factor out TransitionFragment
280f123 initial commit

This point in the post corresponds to the “simple transition” commit. To set the project to this state, you can run this in the command line:

git checkout 0667c36

You can jump to any specific commit by executing git checkout <commit>. The inset text below gives instructions for following along.

Modifying the layout

Let’s modify the second layout a bit. First, we’ll use LinearLayout instead of RelativeLayout. Second, we’ll introduce a new view that’s not present in the first layout. Finally, we’ll rearrange the views a bit.

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scene"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal">

    <TextView
        android:id="@+id/textView"
        android:text="@string/hello_world"
        android:layout_weight="1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:text="@string/hello_world"
        android:layout_weight="1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/goButton"
        android:text="@string/button_go"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

We are now at commit “1e7c5be modified layout for scene 2”. To switch to it, run
git checkout 1e7c5be in the command line.

The transition still works, but now we see how a view that didn’t exist in the first scene comes on the screen: it fades in after the button and text view move.

Let’s take a closer look at the AutoTransition. Turns out, it’s just a subclass of TransitionSet that defines itself as a sequence of fading out, changing bounds, then fading in.

Note that the second transition in AutoTransition is changing bounds. So far, we’ve only seen the button and text view change position. What would happen if we changed the dimensions of our views?

Add the layout_weight attribute to the button to see the effect:

    <Button
        android:id="@+id/goButton"
        android:text="@string/button_go"
        android:layout_weight="1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

Now the button changes both the position and dimensions during the transition.

Jump to “added layout_weight to button”:
git checkout 092ebe0

Let’s now make one more change, changing a view from a Button to an ImageView between scenes:

    <ImageView
        android:id="@+id/goButton"
        <del>android:text="@string/button_go"</del>
        <del>android:layout_weight="1"</del>
        android:src="@drawable/bnr_hat"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

Jump to “example of transitioning from Button to ImageView”:
git checkout 6b4629c

The transition from Button to ImageView:

If you watch the video carefully, you’ll notice that first the button gets replaced by a cropped image view, which then gradually moves to its final position while simultaneously changing its size.

If we change nesting of the views—for example, by wrapping the goButton inside another LinearLayout—the change bounds transition doesn’t happen anymore. The transition manager expects the view with the same id to appear at the same level in the scene layout as before.

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <ImageView
            android:src="@drawable/bnr_hat"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <Button
            android:id="@+id/goButton"
            android:text="@string/button_go"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>

Jump to “example of change bounds not working when changing hierarchy”:
git checkout 2b000b3

It still doesn’t work if we wrap the button in a LinearLayout in the first scene. To get it working again, we need to give the LinearLayouts the same id.

Here are the two versions:

Jump to “restore transition by adding LinearLayout with id”:
git checkout 34e0f8f

In the next post, we’ll continue on with our exploration and learn how to control transitions and how to load them from XML files.

Avatar

Bolot Kerimbaev

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project