Animation / Transition

Transition Animations – Part 1

As I write, it is less than two weeks since Google announced Android 4.4 KitKat. There are quite a few new APIs in this version of Android, and in this series we’ll have a look at the all-new Transition Animations.

Styling AndroidWe’ve looked at various types of animation in the past on Styling Android, so what does the new transitions API offer us? Basically it allows us to animate Views in much the same way that the existing frameworks allow us to do, the main difference with the transitions API is that it does an awful lot of the work for us.

Let’s demonstrate this with an example. Take a look at the following layout:



	

	


Suppose we wanted to create an animation to switch the position of the two TextView objects whenever one of them was tapped. We could do that quite easily by using a property animator to change vertical position of each of the TextView objects by calling its translateY method. This would require a bit of calculation work to determine to end values that each TextView should be translated to in order to move them to the right positions. At the end of the animation we may want to switch the positions of the two items within the parent LinearLayout and remove the translations.

With the transitions API that is a lot simpler!

Let’s first look at the logic we’d need to perform to swap the positions of the two view within the parent LinearLayout:

private ViewGroup mLayout1;

@Override
public View onCreateView(
	LayoutInflater inflater, 
	ViewGroup container, 
	Bundle savedInstanceState) {
	View view = inflater.inflate(R.layout.fragment_part1, 
		container, false);
	mLayout1 = (ViewGroup) view.findViewById(R.id.layout_1);
	mLayout1.findViewById(R.id.item_1a)
		.setOnClickListener(this);
	mLayout1.findViewById(R.id.item_1b)
		.setOnClickListener(this);
	return view;
}

@Override
public void onClick(View v) {
	int selected = mLayout1.indexOfChild(v);
	mLayout1.removeView(v);
	mLayout1.addView(v, 
		selected == 0 ? mLayout1.getChildCount() : 0);
}

There is nothing difficult here, we simply remove one child from the parent layout and re-insert it at a different location. To animate this using the transitions animation requires us to add two lines of code. Let me repeat that: TWO LINES OF CODE!!

Actually, it would have been ONE line of code except I split it so that the source view didn’t scroll.

@Override
public void onClick(View v) {
	int selected = mLayout1.indexOfChild(v);
	TransitionManager.beginDelayedTransition(mLayout1, 
		new ChangeBounds());
	mLayout1.removeView(v);
	mLayout1.addView(v, 
		selected == 0 ? mLayout1.getChildCount() : 0);
}

If we run this we’ll see the following animation occurring:

So what’s going on here? Let’s understand how this works by analysing this little bit of code. There are two classes that we’re unfamiliar with: TransitionManager and ChangeBounds. ChangeBounds determines the type of animation that we wish to perform, and this is responsible for creating an AnimatorSet to perform the required animations. In this case we wish to scale and translate the two items so that they swap positions, and the default ChangeBounds transition does that for us by tracking the start and end states of the views within the view hierarchy based upon their IDs. The call to beginDelayedTransition initialises the TransitionManager to perform the transition when the view hierarchy changes. It achieves this by first capturing the current values that the Transition will be interested in (i.e. the location and dimensions in our case) and then, when the view hierarchy changes, it captures a second set of values, and then calls the Transition to create an AnimatorSet comprising the animations necessary to transition between the two states. Finally this AnimatorSet is run.

The way to think of this is that we are creating two distinct layout states. The initial layout which we inflate from the layout resource, then a second when we programatically manipulate the view hierarchy. The TransitionManager in conjunction with the Transition will automagically animate the views in this hierarchy between these two fixed states and it matches the two states of each individual view because of the view ID.

It is worth pointing out that this currently works really well when the Views being animated all share a common parent layout. There are some work arounds and caveats when there is no common parent which we’ll cover later on in the series.

Let’s look at another quick example. In this case we have three objects labelled ‘Item 2a‘, ‘Item 2b‘, and ‘Item 2c‘:



	

		

	

	

		

	

	

		

	

There are three positions, the first (occupied initially by Item 2a) represents the current selection, and the other two positions represent items which can be selected. The selected position is full width, and the unselected positions are each half the screen width. When one of the unselected items is tapped, it will move to the selected position, and the currently selected item will move to one of the unselected positions. We wish to keep the unselected items in ascending order, so Item 2a will always be in the left hand position when unselected, Item 2c will always be in the right position, but Item 2b could be in either position depending on which other item it shares the unselected row with. Effectively we have three states:

  1. Item 2a selected, Item 2b in left position, Item 2c in right position.
  2. Item 2b selected, Item 2a in left position, Item 2c in right position.
  3. Item 2c selected, Item 2a in left position, Item 2b in right position.

We encapsulate this logic in the following code:

public void onClick(View v) {
	int selected = mLayout2.indexOfChild(v);
	if(selected > 0) {
		View cur = mLayout2.getChildAt(0);
		int currentId = cur.getId();
		int selectedId = v.getId();
		TransitionManager.beginDelayedTransition(mLayout2, 
			new ChangeBounds());
		mLayout2.removeView(v);
		mLayout2.addView(v, 0);
		GridLayout.LayoutParams params = 
			(GridLayout.LayoutParams)cur.getLayoutParams();
		cur.setLayoutParams(v.getLayoutParams());
		v.setLayoutParams(params);
		if(currentId == R.id.item_2c || 
			(currentId == R.id.item_2b && 
				selectedId == R.id.item_2c)) {
			mLayout2.removeView(cur;
			mLayout2.addView(cur);
		}
	}
}

We use an identical call to the transitions animation framework as we did in the previous example, and we get the following behaviour:

When we swap Item 2a and Item 2b, or Item 2b and Item 2c then we simply swap the two items over. But when we swap Item 2a and Item 2c the position of Item 2b changes automatically, as well!

What we have been referring to as layout states are actually given the name “Scenes” in the transition animation framework, and in the next article we’ll look at how we can actually define these scenes directly from layout resources.

Incidentally, Stéphane Guérin has created a back-port of the transitions APIs which work on 4.0 and later. I have yet to try it but if you want to give it a go you can find it here.

The source code for this article is available here.

© 2013 – 2014, Mark Allison. All rights reserved.

Copyright © 2013 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

1 Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.