I’ve been learning a whole lot about animation recently. Not sure why, but it's one of those things that’s relatively confusing and, as a result, I’ve mostly avoided. We’ve used frame animation for various things, and a little bit of view animation (shake an input, etc). The rest of it seemed like a mystery.

However, its really not that complicated. You just have to read and pay attention.

Recently, we needed something that seemed like it would be easy, but turned out to be fairly difficult. When showing an input in a form, we wanted the form’s parent to grow to fit it, but animated. Not just “pop” bigger/smaller. Layout animations seemed like a good option, but they only deal with the children. When something is shown, the parent will first resize, then run the animation, which is kind of pointless.

I will say this up front. Android is one of those frameworks where what you want to do is usually there somewhere, but may be buried very deep in the API. So far, I haven’t found anything to do a resize animation. If it exists, hopefully somebody will correct me. However…

Resize animation! The basic process is to define the duration, the expected height addition (ie guess how big your component will be). Views won’t report their own height until displayed, so its a little of a catch 22, so you have to guess (again, there may be a way to do this, but I don’t know it. Possibly add, set visibility to “gone”, and get height). Anyway, duration and height. Then loop over that duration, and add/remove the height value from the component. Basically, that’s it.

There are 2 basic ways to manage this. You can manually apply the operation. This is a little simpler, in the sense that you can use visibility VISIBLE/GONE, and different estimated heights. The downside is obvious. You have to manually code it. Alternatively, you can attach a listener to the ViewGroup to be notified when the hierarchy changes. This will trigger the resize animation automatically. The down side of that approach is you can’t use the visibility attribute, which means you need to actually add/remove the view from the parent. You also can’t define different estimated heights (unless you modify my code ;)

To make things better, my code also takes an Interpolator object to do smoother animation.

FYI, this code assumes a LinearLayout. If you’re using something else, you’ll need to adjust the code accordingly.


public class ViewGroupResizeAnimation
{
int guesstimatedSize;
protected int duration;
protected Handler handler;
protected ViewGroup.LayoutParams layoutParams;
private Interpolator interpolator;
private View parent;

guesstimatedSize is how big you think the added/removed object will be. duration is obvious. interpolator is an optional Interpolator object. Everything else is internal.


public ViewGroupResizeAnimation(View parent, int duration, int guesstimatedSize)
{
this.parent = parent;
layoutParams = parent.getLayoutParams();
this.duration = duration;
this.guesstimatedSize = guesstimatedSize;
handler = new Handler();
}

public void runAnimation(boolean adding)
{
handler.post(new ResizeRunnable(parent, adding));
}

Call runAnimation to run the animation. If adding the view, pass in ‘true’, otherwise ‘false’.

public Interpolator getInterpolator()
{
return interpolator;
}

public void setInterpolator(Interpolator interpolator)
{
this.interpolator = interpolator;
}

The following Runnable implements the animation.

protected class ResizeRunnable implements Runnable
{
private long startTime;
private long refreshTime = 50;
private View parent;
private boolean adding;
private int startHeight;

protected ResizeRunnable(View parent, boolean adding)
{
this.parent = parent;
this.adding = adding;
startTime = System.currentTimeMillis();
startHeight = parent.getHeight();

parent.setLayoutParams(new LinearLayout.LayoutParams(layoutParams.width, parent.getHeight()));

The height layoutparam needs to be set to a hard value. Normally, or should I say ‘often’, a value like WRAP_CONTENT is used. We hold on to the original layout params and reset them when the animation is done.

}

public void run()
{
final long elapsed = System.currentTimeMillis() - startTime;
if(elapsed >= duration)
{
parent.setLayoutParams(layoutParams);
return;//Done

When we’re done, reset the original layout params (this would be a mess if more than one animation was running at the same time, BTW. I leave that to you).

}

float timelinePoint = (float) elapsed / (float) duration;
if(interpolator != null)
timelinePoint = interpolator.getInterpolation(timelinePoint);

final int newHeight = adding ? (startHeight + (int) (timelinePoint * guesstimatedSize)) : (startHeight - (int) (timelinePoint * guesstimatedSize));
parent.setLayoutParams(new LinearLayout.LayoutParams(layoutParams.width, newHeight));
handler.postDelayed(this, refreshTime);

Calculate new height, with the optional interpolator. Reset for next run. Rinse and repeat.

}
}
}

You can use this manually, or add a listener to the ViewGroup. See the following:


public class ViewGroupResizeHierarchyChangeListener extends ViewGroupResizeAnimation implements ViewGroup.OnHierarchyChangeListener
{
public ViewGroupResizeHierarchyChangeListener(int guesstimatedSize, int duration, View parent)
{
super(parent, duration, guesstimatedSize);
}

public void onChildViewAdded(View parent, View child)
{
runAnimation(true);
}

public void onChildViewRemoved(View parent, View child)
{
runAnimation(false);
}
}

Some example code to add it:

formBlock = (ViewGroup)findViewById(R.id.formBlock);

final ViewGroupResizeHierarchyChangeListener resizeAnimation = new ViewGroupResizeHierarchyChangeListener(50, 1200, formBlock);
resizeAnimation.setInterpolator(new AccelerateInterpolator());
formBlock.setOnHierarchyChangeListener(resizeAnimation);

-Kevin

*UPDATE*
I figured out how to do this with multiple additions, and how to measure the Views rather than guess the size. Once I figure out githup, I’ll post.

*UPDATE - 2*
Link to github page:
https://github.com/kpgalligan/AnimationDemo

Slides from my animation presentation:
https://docs.google.com/present/view?id=djqv5kb_187c62jvbf7&interval=10