(enlightening music)
(audience claps)
- Thank you.
Can you hear me?
Okay, yeah.
Welcome everybody, thank you for joining me today.
First of all, remember to join the conversation
in Twitter, #gotocph.
Remember to engage so you can rate
my actual session and ask questions.
My name is Israel Ferrer Camacho.
I'm an Android developer at Twitter.
You can follow me on Twitter too,
and ask me questions there too.
I work on the Moments teams which is a
nice experience, fullscreen experience
horizontal scrolling where you can add
a list of tweets, and it's a story.
It doesn't have to be following a chronological order there.
For those who are not an English native speaker as me,
smoke and mirrors is an expression used
to describe something that is
obscuring or embellishing the truth.
It's usually used for magicians because they do magic
which is simply not real.
It's just a trick, just so you know.
Sorry if I spoil you guys.
The reason for this title
was in the last Google I/O during the RecyclerView talk,
where Adam Powell and Yigit Boyar,
by Adam Power and Yigit Boyar.
Adam Powell actually started the talk by saying
UI developers, we have a lot of things in common
with magicians, and that is actually true.
If you think about some of the animations that we do,
and some of the components that we use in the framework,
and that's the idea of this talk.
I'm gonna be showing some examples in the Android framework
or where we are using this smoke and mirrors
to make an application just great, flawless, and smooth.
After that I will start talking about one of my
favorite apps in Android, and how that experience
is actually built, and I will do a demo with that.
So let's just start with the Android framework.
So these are Timeline.
Hopefully you know what is Twitter.
So theoretically, anytime like you have infinite tweets,
as you can see we're scrolling really fast,
performance's really good by the way,
so it is a problem right?
The problem is we have infinite amount of views
that we wanna show but we have limitation.
We cannot just instantiate thousands of views
at the same time.
Actually, what we have really do is
which is need to do this.
We are showing one, two, three, four views in the screen.
We don't need to actually show any
or instantiate any order view.
Just five at the same time, seven, eight tops
if the tweets are really small
but Jake likes to talk a lot so they're just big.
So, anyway how we do that?
We use this component called RecyclerView,
the name will give you a hint of
what it's actually doing, which is recycling.
I wanna talk about it, how internally works
because I think it's interesting.
Internally, it has the linear LayoutManager,
which actually recycles,
which actually is responsible for measuring
and positioning the item views,
and as well as determining when to recycle those views
that are not shown anymore.
So when you try to actually show the next position,
the LayoutManager is gonna go to the RecyclerView,
and invoke the method getViewForPosition.
When that's called, RecyclerView will go to the Adapter,
and will ask for the type of that view
because you can have many types of views
in your RecyclerView so example
if you want to have different headers or you want
to have a cutout so for showing ads for example, okay.
So yeah, we get the type the RecyclerView is gonna
go to the RecyclePool, which is where all the views
that ViewHolders that we can recycle.
It will get a ViewHolder for that type.
If there is none, it will go the the Adapter
and then create one.
But if there is one, it would just send it
back to the Adapter and say hey,
can you find this ViewHolder with a new data,
with a data for that new position that we wanna show,
the one that was showing on the animation.
Once it holds the information, it just return it back
to the RecyclerView, and then the LayoutManager will
actually lay it out in the screen, that item.
This is a really simple explanation of how the RecyclerView
actually recycles views, and you can learn a lot more
in the Google I/O talk that I was talking about,
the one that inspired this talk.
It's called RecyclerView Ins and Outs,
and they talk a lot more about how to animate items,
and do transitions in between the states.
Another example on the Android framework is this one
that I'm showing in the Twitter app.
As you can see, there is a list of images,
and when you click one with transition to fullscreen.
Those are called Shared Element Transitions.
Shared Element Transitions, they have smoke and mirrors too
but first let's dig in on how it works.
The way it works is the activity has an activity transition
a state which persists a state of that transition,
and it has like the most important part is the shared views
that are gonna be transitioning from one screen to another.
When activity life cycle is invoked, for example
it goes through onDestroy, or onPause so it's disappearing,
and then we are gonna start a new activity,
it will call the activity transition state to start
a new transition which will actually call to the
activity transition coordinator.
The ActivityTransitionCoordinator is a base class.
There are actually two.
There is entering and exiting coordinator,
so one handles the enter transition,
and one the exit transition,
and that has a transition manager,
and it will begin the transition.
So by the default the transition is a translation
but you can change that on your theme,
and you can actually customize your own
transitions between activities.
But you must be thinking, you're talking a lot about
how this works but what is the actual smoke and mirrors
in transition in the shared elements.
Well, think about it.
When an activity is being, going to hide
or to the background, and another one comes in the top,
how they are actually re-enabled to do that transition
from one to another.
Well what they do is they hide the target activity,
and they have a list of the views that are transitioning
from what position they are transitioned
so they pretty much in any activity do that transition,
that's what they are doing.
How they do that, well, they use ViewOverlay,
which is used by the default transition in the framework.
ViewOverlay provides a transparent layer
on the top of a view,
to which you can add any type of visual content,
(mumbles), and that doesn't affect the
layer hidden (mumbles) on the top so that means
that you can pretty much animate anything,
and it just works and doesn't mess with
the layer hierarchy, it's great.
It's gonna be your best friend forever in animations, okay.
Think about that, that word of what I said.
So let's imagine this, you can see there is two layers.
There is the LinearLayout which has a nice image view
with a Saturn illustration,
and then this is the ViewOverlay.
In order to make an animation,
I'm gonna add that to the ViewOverlay.
How do I do that?
Well LinearLayout, any view has that,
LinearLayout, getOverlay, and then you add to that overlay.
What is gonna happen is that suddenly that ImageView
is not gonna be part of the LinearLayout anymore.
It's gonna be in this temporary ViewOverlay
that we're gonna use for animations.
When you do that, the rect of that ImageView is
invalidated in the parent so that force a re-layout
so you have to be careful if you depend on that view.
Otherwise you will mess the whole hierarchy.
But the good thing is, any type of touch or animation,
touch depend on animation, it gets delegated
to that ViewOverlay with that ImageView.
So if you are actually, imagine you have a reference
to this ImageView, and you try to do animation,
it will animate even if it's not part of
the LinearLayout anymore, which in this case
is part of the ViewOverlay.
So unfortunately, shared element transitions are real nice
but there are limitations.
The first one is that the user can't control
the transition with a touch.
It just an simple animation from one place to another
so the eye of the user gets caught,
and doesn't lose the context between the screens
but it doesn't allow the user for example
to pinch and zoom, and control the transition
with a pinch and zoom, right.
So that's a limitation.
Another limitation is that one,
actually when I was doing the code, I saw that here.
The transition doesn't track the target destination
so if you don't eat all the touch events
while the transition is running, it looks terrible
and it's just a limitation.
I mean, we can simply fix that
by using this TransitionListener.
Thankfully, we can hear all the life cycle events
of the transition so what we can do is onTransitionStart,
we are just gonna eat all the touch events, pretty much.
Then when the transition finishes, we'll just set the
touch listener again to the right place.
Easier right?
Okay, so before entering into the interesting part
of the talk, which is the demo,
let's talk about two important attributes for an image.
Consistently these are the attributes that are actually
might as well be thought type of
rendering outside the parent.
So first of all is the ClipChildren.
The ClipChildren attribute is an attribute of
a ViewGroup and by default is set to true,
which makes sense because it clips the children
to the bounds of the parent ViewGroup,
and that's exactly what we want right.
By default, we want that to happen.
So it looks like this.
Right, you're not allowed.
That's me actually, that's my avatar
so I'm not allowed, my ImageView is not allowed
to draw outside the parent view
unless I set ClipChildren to false.
In that case, I'm gonna be able to draw
outside the first parent but not the second,
not the following parent.
So what I have to do is, spoiler alert,
I have to set that to false in all the parents,
which is kind of like, but that's how it works.
So, yeah okay, really bad animation that I got there.
It just shows the point.
Okay, so ClipPadding, so ClipPadding is the same.
So if you have the ClipChildren false
but you have a padding, the view is still
gonna clip to that padding, yes, that happens,
especially with RecyclerViews, happens a lot.
So just remember if suddenly a view of yours gets cut,
in a RecyclerView, maybe it's because there is a padding
and you didn't set that to false.
So this is what it looks like.
It gets clipped to the padding,
and pretty much already at some point,
I think a long time ago,
was creating this type of utility,
which allows me to go through all the parents of the view,
and just set everything to false,
which is not super difficult.
Anyway, let's talk about one of my favorite apps,
yeah this is Google Photos.
Have you think about it?
Do you have it?
Do you like it?
I think before I never had such a
great UI experience in a gallery.
I was actually, it's just top-notch,
it's really well thought and for a long time,
I was thinking, how did they pull that off?
It looks so good,
but let's check it out how it works actually.
So there is one layer, two layers and then fullscreen,
and really what I've, fullest, I pinch this so much,
you can see that, I'm not really accurate,
but anyway so that's how it works.
It's just like super smooth, it has three levels of zoom,
and then it goes to a fullscreen.
As you can see, they're able, the user is able to control
the transition to fullscreen, which we were saying
that Shared Element Transition can't do.
It's a limitation.
So the way you figure out how something works in the UI,
pretty much these developer options.
You make them 10 times slower, which is too slow,
maybe five times is fine,
and you make the animation 10 times.
The duration and the animation is scaled 10 times slower.
Then when you do that, so this is what it looks like.
Yeah, the other is just (mumbles).
So with that you are able to see what is going on
because one, you will understand why that's really important
soon but for now and other developer option that you want
enable these flames, yes I like flames.
So show layout boundaries,
and you should enable it all the time.
It's such a beautiful UI right now.
It's wonderful for your eyes.
No, but really, this is really useful for developers.
That is the blue line which are the clip-out,
the clip bounds, whereas there is the red line,
which is the optical bound, which is something
into a zoom 4.3 that I actually didn't have.
Never to use but maybe you need it, so check it out.
And the pink area, which are the margins.
These not only helps to see the alignment
when you hold the screen but it will help us
to feel that, I did ultimately but I wanna show
if we were doing together so you see what is
the trick of that will work for us, application.
So now this application looks like that.
It's pretty much the same but with some
layout boundaries, right.
Yeah this is Google I/O,
we had such a great time by the way.
Anyway so now, just pay attention to the video.
I'm not gonna say anything, just think yourself
how this is happening, and then I will go deeper
into what is really happening.
Okay, so I pinch and zoom, this is my,
that was the concert, anyway.
So three levels, zoom scrolling, going back.
Are you seeing what is happening?
You see, we don't see that before
because it was fast enough right?
And now yeah, picture zoom is more difficult
once you put it at scale 10x.
It's super difficult actually.
You have to do it like it really, huge span in order
to go to fullscreen.
So what happened?
Did you noticed what happened?
Okay, let's cut to the real part,
which is, okay this part of the zoom in.
Let's get closer.
Did you see that?
Can you see the two different RecyclerViews.
There is another invisible RecyclerView with
a destination size, with a bigger size,
and not only that but they're actually cross-fading
between one and another.
It's all smoke and mirrors.
They're just tricking us but you know,
that's magic guys.
That's what we do, we do magic.
So the lesson here is,
sorry let's put it back, it's really good.
Magic, magic, okay, yeah.
So the lesson is anything fast enough will look good enough,
and that's true for everything.
When you just try 24 hours to make all your animations
10 times slower, and you are gonna see how the application
suddenly doesn't make any sense any more
because they all actually use that.
It's fast transition, fast animation that hides all the
problems in implementation, right.
There is one more thing that I wasn't able
to get on that video but in this video,
you will figure out.
When you try to zoom in to the fullscreen.
Uh-oh, here we go, what is going on?
It's the same image.
Right so, they are kind of faking it.
They're copying the same drawable maybe,
and they're using it in order to do the transition
to the fullscreen.
So that's another trick but if you do it fast enough,
and the scale is not enough, 10 times slower,
then it just works.
So that again, more magic.
These Google Photos guys are just magicians.
So fake it until you make it.
Pretty much, fake it until you make it.
That's what we just do all the time in the development.
So I showed you how it works but now I tried to be sure
that I wasn't doing it wrong.
That my assumption wasn't wrong so I actually create
a demo, well I created a demo, which is a simplification
of Google Photos but it's pretty much it.
It's like two zoom levels,
and then the last one is, goes to fullscreen.
So you can see all these smoke and mirrors,
and you can actually see the code.
It's gonna be in the GitHub repo so you can check the code,
and you can actually apply all those tricks
to your applications.
So let's go for the demo, yes fireworks, so good.
Again, fireworks.
So this is the application.
This is some nice drawings, by my wife,
this illustration, is an illustrator.
So zoom in, zoom out, okay,
and then fullscreen, fullscreen, fullscreen.
So how did I implemented that?
So these are the layers.
I have a container, a medium RecyclerView,
a small RecyclerView, and a fullscreen container.
So the container is gonna be the root of
the whole hierarchy layout,
and of course it's a FrameLayout.
It's an obvious choice because we need to stack all
those layers on the top of each other,
and they are gonna take fullscreen.
Why do I need to put a fullscreen container on that screen
is because, as I said before, the transition,
the shared element transition doesn't allow the user
to control the transition to fullscreen
but I wanna do it because that's the nice experience.
So I put everything here, and you can just do that
by creating your own NavigationManager,
and depending on the state, you have a stack,
and depending on the stack, you can just inflate the,
sorry, instantiate the presenters and the view delegates
that you need for each of the layers.
But let's talk about the important part which is
the medium RecyclerView and the small RecyclerView.
From now on, I'm gonna call the small images,
the small RecyclerView, and the medium,
which is a terrible word, I didn't know how to say bigger
than a small, which medium is fine,
because it's not too big so medium is fine.
So medium and small RecyclerView will be the
higher level of zoom before going into fullscreen.
So we are using two RecyclerViews because that's the trick
that allow us to do that nice zoom in effect on the gallery
but before doing that, we have to talk about some thing.
Really when I pronouncing this word,
nobody actually understand me
so it's pivot or something like that.
Otherwise you have this nice meme from my favorite
episode of Friends, which was the pivot, pivot, pivot.
If you don't know it, you maybe too young
so you should watch it because it's really fun.
It's still fun, incredible, still fun.
So yeah, let's talk about pivot.
So the pivot by default, it sit in the center of the view.
So what happen is when you try to actually scale something
it scales from center, and it goes in all directions
so it looks, that's not what we want, right?
What we want is to actually to scale right down.
So the first thing that we have to do is
said the pivot, the pivot, to (mumbles)
to that top left point of the view,
which is the RecyclerView
because it's the one that we are scaling.
That's important for scaling and rotating too.
So then it looks like this, which is actually what we want.
We want that type of scale.
Okay, so we have two RecyclerViews,
and my approach is to use two Adapters
bind with the same collection back in the map.
Because really they are actually using the same data.
They're just the same image.
They just have different size, right.
It's important to set on the setup
the medium one to invisible because we only want to show one
first which is the small one, which is the one that is gonna
be zooming in the first time.
Then let's talk about touch events.
There is different approaches but the approach that
I like it, was to create this eat and touch dispatcher
which is a class that gets all the touch events,
and depending on the state of that view,
we go to do, we delegate that touch to one or another class.
So pretty much what I did was create this class,
and then add that class to both Adapters.
So how does it look like?
Well, it looks like this.
It has more stuff but the touch event is probably
the most important part.
So since both the RecyclerViews are using the same
we can get the ideal RecyclerView.
So in all the Moment we know
what is a RecyclerView getting the touch been.
For that, what we do is we have this gallery
adjusts the texture which I will talk about it later.
It's the scale adjust to the texture
that we will use for scaling the RecyclerViews.
What I do is in case the small RecyclerView
is getting touched, it means there is only one way,
which is it can only go the medium RecyclerView,
it can only zoom in, right?
So that's what it's doing.
It's pretty much delegate that to the gallery
adjust to the texture, which will know
and will do that zooming.
Then in case of the medium RecyclerView
we have the same.
The gallery adjust to the texture will know,
will know how to do it.
It has both logics.
The important part here though is that
we have zoom out, which goes from the medium to the small
but we have zoom in or in this, actually I was kind of lazy,
and I just did click but when you click,
you go to the fullscreen.
Then how we do that?
Well we use the span.
The span is the, it's actually not the correct span,
it's the delta between the last span and the current span.
A span is the distance between your fingers
so with that if it's (mumbles) I know
that it's now pinch and zoom in, it's just clicking.
With that, I ask the RecyclerView, which in this case
we know is the medium RecyclerView,
give me the view on that coordinates,
and then I just perform a click.
So that's how the zoom in, zoom out between
this medium RecyclerView and the small RecyclerView works.
Now how do we scale them?
So this is what we're missing, right?
We weren't scaling, we were just sending the touch events.
We're not a scaler, right.
For that, we have convenient contract in the platform
OnScaleGestureListener, and it has
onScaleBegin and onScaleEnd.
The names are pretty self-document
but onScaleBegin, you just set up everything,
first starting the scaling.
On a scale, it's a constant stream of the scale gesture.
And onScaleEnd is pretty much the user just stopped
touching the screen so it just reset the position
to the state that you want.
So onScaleBegin, what we wanna do is show both
small and medium RecyclerViews because what we wanna do
as you saw in the video is add the middle of that
zoom in or zoom out, we are gonna do a fade in, fade out
in between both RecyclerViews, right.
So both have to be visual.
You can do it different but this approach works
because the smoke and mirrors,
it comes out the end onScaleEnd.
But on the onScale is pretty much all the maths
of the talk which is, while there's a gestureTolerance
which applies a low plus filter to avoid the flakiness
because our fingers are not that accurate
so even if you think you're not moving your fingers,
the screen is getting some events so there's
a constant signal that can prelude a small increments
and decrements on the scale.
So we want to apply a low plus filter
so we know that a minimum span was met,
and it's a span on that same duration
that will work scaling before.
So that's the gestureTolerance.
As I was saying, this code is all on the repo
so you can actually read that method later.
Then we have the scaleFactor and scaleFactorMedium,
which are the scaleFactor.
As you can see, on a scale give us are the texture
that the texture give us a scaleFactor,
and we use that as scaleFactor with a clamp function
to limit the minimum and the maximum
scale of both RecyclerViews.
Of course, one is the inverse of the other,
and the minimum and maximum, they have to match.
Otherwise in the middle of the transition,
they would not have the same size,
and then the fade in, fade out will look terrible, right,
because it will not match.
So the numbers are whatever I like but you can actually
modify that and make it as complex as you want.
Then the outfit's the same, we want that outfit to be
in the middle of the transition,
half or both of the RecyclerViews,
and then at the beginning, a small RecyclerView want,
and the medium to see to,
and at the end the other way around.
So that's how the onScale works
but what happen if suddenly the user stops
touching in the middle of the transition?
If we don't do anything else, if we leave it as it is,
it's just gonna look like two RecyclerViews
and half half of both of them,
or you don't know, it depends right?
But it will look weird,
we're just getting a weirdest state.
So onScaleEnd, what I wanna do to hide this implementation,
this smoke and mirrors, what I want to do is just
finish the transition automatically to one of the states.
For that, I just use a TRANSITION_BOUNDARY
which transition one that is just like I calculate a delta,
and so I know how much is missing for the transition
so if it's below 20 or 30%, and you can change it
for whatever you want, I will assume that the user
didn't wanna go fullscreen and he actually wanna go back
to this small, right, that's what I do.
Otherwise, it fits that both that TRANSITION_BOUNDARY,
it means, okay, he wanna go to fullscreen
but he was just, doing really quick the move.
If you don't do that, what happens is like
everything just gets stuck in one place.
We don't want that because that will
show the trick to the user.
So with that, we have the container,
the medium RecyclerView, and the small RecyclerView.
But then let's talk about the fullscreen image container.
The fullscreen is a black, it's a friendly art,
which are black background,
and that shows in a full with the image, so exactly this.
So to do that, there is this requirement by a RecyclerView.
When you add an item to the RecyclerView,
the following, so you add one item and then another item.
The second item is gonna be in
a higher level base from the first one
so if you try to actually scale that first item,
it's gonna be drawing below the second item
so it would just not look right.
That's each item has an elevation in a RecyclerView.
So there are different approach.
This is probably the most difficult which is just create
a custom LayoutManager that knows
when we're clicking that item.
When we're clicking one single item,
we want that item to be on the top of everything,
and be able to draw above all the other items,
which I will not do because that's too much work
when you can fake it, exactly, I like to fake things.
So yeah, then you can use this one.
This is middle ground which is you see, is just a callback,
and then what you're gonna do is when you get that callback,
you set the elevations to the rest of the items
so they're in that we wanna scale,
is gonna be on the top of all of them.
But remember we talked about it five minutes,
no, not five, 10, 12, I don't even know.
At the beginning of the talk, we talked about your best
friend forever in animation from now on, ViewOverlay.
That's what we can use.
We can just use the ViewOverlay on top of the RecyclerView
for the transition for the pinch to zoom to fullscreen.
Then we just add that item, that view to the frame layout,
which is the fullScreenContainer.
Why you wanna make it more complicated
when you can do it easily, right?
That's it, it's like five lines.
So how does our, it's more than five lines.
There are some methods there and some instruction but okay.
You get the overlay from the fullScreenContainer.
You're gonna clear the state of that overlay
because you don't know if previously
maybe another item that was zoomed in.
Then you're gonna add a new item.
As you can see this was in onClick.
That's the perform click that we were doing before
on the itemTouchDispatcher, itemTouchListenerDispatcher.
Yes, that one.
What we're gonna do is, we animate that itemView,
and then animate to fullscreen,
and what we have to do is withEndAction,
which is gonna be setting the click listener
so we can go back to the gallery,
to the medium RecyclerView.
Let's take a look at that method.
So that method is actually pretty trivial too,
and as you can see, of course that is more things
going on like setting the background to BLACK,
setting the background to TRANSPARENT
or setting the visibility to GONE
but pretty much the idea is
you are just using the overlay to do your transitions,
and then at the end of the state,
when the transition is done, you add that view
to the real parent where we will actually add
all the logic to do, if you wanna did that image
or you wanna change the name of the image
of whatever gallery application does on a fullscreen image.
So what we are gonna do when somebody clicks on
that item on that fullscreen image again,
we just have to transition back to the medium RecyclerView
so we add that item again on the overlay.
Then we're just gonna animate it,
we're just gonna set the originX, originY
and then scale it to one,
which is the original size for that image.
Finally, don't forget to remove the item from overlay.
Otherwise it will just still be there until somebody,
something on the framework decides to recycle that.
That's it, that's how easy you can allow the user to
interact on the transition without using the
shared element transition.
Just by creating layers, layers on the top of it,
and just one activity.
Then you get this, you get zoom in, zoom out,
click listener fullscreen.
Not bad for a 20 minutes or 30 minutes of talk.
Again, magic guys, this is all magic.
That's what we do, we do, you're development, we do magic.
Magician is your new job role from now on.
So let's recap, let's recap.
Magic tricks that you can use for animations.
ClipPadding, ClipChildren to draw over parents and paddings.
That's important one.
If you see something is cutting your views,
it's probably one of those are not set to false.
The ViewOverlay, best friend forever for animations, really.
It allows you to draw over the whole layer hierarchy,
and to do any type of things without messing with it.
Shared element transition has, it doesn't allow the user
to control the transition without events.
So you can actually allow the user to do that
by just creating a single activity,
and then creating your own navigation,
which is not, it doesn't have to be super complex.
We don't need to have 300 life cycle events
or anything like that.
It should be just show, hide, and maybe something like
animate to next, or enter, or exit the screen.
That's whole idea for another talk.
Then finally, fast animations hide
any problem with implementation.
If you do it fast enough, as we saw during the talk,
it's just gonna look right, and nice, and smooth,
and good anyway.
So remember that, make it fast enough so the eyes
are not able to see the trick,
which is pretty much what magicians do.
With all that, thank you.
Here is the repo.
You can check the code right now.
You can ask me questions there too,
and if you want more examples of smoke and mirrors,
these great applications by Nick Butcher's Plaid
has many more examples.
So remember to rate my session, thank you.
(audience claps)