ALAN SHANSKE: Hi, my name is Alan Shanske.
I'm an intern on the Android Team at Google.
And today I'll be talking about Listview dragging
animations.
So the demo today is going to be a simple Listview, where
you can long-click on an item, then drag it around.
And you'll see that as you drag the item around it will
actually replace and move around, swap the underlying
data set, with the other Listview items.
So the main trick to doing this animation was to realize
that we're not actually moving the views of the list you
round but what you're going to do is create a Bitmap
drawable, overlay the Bitmap drawable on top of the
Listview, and then as you're dragging your finger around,
what's actually going to happen is that we'll be moving
the Bitmap drawable, but not the view which is actually
contained inside the Listview.
So let's jump straight to a demo.
And so here we have just a list of cheeses.
We can click and hold on one of the items in order to get
hold of it, and then as we drag it around you'll see that
the items around it animate into place while swapping the
underlying data set change and moving around.
Furthermore, if the Bitmap drawable, if the hover view
that we have goes outside the bounds of the Listview, you
can see that the Listview starts scrolling and also at
the same time swapping elements as we need to.
Same thing goes when it's above the
valance of the Listview.
When you let go of it, the item just animates back into
place into the Listview, and normal Listview
interactions ensue.
So let's just jump straight to code.
The main file to look at here is Dynamic Listview which
extends a Listview object.
And as you scroll down, the first important thing to see
is that we added an OnLongClick item listener.
So what happens when an item has been selected via
LongClick event we have to do a few things.
One, we have to determine what the position is of the
ClickDown event, we have to determine which item was
selected, and then we also [INAUDIBLE]
the view that was selected from that spot, as well as the
item that we associate with it.
Then the important thing that's happening is we call
this method called, GetAndAddHoverView, which
returns a Bitmap drawable.
And what that does is capture the contents of the view that
was selected, it takes a screen shot of it, adds the
little black border around that we saw, right here in
GetBitmapWithBorder, and the screen shot is captured right
here in GetBitmapFromView.
So once we capture the Bitmap and we insert it into the
Bitmap drawable, the important thing to note is that Bitmap
drawables have the notion of bounds.
So by modifying the bounds we can modify where it's located,
and then when we draw it on top of the Listview it'll move
around accordingly to how the bounds are updated.
OK, so moving on to the OnLongItemClick listener, the
important thing to note is that when an item has been
selected via LongItemClick event there's a few things
that we need to do.
One, you need to determine what the position is of the
Down event was, you need to determine the item number that
was selected, and you also need to retrieve the view that
was clicked as well.
So then what we're going to do is call this method called,
GetAndAddHoverView which all it's going to do is return a
Bitmap drawable that has the same contents as the view that
was selected.
Using a couple methods here year below, we're going to
capture the contents of the screen, add a black border
around it which you saw in the demo, and then we'll throw all
of that into the Bitmap drawable which then will be
overlaid over our Listview.
The important thing to note is that Bitmap drawables do have
the notion of bounds, so if we do update the bounds of the
Bitmap drawable we'll be able to move it around accordingly
on the screen to where we want it to be in response to the
user touches.
That can get an AddHoverView, the important thing to note is
that we have to make sure the visibility of the view that
was selected is set to invisible.
So it's still going to be in the Listview, and the Bitmap
drawable is going to be overlaying the Listview, but
these two objects are completely independent even
though they correspond in response to the user touches.
The other trick that was implemented in order to do
this item swapping animation is to know that whenever
you're going to swap an item you're only ever going to swap
it with the item right above it, or right below it.
So that's why whenever a swap animation takes place, or a
dragging event begins, you always need to retrieve the
above item ID, and the below item ID.
By returning True for a stable item IDs in this table array
adapter, which is a custom array adaptor implemented in
this project, what you're essentially saying is that
every single item in your Listview has a unique ID that
you maintain yourself, and that will never change for
that corresponding object.
So we know that this above and below item ID will never
change for the above and below views around which are
currently animating.
There's some logic here in GetViewForID and
GetPositionForID where I was able to actually retrieve a
view given some item ID, and vice versa.
You can look at this later in more detail if you're
interested in seeing how I did that.
But the important thing here is that in this batch draw, if
I do have that Bitmap drawable it will be
drawn onto the screen.
So what I'm doing here, if I'm not in dragging event then my
hover cell is going to be null, I
won't be drawing anything.
If my hover cell is a non-null object then I will draw it,
it'll overlay the Listview since I'm first calling
[INAUDIBLE]
and only then drawing the hover cell on top of it.
So now in OnTouch event there's a few things that need
to be done.
When an ActionDown occurs I don't yet know whether or not
that's going to become a dragging event or not, so
either way I need to retrieve the x and y-coordinates of the
touch event.
Then in ActionMove this is where I actually move the
hover cell in response to the user touches.
Firstly, I calculate the Delta by how much the user has
already moved his or her finger, and then if I am
indeed in a dragging event then I need to offset the
bounds of my hover cell.
So here I offset the bounds, I update the bounds, and then I
call Invalidate which will call DispatchDraw, draw the
Listview, and then draw the hover cell on top of it.
The other thing I need to do is call HandleCellSwitch which
is where the logic for determining whether an item
cell swap is going to take place.
The way this works is I have a couple bullions that determine
whether or not my moving cell, my Bitmap drawable has been
shifted up or downwards enough to actually completely overlap
the cell that's above or below it.
If that is the case, then I determine whether or not it
was indeed moved up or moved down and retrieve the item ID
corresponding to the cell which will be swapped.
After I do that, I need to swap the elements in the
underlying data set change and notify the adapter that data
has indeed been changed.
So what's going to happen when the NotifyDataSetChange method
is called is that the Listview knows that the data is
different so it's going to invoke a layout and everything
in the Listview will be re-drawn.
Before I do that, I want to make sure that the cells that
I need to be visible remain visible, while the cell that
is still in the swapping and the dragging event is still
invisible, so I update those here.
And then I also update my above and below view, because
now that a soft-swap has indeed occurred in the data
set, either the cell above it or below it is now different.
OK.
And now the most important thing here to actually have
the cell animated to place when a data set change has
occurred is OnPreDrawListener.
So what OnPreDrawListener does is give you a call back where
after OnMeasure and OnLayout event are called, so after all
the values for all of your views, all of your widget on
screen have already been updated, but OnDraw has not
yet been called.
What this lets you do is essentially gives you the
power to access all the final values that all the widgets
will have, but before they've actually been drawn onto the.
Screen
So you can do some custom drawing here, given the final
the values, and then once this completes, you can return True
for all of the other default drawing to
actually take place.
So now in OnPreDraw, you always want to remove
PreDrawListener if you only want to get in here once.
And then what you want to do is grab the cell that's being
swapped, not the cell you're dragging, but the cell that's
going to swap into the new place.
You offset it by delta y because you know that it's
currently in its final position, but you want to be
in it's old position prior to the date set change.
So we calculate the delta y, we set that to be the
translation, and then we simply use an Object Animator
to animate the transition y back to
zero with some duration.
So as you saw in the demo this will actually achieve that
sliding effect when the data set change has occurred.
Strolling down furthermore we have SwapElements which is a
simple method swapping the underlying data set.
And then TouchesEventsEnded, the only important thing to
note here is this Object Animator that we create, and
all this does is animate the Bitmap drawable back into its
correct position in the Listview when a user has
lifted his finger up from the phone, as well as reset all
our variables back to their default values.
The other important thing that was really tricky to do is
determining how we can actually have the Listview
scroll while you're dragging a cell and making sure that all
the above and below item IDs get updated as
we need them to.
So here in HandleMobileScroll is a method that
gets called when we--
so HandleMobileCellScroll is a method that gets called every
single time the user moves his finger, and that calls this
method here.
All this does is use a few methods available in view to
determine whether or not the bounds of the hover cell are
either below or above the bounds of the Listview.
If that is the case, then we'll call SmoothScrollBy
either up or downwards, and return True.
And by returning True what we're saying is that we are in
a scrolling state, in a manual scrolling state created by the
fact that the bounds are outside the
bounds of the Listview.
And if that is True, then we'll know that after a scroll
has completed we should continue scrolling because
we're still in the manual scrolling state invoked by the
bounds of the hover cell.
So in order to do that, we add the ScrollListener right here.
And what that does is every single time an OnScroll event
has changed, has taken place, it checks whether the first or
a last visible cell in the ListView has been updated.
If that's the case, then that means that new first or last
item is a potential candidate for an above or below cell to
be swapped, and that means we need to change those above and
below item IDs.
So we call those methods here, and after a scroll is
completed we call HandleMobileScroll once again
to keep that scrolling event continuing and keep calling
SmoothScrollBy as long as the bounds are outside the bounds
of the Listview.
So pretty much, you can look at the code if you want to see
any more detail.
But you can take Dynamic Listview extend it, include in
your project, and that's how you can achieve a Listview
which can then be dynamically rearranged using user
[INAUDIBLE].
Thanks.