CHET HAASE: Hi, I'm Chet Haase, an engineer on the
Android team at Google.
In a previous episode, I talked about ListView
animations where we would remove a view and then animate
both the swipe of the item out of the ListView as well as the
collapse around where the item used to be, animating all the
other views into place.
So that was all good-- you can run that kind of animation on
the property animation system that goes all
the way back to 3.0.
The demo I'm going to show you today looks remarkably similar
to that, but there's an important twist to it.
So first let's see the demo.
If you want to go back and watch the previous show so you
have some context with that, that's fine.
OK, you're back.
Here we go.
Let's run the current demo, and this should look
remarkably similar to what we saw before.
So we swipe an item out.
If we swipe it a little bit, it animates back into place.
If we swipe it further, it animates out, and then it
animates the gap to be closed.
So here's twist.
If we run this on Gingerbread, which is on my Nexus S here,
you can see this same animation.
So this is a question I get a lot with my animation demos
is, gee, all this property animation
stuff is really cool.
How would I do it on older releases?
Hopefully, that issue is going away over time.
But if you are concerned about Gingerbread releases still for
your application, then maybe we can see how you could get
this kind of technique even on that older release.
So let's take a look at the code.
I'm not going to go over all the elements of the code-- the
background drawing, and the shadow, and everything that
was in the previous talk.
Just go ahead and check out that other DevBytes, as well
as the Google I/O animation talk that that demo came from.
Instead, I just want to focus on the aspect where we're able
to run both the new property style animations as well as
the older animations.
So in our touch listener, we're listening for these
swipe events.
And we're doing essentially the same thing--
we're listening for the down event, and we're tracking
where that down event occurs.
And then in the move event, we figure out where the new
x-position is to figure out how far to move this thing.
And at this point you might be thinking yeah, yeah, yeah, but
we're actually using TranslationX.
We're using a property that doesn't actually exist on
Gingerbread.
So how does that work?
That's the key.
So before we could get the TranslationX value and we
could set the TranslationX value, and these are view
properties that came in in 3.0.
On Gingerbread, those properties don't exist on
view, so it's a little bit difficult to
work with them there.
So instead what we do is call this setSwipePosition.
So I refactored the code so that we could do the correct
thing on either release, Gingerbread or
non-Gingerbread, PostGingerbread.
So in that method, setSwipePosition, you can see
how this works.
So if runtime is PostGingerbread, this is what
we did before.
We simply set the TranslationX value, we basically pop this
item that we were swiping over to the appropriate place, and
then we set the alpha value to be more or less translucent
based on how far away it was from the original position, or
how close to the edge of the screen.
If you're on Gingerbread, those properties don't exist,
we don't have Alpha, we don't have TranslationX,
so what can we do?
We can work with the old style animations.
This is the animation system that existed prior to 3.0.
Well, we can use that if we're actually setting these
properties and faking the way the view looks so that it
essentially looks the same.
It's not quite the same because
there is no alpha property.
But it is more translucent, so it's effectively visually the
same thing.
So we can set a
TranslateAnimation on the item.
We can set an AlphaAnimation on the item.
And we can create an AnimationSet to bundle
both of these up.
We add both of these animations to the
AnimationSet.
We setFillAfter and FillEnabled to true, which
means they're going to hold their value even when the
animation is over, and then we're going
to start the animation.
So basically we set up the animations on these things.
And we're going to effectively set the alpha and the
translation property of this item by running these
animations to a particular value.
And notice that we're actually running the translation
animation basically from the current value to the current
value, which means we're going to just slam it in there where
it's going to start and end at the same value.
And even though the animation is ended, we've set
FillEnabled to true, and FillAfter to true, which means
that they're going to stay at that value.
So that's the trick to actually, not animating the
swipe, but actually setting the swipe to be at the right
position under the user's finger.
OK, so that's setting the swipe, but then we have the
other behavior of actually animating the
thing after it's removed.
So we come in here and say, OK, when the user lifts up, if
they were swiping this item, then we want to figure out how
far to move, and then we want to animate the swipe.
So we call this other method down here-- again, we've
refactored this into a separate method where we
either do the Gingerbread thing or the
PostGingerbread thing.
PostGingerbread, this looks similar to it did before.
If you saw that other video, we animate, we use View
Property Animator, we're animating alpha, we're
animating TranslationX.
Here we use a listener, so actually this animation will
work all the way back to 3.1.
There's no dependency on the Jelly Bean API with end action
in View Property Animator anymore.
Instead we just set a listener, and when the
animation ends then we do the appropriate thing-- we restore
values, we animate to close the gap, all that stuff.
If we're running in Gingerbread, to animate the
swipe, this looks remarkably similar to what we saw before
where we were setting the swipe position.
All we're doing is animating the swipe position now.
So we're starting from the current x and the current
alpha value from wherever that item ended up, and we're
animating either off the screen or back into place.
Again, we're using an animation set.
We're no longer setting FillEnabled because we don't
want the whole [? devalue ?]
when we're done.
Instead we're just going to animate off or animate back.
And when the animation ends, then we either restore values
to where they were if we're animating back, or we animate
the other views, and for this one, we want to animate the
gap closed.
So using the same technique that we used in the previous
DevBytes video, we figure out where all the views are and we
associate those view positions with the itemId.
So we're using a stable ID array adapter here again, and
we store these in a hash map.
And then we remove the item from the adapter.
That forces a notify data set change that
will force a layout.
But before that layout is displayed on the screen, our
onPreDrawListener in the ViewTreeObserver kicks in.
And we can now track the positions of all the views in
the current version of the ListView and figure out what
itemIds those are associated with, compare them to the
other itemIds, and animate the changes.
Again, all of this is the same as before.
The only trick here is how to actually make it work in
Gingerbread.
So we remove the PreDrawListener, we figure out
where things need to go, and then we run the animation,
which is right here.
moveView runs the animation to actually shift these things on
the screen.
Again, we have a Gingerbread a non-Gingerbread version of
this thing.
The non-Gingerbread should look remarkably similar to
what it did before, except in this case we're actually using
ObjectAnimators, sort of interchangeable with View
Property Animator.
View Property Animator is a little bit more of a
streamlined API.
I happen to use object animators in this case.
In the Gingerbread version, again we're using a
TranslateAnimation, pre-3.0 APIs, and we're saying, OK, I
want this view to move from the current start and end x-
and y-positions.
And those were calculated according to where the item
contents were before and after the layout
change on the ListView.
And we set the duration, and we start the
animation, and that's it.
Really fairly straightforward.
The only trick was that the old approach was using
properties that didn't exist on view prior to 3.0, so we
can't use that.
So we use the old animations instead to fake the system out
and make it look the same.
And then the animation, same thing, we're using animation
APIs based on properties, didn't exist prior to 3.0.
So instead, we'll use the old animations that didn't use
properties, but give the same visual effects to
what we were doing.
Everything else is the same logic-- track the itemIds
before and after, where the views associated with those
itemIds were, and then simply animate the
changes and that's it.
So go ahead and check out the animation talk called "A
Moving Experience" at Google I/O to get a little bit more
glimpse into how the demo actually works and the
reasons behind it.
And check out the other DevBytes videos to see the
overall structure of the code and some of the other elements
that I didn't talk about today.
And check out the code for this demo.
Thanks.