Animation / Transition

Transition Animations – Part 4

Previously in this series we’ve looked at a few aspects of Transition animations, but all of the examples have comprised of the views being animated having a single, common parent. In other words, they are all children of the same parent layout. However, Android layouts are rarely as simple as the example layouts that we’ve been looking at. In this concluding article we’ll have a look in to why the ChangeBounds transition has this limitation, and some potential work arounds.

Styling AndroidI have warned previously that the views we animate using ChangeBounds must share a common parent layout, so let’s have a look at why this is. If we look back to part 2 of this series we used a layout containing 3 children, and had to be a little hacky creative with the layout by setting the widths of the second and third views as DP values in order to get them to display as we expect. To do this properly, we would normally use a horizontal LinearLayout and use layout_weight on the children to divide up the space between the children:





	

	

		

		

	


This would be a much better way of ensuring the size gets split evenly between @id/item2 and @id/item3. If we also create two other layouts each with a different selection in the top position much as we did in part 2 of this series, except that we’ll adapt the layouts to match this example. I won’t include them here, but you can view them in the source at /res/layout/part4_2.xml and /res/layout/part4_3.xml.

Next we can create out Scenes and set up the transitions in exactly the same way that we’ve done before:

private ViewGroup mContainer;
private TransitionManager mTxManager;

private Scene mScene1;
private Scene mScene2;
private Scene mScene3;

@Override
public View onCreateView(LayoutInflater inflater, 
		ViewGroup container, 
		Bundle savedInstanceState) {
	View view = inflater.inflate(R.layout.fragment_part2, 
		container, false);
	mContainer = (ViewGroup)view.findViewById(R.id.container);
	mScene1 = Scene.getSceneForLayout(mContainer, 
		R.layout.part4_1, getActivity());
	mScene1.setEnterAction(new Runnable() {
		@Override
		public void run() {
			mScene1.getSceneRoot()
				.findViewById(R.id.item2)
				.setOnClickListener(Part4.this);
			mScene1.getSceneRoot()
				.findViewById(R.id.item3)
				.setOnClickListener(Part4.this);
		}
	});
	mScene2 = Scene.getSceneForLayout(mContainer, 
		R.layout.part4_2, getActivity());
	mScene2.setEnterAction(new Runnable() {
		@Override
		public void run() {
			mScene2.getSceneRoot()
				.findViewById(R.id.item1)
				.setOnClickListener(Part4.this);
			mScene2.getSceneRoot()
				.findViewById(R.id.item3)
				.setOnClickListener(Part4.this);
		}
	});
	mScene3 = Scene.getSceneForLayout(mContainer, 
		R.layout.part4_3, getActivity());
	mScene3.setEnterAction(new Runnable() {
		@Override
		public void run() {
			mScene3.getSceneRoot()
				.findViewById(R.id.item1)
				.setOnClickListener(Part4.this);
			mScene3.getSceneRoot()
				.findViewById(R.id.item2)
				.setOnClickListener(Part4.this);
		}
	});
	mTxManager = new TransitionManager();
	ChangeBounds transition = new ChangeBounds();
	mTxManager.setTransition(mScene1, mScene2, transition);
	mTxManager.setTransition(mScene1, mScene3, transition);
	mTxManager.setTransition(mScene2, mScene1, transition);
	mTxManager.setTransition(mScene2, mScene3, transition);
	mTxManager.setTransition(mScene3, mScene1, transition);
	mTxManager.setTransition(mScene3, mScene2, transition);
	mScene1.enter();
	return view;
}

@Override
public void onClick(View v) {
	switch (v.getId()) {
		case R.id.item1:
			mTxManager.transitionTo(mScene1);
			break;
		case R.id.item2:
			mTxManager.transitionTo(mScene2);
			break;
		case R.id.item3:
			mTxManager.transitionTo(mScene3);
			break;
	}
}

There’s absolutely nothing here that we haven’t covered already. However, if we run this the behaviour is really quite odd:

What is actually happening here is that the start positions for the animations for each view are being calculated from within it’s own parent layout, yet the transition actually does not start from there. If you keep an eye on @id/item2 in the second half of the clip, it animates as it should, and this is because it remains within the same parent layout (the horizontal LinearLayout) during the transition.

So why the problem when the parent changes? When the parent remains the same, animating the child views is quite easy because they can be laid out relative to the parent, although suppressLayout(true) is called upon the parent to stop it from preforming a layout pass for each frame of the animation (which would kill the frame-rate). This is not possible when the parent changes, so the same approach cannot be used.

That said, there is something of a work around because ChangeBounds contains a method which enables is to support changing parents: setReparent():

mTxManager = new TransitionManager();
ChangeBounds transition = new ChangeBounds();
transition.setReparent(true);
mTxManager.setTransition(mScene1, mScene2, transition);
mTxManager.setTransition(mScene1, mScene3, transition);
mTxManager.setTransition(mScene2, mScene1, transition);
mTxManager.setTransition(mScene2, mScene3, transition);
mTxManager.setTransition(mScene3, mScene1, transition);
mTxManager.setTransition(mScene3, mScene2, transition);
mScene1.enter();

If we try this, we get something much closer to what we achieved before:

What’s actually going on here is really quite different, as a ViewOverlay (introduced in Jelly Bean API 18) is used. We’ve yet to cover ViewOverlay on Styling Android, but it is a really useful mechanism for animating objects which, possibly only temporarily, do not need to handle touch events, or require expensive layout calculations. Thus ViewOverlay is perfectly suited to the task in hand.

However, the more observant will have noticed that while the translation of the views is now correct, there is no animation of the changing size of the views. A quick look at the source of ChangeBounds shows why: It hasn’t been implemented. Actually changing the sizes is a lot harder than it sounds. While it is easy enough to change the size of the view within the ViewOverlay, it only exists as a bitmap within the ViewOverlay and not the View object that exists in the layout, so changing the size will actually distort the bitmap – in our case the text would stretch rather strangely. This isn’t an issue when animating within a single parent because the view still exists as a View, so will be redrawn for each frame.

Another important point to remember when using setReparent(true) is that as well as matching the ids of the Views in order for the TransitionManager to link the views in separate Scenes, you must also match the ids of the parent layouts so that TransitionManager can identify the source and destination parents. In the example, the two LinearLayouts have ids set which permit this. If you forget to specify ids for your parents, you will see some strange behaviour!

The Transitions API is very new, and quite minimalistic at present, and there is clearly much work still in progress. However, it is a really easy API to get some useful results with quite quickly, and it’s a fun API to play around with.

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.

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.