CHET HAASE: Hi, I'm Chet Haase from the Android team at
Google, where I work on graphics and animations.
And today I wanted to talk a little bit more about ListView
animations, which I talked about in a previous episode.
This is another episode in a series that I like to call,
You're Doing It Wrong.
So this is another item related to the last one where
there is a need to understand how views are
actually used by ListView.
ListView recycles views, which means that when those views
are not on the screen, it may use that view to hold
different content.
And you need to be aware of that, especially when you're
running animations, so that you're not
animating the wrong thing.
So to demonstrate that, let's take a look at a little demo.
So we have this demo that I call ListView Animations.
And there's a long list, and quite a
boring one, of cheeses.
Now, as I click on these items, they're going to
automatically fade out and then be
removed from the adapter.
And the ListView is going to update itself, and that cheese
will no longer exist.
So we can scroll someplace.
We can delete this one.
We can see the fadeout.
And then the ListView collapses around it.
So all of that is good.
But what happens if we delete a couple of these and then
fling the list?
Hopefully, you can see this on the screen.
But what you should be able to see is, as I'm flinging the
list, there seem to be items that have nothing to do with
the ones that I clicked on that are being removed.
And this is because we're running a fade animation on a
view, which has since been recycled and is being used for
different content.
Now, that doesn't mean that the wrong
item is being deleted.
But it does mean that the wrong view is being animated.
So what happens is we click on an item.
It starts running a fade animation on it.
We fling the list.
That view is off the screen.
It gets reused for different content.
The view continues fading, because
it's the same instance.
It's the same object inside.
So it continues fading, even though it now
shows different content.
And then when that animation is done, then we automatically
delete the original item.
So the data is still intact.
That's fine.
But the visual experience on the screen is just weird.
We don't want to do stuff like that.
So there's a couple of ways that you can account for this.
And I want to talk about those today.
So for one thing, you could use this item called View
Property Animator.
And as of Jelly Bean-- actually, both of these ways
of compensating for this are in the Jelly Bean release.
One is to use View Property Animator, which internally
tells the system, hey, I'm not done with this view yet.
So just let me run my animation in peace.
So if I'm using that for the animations and then I do the
fling operation, you'll see that there is no more
disruption on the screen.
Nothing is being faded out in the new views that we're
seeing on the screen, because we are persisting the views
that had the animations running on them.
So, basically, we're grabbing it and saying, you know what?
I have a lock on this view.
Don't recycle it yet, because I'm in the middle of using it.
If you don't want to use View Property Animator, another way
to get the same capability, again, in the Jelly Bean
release is to use this new property
called Transient State.
So you can call this method, saying, set
has Transient State.
And that tells us you're in the middle of using it, and we
won't recycle it until you tell us to.
So, again, we delete some things.
We fling the list.
And we have no disruptive experience on the screen,
because we know that you are still using those views that
you are fading out.
So interesting demo, more interesting code.
Let's take a look at that.
So we have this class called ListView Animations.
We create the check boxes and the list view and the adapter,
all that stuff.
The interesting part is down here.
What happens when we click on an item?
So when we click on it, we're immediately
going to run this animation.
But how we run it depends on which of those check boxes we
check at the top for using View Property Animator, or
Transient State, or none of those.
So in the none of those case, basically, we wind up in this
code down here.
We say, OK, I'm going to create an Object Animator.
And we're going to animate.
In a previous episode, we saw some basics on how to use
Object Animator.
We use a target object.
We use a target property.
We're going to animate the alpha property on that view.
And we're going to animate it to zero from whatever value it
has right now, which happens to be one, fully opaque.
It usually is.
So we create the animation.
We set a duration of 1,000.
Way too long for this operation, but for the
purposes of this demo, I wanted you to actually be able
to see the animation and the artifact as it was happening.
The transient state is not checked.
So we're going to skip this code here, which is the
interesting part, which we'll get to later.
We're going to add a listener so that we can actually
restore these things later and then remove it
from the data set.
So on animation end, when this nice, little fade animation
out to zero ends, we're going to go in here and actually
remove it from the list of cheeses.
Notify the data set change, restore the alpha property,
which is always important to do with fading animations, and
then we're done.
And then we start the animation.
So life is good unless that view got scrolled off the
screen after the animation started, in which case, the
view has been reassigned to different content when you get
that disruptive experience that we saw earlier.
So the two workarounds for it are to use a
View Property Animator.
So if you checked that box at the top of this demo, then
instead of using Object Animator to animate that view,
we're going to use View Property Animator.
And we do that by calling code much like this.
We say view.animate.
That returns the instance of the View Property Animator for
that specific view.
Set duration, again, we want an unusually long duration for
this animation just for the purposes of this demo.
And then we're going to animate alpha to a value of
zero from its current value, which happens to be one.
And when this animation finishes, it's going to have
an end action, which does something remarkably similar
to what we saw before.
It's going to remove the item from the cheese list, notify
data set changed, and restore the alpha property.
And we're done.
No special magic to work around this.
Instead, internally, we're doing the right thing to tell
the ListView, don't recycle this view yet, because I'm in
the middle of using it.
And then when that animation is done, it unsets that flag,
and that view can be recycled appropriately.
The alternate way to do it, if you're not using View Property
Animator, you can have access to this exact same
functionality by calling set has transient state.
So if you checked that other box at the top of the demo
that said Transient State, then we will say, set has
transient state.
So when you click on the item, we're automatically going to
flag that item in the ListView as having transition state,
which means the ListView will not recycle that item until
you tell it it's OK.
So we're going to set has transient state.
We're going to run the same exact animation as before.
But OnAnimation end is going to run this little additional
bit of code, which restores the transient state to its
default state of false.
So you click on the item.
We start running the Object Animator.
It's fading out to View.
It's all happy.
You've set transient state on it.
We fling the list.
That view still exists.
Even though it's now off-screen and you can't see
it anymore, it's not being used for other content, which
is really the key to making this animation
actually work correctly.
So ListView Animations, if you want to do them right, be
aware of that transient state.
And don't try to animate stuff that's going to be recycled
with other content.
Thanks.