CHET HAASE: Hi.
I'm Chet Haase from the Android Development Team.
I work on graphics and animations.
And today, I wanted to talk about animations.
Specifically, I want to talk about activity animations.
So typically, when an activity launches, or even a
subactivity, you get standard animations
from the Window Manager.
And you can choose the kind of animation that you want.
You can load and resource for this.
And as of Jelly Bean, we also allow you to specify thumbnail
that you can scale from, and potentially cross fade from.
But that's about it.
There's not a lot of customization possible with
window animations.
What if you want to do something really custom?
So we're going to see how to do that today.
So if we run this little demo called Activity Animations,
you see as we have the main activity with these gray scale
thumbnails, and we click on one of those thumbnails, and
we launch into the subactivity, it shows a
detailed view of the picture, and then the text that goes
along with the picture.
So again, click on the thumbnail.
It colorizes the picture.
It zooms up into the full scale view.
And we see the text slide out from underneath the picture.
Kind of nice.
We can slow down the animation so we can see it a little bit
more in detail.
And you see there's also a drop shadow in there, giving
you the effect of the picture really popping out toward the
viewer, and then popping back when the activity goes back to
the main activity.
So how do we do that?
So the main idea behind this is that we disable window
animations, because we don't actually want them to do
anything that they're used to doing.
Instead, we launch the subactivity immediately, or we
launch the activity that we're going back to immediately.
And then we run custom animations, using the normal
property animation system.
And once we're into the activity, with property
animations, you can do anything you want.
So again, we disable window animations.
We get into the activity.
And then in the activity, we can do whatever we want.
And the trick with handling these interactivity things,
like launching from a thumbnail from one into to a
full scale view of the other, is to pass the information in
a bundle, so that we have the appropriate information that
we can key off of to get the effect that we're looking for.
So first of all, let's take a look at the application
manifest here.
And we can see that we've set up a style of our subactivity
of transparent.
And this is something that I've defined in
the styles.xml file.
So you have the seam transparent, where we say the
window background is transparent.
This means that when the subactivity launches, it's not
actually going to paint a default color over the
activity that we're launching from, which is the
effect that we want.
We want the activity launch immediately, but we don't want
that obvious to the user by seeing some opaque color drawn
in front of the previous activity.
Instead, we want it to launch transparently,
and then do its thing.
Now one important caveat about transparent activities is,
this means that the window manager in the system is going
to be drawing the other activity behind there.
Every time the window is drawn on the screen, it's going to
have to draw the activity behind it.
This could potentially be a performance issue, so it's not
something you want to do in every situation.
And if it's a really complex set of views that you're
dealing with there, maybe it's not something you want to do.
But for this sort of transient behavior of going from one
activity to the other, it's a pretty neat effect.
But you just need to be able to determine the correct
trade-off for your own application.
Anyway, let's take a look at the actual code.
So there's a few things going on here.
One is that we have this main activity called Activity
Animations.
We set up a ColorMatrixColorfilter,
arguably the longest class name in the Android framework.
And this is to create the grayscale effect on those
thumbnails.
So we're loading these full scale images.
And we're creating thumbnails out of them.
And then we're setting a filter, which will make them
appear as grayscale in the thumbnail view.
And then we're going to launch from the thumbnail into the
colorized view.
So we're going to set up grayscale filters on those.
We're going to set up our layout.
We're going to load in the pictures and
add them to the layout.
Nothing really amazing going on here.
And then we're going to set a click listener so that we know
whenever we click on a particular thumbnail, then our
listener is going to kick in, and will do
the appropriate thing.
So that happens down here, OnClickListener.
So here, we're going to package up the information
that we need to pass to the subactivities so that it knows
where to launch its picture from, where to zoom in from,
and what picture to zoom in from so that it can do the
correct animation into the full view of that picture.
So here, we figure out where the thumbnail is on the
screen, how large it is, and also the orientation.
I'll talk about this a little bit later.
And then the resource ID of the picture.
So we're going to give all the information that the
subactivity needs to know where and what size to zoom in
from, as well as what picture to actually zoom into.
And then, the text description as well that's associated with
the picture.
We put all of this into the extras.
So we're going to launch this subactivity with this extra
information.
And then the subactivity can extract it.
Now here's an important point.
If you're doing this technique, you want to
override pending transitions after
you've started the activity.
And this will tell the window manager to back off and not
run the standard window animations, on top of what
you're already trying to do in your custom activity
animations.
OK.
So that's all.
Not much going in the main activity.
All the interesting bit is in the subactivity.
So let's take a look at that.
In PictureDetailsActivity file, we have various fields
that we need.
We have a couple custom interpolators that we've
defined there for the animations.
And then we get down here into OnCreate.
This is going to be called when the activity launches.
And at that point, we can extract the bundle, which is
the information that we packaged up about the
thumbnail we're actually launching from.
So from that, we get all the information about location,
size, the picture, the text description, all that stuff.
And then we can actually run our animations.
So we come down here.
This is an important point here.
If the savedInstanceState is null, then we want to go ahead
and run the information.
If not, then we're actually launching this subactivity
from something like returning to it from the launch, or
maybe somebody launches your activity from [INAUDIBLE].
We don't want to do the custom animation in that case.
We just want to launch the activity with the standard
information.
But if the savedInstanceState is null, then we're going to
launch from the main activity.
So let's go ahead and run the animation.
OK.
So here's a technique that I use in a lot of my animation
demos, which is an OnPreDrawListener.
This gets called right before we're about to draw things,
which is an excellent time to track information that's going
to be set up prior to that first draw.
In particular, we want to know where everything's going to
be, like our main picture view.
Everything's going to be set up to be
rendered in the main activity.
And this is the time for us to kick in and say, OK, now that
we know where everything is going to be, now we can run
our animation to animate from where it was in the previous
activity into this subactivity.
So we handle the OnPreDraw.
We remove the Listener, because we don't want to
listen to it after this.
That would be silly.
Then we get the location on the screen of the picture that
we're going to.
We also get the size.
And then we create scale factors.
And this basically, the technique that we're going to
use in the animation is we're going to prescale down to the
size of the thumbnail that we're coming from, , and then
we're going to scale up to one.
Right.
So we have the full size of the image.
We're going to prescale it down to some fractional size,
and then scale it up to one until it
reaches the full size.
And then, we run the EnterAnimation.
Here's another important point.
If you override OnPreDrawListener, listener
return true, by default, IDEs like to have you return
faults, which is silly.
Because that basically disables
rendering on this frame.
We wanted to go ahead and draw the first
frame, so return true.
It's the right thing to do.
Then we run the EntireAnimation.
We calculate a duration based on whether we've enabled that
scale animation property in the menu.
No big deal there.
We set up some initial values.
So, the pivot point that we're scaling around.
The scale factor that we're going to scale down to before
we start the animation.
And then, the translation x and y.
So we're going to scale from a particular size and from a
particular location.
We also tell the TextView, which is going to hold the
text description for the picture, to alpha of 0.
So it's going to be initially transparent, then we're going
to fade it in.
Here's the main animation.
So for the ImageView.
We're going to run a ViewPropertyAnimator at that
duration we calculated earlier.
We're going to scale x and y into one.
So it's going to be the full size of the
picture when it's done.
Translation x and translation y are going to go to 0 from
that initial location they had from the thumbnail that we're
launching from.
We're going to have a Decelerate Interpolater, which
means it's going to start fast, and then slow down and
lock into place.
And then when the animation ends, we're going to run the
following runable, which is going to go ahead and start
the TextView animation.
So we're going to set the translation y to be basically
up underneath the picture.
And then we're going to animate it down to
translation y of 0.
And we're going to fade it in at the same time, again, using
a ViewPropertyAnimator on the TextView itself.
In parallel, we're going to fade in a black background.
So if we see the demo again, you can see that we're fading
in the background to this opaque black background behind
the picture once we're finally done.
So that's going to happen in parallel.
So we're going to animate the alpha value of this background
that we've already defined.
We're also going to run a colorizer animation.
Now this is not a standard property.
Instead, it's a property that I've defined on this class
called saturation.
So I just created a property setter setSaturation, which
we'll see down below.
And we're going to run that from 0 to 1.
And as that runs, we're going to change the color filter,
which is going to color as that image from the initial
grayscale up into the full color version of the picture.
And we're also going to animate a drop shadow.
We'll see how the rendering of this works in a minute.
But it's enough now to know that we're going to animate
the shadow depth from 0, which is basically the shadow's
going to be exactly mapped behind the image to some
offset value, which we'll then use to calculate alpha, as
well as offsets for the shadow.
And the idea here is that we want to start with the
thumbnail starting on the plane that
we're projecting onto.
And then as the thumbnail pops out, we want the shadow to
help give the illusion that the image is actually popping
out toward the user.
And then we start the animation.
So we also have a runExitAnimation.
It's basically the same animations in reverse, so I
won't go through the code there.
But there's some other interesting
bits to look at here.
One is the saturation.
This is the property setter that I defined that's going to
be called by the Object Animator.
We're going to set the saturation
on the color matrix.
And then we're going to define a new filter and
set it on the drawable.
This is an important point.
We can't animate the filter itself, because it's not a
mutable object.
Instead, we need to create a new filter with this
saturation value, and then set that on the drawable.
And that's how we animate the colorization of
the drawable itself.
Two more points here is onBackPressed, we override
this, so that in the Back button is pressed, we'd like
to go back into the main activity.
But we want to animate that.
And to do that, we need to run the animation and then finish
the activity.
By default, onBackPress simply runs Finish immediately.
What we want to do is run our animation first, which is
going to happen in that method I skipped before.
runExitAnimation.
And then when it's done, this runnable's going to execute,
and that's going to call Finish.
We also override Finish for the sole reason of overriding
pending transitions.
Same as before.
I don't want window animations to run when I'm returning the
main activity, because I have my own custom
animation to run.
Only one more detail here is the shadow layout.
This is a neat rendering trick for drawing shadows.
This is sort of a general container that will draw
shadows behind all children.
In this particular case, it's just drawing shadows behind
the one child that our container has, which is this
image that we're animating into.
So we set up some initial values here.
We use a trick of blurring the edges, because it's kind of
nice to have this sort of blurry, anti-aliased edge to
the shadow.
So we draw a RoundRect with blur into this bitmap, and
then we're going to draw the bitmap to the appropriate size
whenever we render ourselves.
This property setter is going to be called by the Object
Animator, which animates the shadow depth.
So it's going to set a couple of values in here, including
the alpha on the paint that we're going to render with.
And then we invalidate those containers so that we can
actually have a chance to render our shadow as we draw.
And then onDraw, we override to actually draw the shadows
behind all of our children.
So we're going to walk through all of our children.
In this particular case, there's only one, but you
could see how maybe there would be several children that
we'd want to draw with this shadow.
So we're going to get the child.
We're going to calculate a depth factor here.
We're going to set some values on the canvas that we're
drawing with.
Specifically, we're going to translate to
where we want the shadow.
And then we're going to go ahead and draw bitmap that we
created before.
And that's it.
Again, an important point is transparent activities aren't
a good idea in general for performance reasons.
But if you really want a custom animation going into
that subactivity, this is a pretty good
way to go about it.
You can check out information about the code itself at a
link that's provided in the description of the video, and
check out the other DevBytes for more animations
information.
Thanks.