DevBytes: Custom Activity Animations

DevBytes: Custom Activity Animations


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.

34 thoughts to “DevBytes: Custom Activity Animations”

  1. I remember a the teacher in our collage android class wanted us to do something very similar in 2.3.3. It was a murder… But it can be done with some clever self aware thumbnails & click movement info & the ever useful translucent activities.

    Return true – it's the right thing to do! 😛

    In the feedback section: you guys could do more about controlling animation of the browser window lunched within/from the app… on the return it goes back to standard system animation, kinda annoying. ;]

  2. Here you can download the Android Developer Tools bundle
    h t t p://developer.android(.)com/tools/index(.)html

  3. I implemented a custom Activity animation using similar techniques.

    Here's my post about it:
    udinic (.) wordpress (.) com/2013/03/13/activity-split-animation/

  4. The onPreDraw call back gets called two times even though I remove the listener in the first call, when I add the listener in the onCreateView of a fragment. Is there a way to have it fix?

  5. @Chet Haase, you mention in this video that there are performance ramifications for using a transparent background. At least after the completion of the animation, would the performance impact remain if you were to use setBackgroundDrawable, in the example given in the video, to set a black ColorDrawable as the window background?

  6. Awesome video. This is a must for everyone who want to learn about animation transition in activity.

  7. SLOW

    DOWN!

    You talk WAY too fast – there's no rush – do you want people to absorb this, or do you just want to waste our time AND yours?

  8. Very nice demo @Chet Haase !
    I was trying to do the same thing, but have it also work on orientation change (you open the child activity, rotate the screen, press back, and the thumbnail is not at the right place in the parent activity anymore). I managed to do it but only by keeping a static reference to the parent activity, and having the child activity ask the parent the thumbnail position before animating it back. This is kind of an anti-pattern, any suggestion on how to do this cleanly?
    I created a thread for that here: http://stackoverflow.com/questions/25222350/cleanest-way-to-access-parent-activity-from-child-activity
    Thanks!

  9. I feel like Android makes some things that I should be able to do simply very very hard. Now that I've made this blanket statement feel free to respond.

  10. So, these window transitions don't seem to work with a StreetView object, at least as far as animating the "scale" attribute.  I was really hoping to have a StreetView object (activity) expand from it's starting rectangle to full screen, as seen in the Material Design animation techniques.

    However, after much wasted time, I've come to understand that StreetView and MapView's derive from SurfaceView, which apparently does not support a scale animation.

    Does anyone know if there's another way to animate the scale of a map/street view?

  11. If anyone is interested, I adapted the code here to do the same animations but with fragments instead of activities. 
    Check it out here: https://github.com/yrizk/FragmentAnimations

  12. Scenario:
    Activity A starts activity B (transparent).
    B.onCreate > custom animator (in my case circularReveal iewAnimationUtils.createCircularReveal ) – works
    B starts activity C (B is in front of C)
    I want B to circularReveal the new activity C in the back – how can I achieve that?

  13. This is helpful indeed. Also, it's pretty simple to implement such thing with activity and fragment (a little more complicated with onBackPressed, but still simple)

  14. in my device API 16 this method withEndAction (WEA) do not work correctly. i've replaced this method on setListener, then activity animations work correctly. why method WEA don't work on my device? as i know WEA was added in API level 16 (android doc.)

  15. Fantastic video. I know it's a few years old, but it really helps spice up my app! Thank you for explaining everything and including the source code in the description!

  16. Nice video. You may also want to checkout the review of Activity on my blog at peacereviews. com/activity-review/ Thanks. Ddt Clemmie.

Leave a Reply

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