Hi, my name is Daniel [? Orchansky ?].
I'm an intern here on the Android team at Google.
And today I'll be talking about some folding animation.
It's a folding animation kind of like an accordion, except
you can specify the number of folds, the orientation, as
well as the anchor point along which the fold is going to
take place.
So now while you'll be able to fold any image, as you'll see
that very soon.
And the trick behind doing this is noting that given some
number of folds, you can split an image into several
different segments.
Each of these segments is a rectangle that can then be
transformed in various ways using major transformations.
And then, in order to make the fold even more realistic, what
I'm going to do is use gradients along with a
shadow-like effect on alternating folds to actually
get that real folding-like effect.
In this demo, it's important to know that there's fold
factor that I'm going to be using that varies
between zero and one.
And what's going to happen when something has a fold
factor of zero.
It means that it's completely unfolded.
Whereas when something has a fold factor of one, it means
that it's completely folded to the point where you cannot see
it anymore.
So that being, said, let's jump in
straight into the demo.
So here we have just an image.
And I'm going to start folding it.
And as I'm moving my finger, you can see
the image gets folded.
And then it gets unfolded.
This is four folds.
You change the anchor point of where it's happening.
And once again, the same thing applies here.
I can change the number of folds to eight if I want to.
And the other thing you're noticing is this kind of sepia
effect as I'm animating.
And I can turn that off.
It's a cool little feature I thought would add a lot to
this, which I'll show you how to implement in a second.
And the other thing about this is that you can even fold not
just that static image, but also a live camera feed if you
use something like a texture here.
So here we have our camera crew and unfolding and
unfolding it.
So it is a live image that is being folded an unfolded.
OK, so that being said, let's jump straight into the code.
And the way this works is folding layout extends the
view group.
So this is very generalized, except it is set up in such a
way such that my layout can only ever have,
at most, one child.
But then again, this one child can be a layout that you can
populate any way that you want, and then fold--
even a user interface, with a buttons and text boxes if
you'd like.
So scrolling down, here I'm doing a lot of checks.
So if you're trying to add more than one view, what's
going to happen is that I'm actually going to throw an
exception because, in this context, that's not something
that's being supported.
And once we get through that, then there's all the setters.
So like I mentioned before, you can set
the number of folds.
You can set the orientation.
And you can also set the number of folds, the
orientation, and the anchor point along
which it's being folded.
So I'm not showing the orientation change yet, but
the same thing applies when you are in the vertical, as
opposed to horizontal, orientation.
So then here you can set the fold factor, which is, again,
a value between zero and one.
And every single time you set the fold factor, you need to
calculate matrices and invalidate.
So invalidate will draw the new folded view onto your
canvas in it's new folded state.
And calculate matrices actually calculates all the
transformation matrices that you need to go from the
unfolded image to the folded one to some degree.
For every single time some other parameter gets changed,
we need to call this method called update fold, which does
all the preparation for actually folding a new image.
So in update fold, we're going to call prepare fold.
And all prepare fold really does is return all our
variables to default because this is now a new image.
It allocates space for all the transformation matrices we
need, the paint objects, the gradient objects,
and things like that.
It sets up the gradient objects that we're going to
need for later, stores all the values we're going to need,
stores the original widths and heights of the images.
And the most important thing that it does is right here,
where it splits the image into rectangles.
So like I said before, in order to fold something, what
you need to do is split it into separate segments, so you
how each segment should be transformed.
But every single segment is just a simple rectangle.
So I'm going to store all these rectangles in an array.
And I'm going to be referencing these rectangles
as the source for every single transformation
that I'll be doing.
So that's on the fold.
Then in calculating the matrices, this is where we
actually calculate the transformation matrix.
I'm not going to go through the math of how I did
everything.
But the main thing to note here is that you need to make
sure that you reset your transformation matrix on every
single time the fold factor gets changed.
And the other thing to note is that the amount that a fold
should be folded by is inversely proportional to the
distance from which it is away from you.
So for example, when you start folding it, you'll that edges
very quickly start going into the page, whereas when it's
already really folded, the edges don't go
into the page as much.
A simple inverse relation achieves this.
And that's how you get the fold.
So back in calculating matrices, there's a listener
that I added on end fold and on start fold.
So what this gives you is is just kind of a listener you
can listen to when a fold ends or starts.
And scrolling down past all the math, what we're doing
here is we're going to be transforming a set of eight
points to another set of eight points.
Since every single segment is a rectangle, every single
vertex has two points.
An x and a y coordinate.
So given the eight points from the source, and the eight
points in the desination, we'll be able to achieve the
transformation that we need.
So how we do that is using the function available in matrix,
in Andrew's matrix API called, poly to
poly, set poly to poly.
And what this does is this take an array of source
points, an array of destination points, and the
number of points you're going to use.
So in this case, that will be four.
And this is limited to four at most.
And so all that's going to do is give us a matrix that
transforms that original
rectangle to this new rectangle.
And in order to then imagine the graphical effect here, the
destination source has its edges kind of curved in, as
opposed to just a rectangle, where all the sides are
parallel and perpendicular to each other.
And scrolling down farther more, the other thing we need
to do is scale our gradient.
So if you saw in the fold, every other fold has a very
solid kind of overlay black shadow over it.
But that every fold in between that just has a small gradient
going across it to achieve that more realistic effect.
So the alpha value, as well as the size and scale for all of
these are all calculated here.
Then, in this batch draw, there's a couple optimizations
that we have.
For example, if the fold factor is equal to zero, and
it's completely unfolded, no folding needs to happen, so we
just draw the original image and call super.
In some other cases, like if it's equal to 1, then we don't
want to draw anything.
And then what we're going to do is loop through all of our
folds and just start drawing each and every fold into our
canvas separately.
So right here, first, what we do is concatenate our canvas.
First we need to save the state of our current canvas.
Then we concatenate our canvas with the
transformation matrix.
We need to clip [? our rect ?] because all we really want to
show, for every single time, is one segment at a time.
So we clip that rectangle.
We transform it appropriately so that it's located in the
beginning of the view because our transformation matrix, if
you look the map later, takes things from the very far left
and then puts it in the correct
position on the screen.
Recall super and dispatch draw.
And since the canvas is clipped, what this is going to
do is only draw that clipped section of the canvas that we
have specified.
Then we transit our canvas back.
And then using the same matrix information, we'll also draw
the solid shadows as well as the gradient shadow on top the
folds that we've already drawn above.
So one of the biggest problems that we actually encounter
while working on this is that in Android 4.3, a new bug was
introduced where if you call super.dispatchdraw multiple
times within one call to dispatchdraw, it won't
actually have the same effect as it does on 4.2
and prior to that.
What's going to happen is all the canvas transformations are
going to be delayed until the last canvas transformation.
And only that last one that will actually take effect.
This is now a bug, which a patch will be
uploaded in the future.
But in the meantime, there's a small work around that we
found, which only works for static images, not the like
camera feed that you saw earlier.
And the way this works is, in prepare fold, what we're going
to do first is capture the actual bitmap that we can see.
So we're just going to take the child and capture that
whole thing in a bitmap.
The matrix transformations are going to remain
completely the same.
But the only difference we're going to be is here.
So if it is GBMR2, so Android 4.3, instead of calling super
to dispatch draw and then transmitting canvas itself,
all we're going to do is take our bitmap, specify a source
and a destination rectangle within our bitmap, and then
use that transformation matrix to draw only that segment from
that bitmap onto the screen where we want to.
So it will have the exact same effect on static images, but
it will not have the same effect--
it's actually, it will not work for something like a
texture that continuously updates.
The last thing that I just want to show is how we add the
sepia effect.
So there's a lot of boilerplate code in this file
to handle all the touch events, but here in on fold
listener, which is the custom listener that we created, all
we're going to be doing is adding a hardware layer on top
of our view.
And when you set a hardware layer, what you can also do is
specify a paint object.
So if it is a hardware layer, you can
specify a paint object.
And that paint object gets applied onto the view that has
that layer.
And if you look at sepia paint it's a very
simple color filter.
They use a couple of color matrices with some values.
And by overlaying that onto the view, you get the sepia
effect as you're folding it, which can be very easily
turned on and off by removing the layer off of our view.
So that's ow you do cool folding animation.
Thanks for watching.