字幕表 動画を再生する
(inspiring jingle music)
(scattered applause)
- Hello, everyone.
Okay, cool, the mic's working.
So, yes, welcome to this talk
about efficient Android layouts.
If you've never met me before,
I've been doing Android drawup for about seven years,
first the Travel App company, the Expedia,
then currently at Trello.
As per usual, my thing doesn't work.
All right, I'm gonna give my presentation over here.
You know, you can click to engage, to rate sessions,
and ask questions, of which I will probably then answer
the questions at the end if you ask them.
Anyways, so this talks about efficient Android layouts,
and when I was writing it, what I found that
I was really interested in wasn't so much so
the efficiency in terms of performance,
but the efficiency in terms of leverage that you have
as a developer, and so I started thinking about it
in the way that Archimedes was referring to, like,
how fulcrums work, where, like, if you just give him
a proper place to stand, he can move the Earth,
and so that's sort of the focus of this talk,
is like, how to get the most leverage
as a developer, because a lot of Android teams
are fairly small, and you're asked to do a lot of things.
And so in my case, it would be if you give me a
a standing desk, I will write you an Android app,
and I hope that you're not expecting me to make
any more terrible jokes this entire time,
'cause it won't happen.
So, this is really just kind of a mish-mash of things,
there's no narrative, so I'm just trying to cover up
the fact that I have no transition here.
Let's talk about ViewGroups.
In particular, picking ViewGroups,
which ViewGroup are you gonna use for
any particular layout, and I think the main thing
is the simpler the ViewGroup you can get away with,
generally, the better, because the more complex ones
require a lot more maintenance, and you can run
into a lot more bugs, and so on the higher end of things,
I'd say probably RelativeLayout's one of the most complex.
ConstraintLayout is yet to be seen, but it looks like
it's probably going to be more complex
than RelativeLayout when it's finally done.
Somewhere in the middle of there is LinearLayout,
and then down there at the bottom is FrameLayout,
which is one my favorites, because it's so simple.
And there's a lot of other views in between,
but these are the main building blocks
for most applications.
So RelativeLayout and ConstraintLayout,
they sort of occupy the same space in Android right now,
which is that they position views relative to each other,
and RelativeLayout's sort of limited in this regard,
but it's what we've had since the beginning,
whereas ConstraintLayout is new and can do,
like, all these amazing things,
but there's some key problems with both of them
besides the fact that they're fairly complex.
One of them is that RelativeLayout is fairly slow,
and then the issue with ConstraintLayout
is that it's fairly alpha-ish at the moment.
They haven't officially released it yet,
So there's alpha out on Maven central, but a few times
they've completely changed the API around, so it's not
necessarily production ready.
I mean, you could play around with it now, and you could
use it if you want, but you're gonna end up with
some of that cutting-edge problems that you end up when
you're trying to experimental technology.
LinearLayout is great, stacking views vertically and
horizontally, you can distribute the weight, so this is a
simple view, so rows are stacked vertically, and then also
I distributed the weight between those
two spinners equally.
I'm actually fairly okay with nested linear layouts
as an opposition to RelativeLayout, and I actually,
last time I told someone this, this was like by far
the biggest question that I got, is everyone coming
to me later saying, "But I love my RelativeLayouts,
that's all I use,
I was then that nested LinearLayouts are the worst
things in existence and so I can't believe that
you just said that."
SO to head off that question that I was going
to be getting on the sessions thing, is that LinearLayouts
are sometimes slow.
So if you use layout wait, and you nest them pretty
deep, then they an get pretty slow, but that's only
a sometimes thing, whereas by opposition, RelativeLayout
always has to do two passes, it's always slow.
So the hope is that eventually ConstraintLayout
will be our savior and save us from the situation
of having to decide between the two of them.
But in the meantime, I think really what's most
important is just to focus on profiling.
So whatever layout that you do end up with just turn
on profile GPU rendering, and see if things are
running fast enough on whatever test device
that you're using, hopefully like
a really slow one.
And if you've never used profile GPU rendering,
I highly recommend Googling that and looking into
that, 'cause then you get these nice bars that
show you whether or not you're hitting
60 frames a second, and what sort of things you're
spending too much time on if you don't.
But really I talked about all that, so I can talk
about FrameLayout, which is my favorite layout in
the world, because it's so incredibly simple.
All it can do is position things based
on the parent bounds.
So that is, you can position things inside the center
of the FrameLayout, or you can position things on one
of the eight cardinal directions of the FrameLayout.
Wait, am I getting that right?
Yeah, eight.
So, but there's a lot you can do with this.
It turns out that a lot of layouts, if you just wanna
have like say, a simple progress bar in the center
of some large screen, like that's a FrameLayout,
you don't have to do anything complicated with
RelativeLayout or what have you.
It's also really great as a simple layout for overlapping
views, so if you need two views to be on top of each
other, FrameLayout is a great container for that.
It's also good for things like clickable item backgrounds,
so if you have some image that takes up a very small amount
of space, but if you wanna have multiple views that
compose a single thing that you click, it's good to have
like a FrameLayout as the parent of that, that can
actually have the click detection so when you click
on it, it actually looks like something is happening.
So a good example of this, like in the Trello application,
is the notification bar in the upper right corner.
So this always present on the screen, it's a single
FrameLayout, and there's a icon inside of it that,
that white icon is always present, and then if you have
unread messages, it'll put that little red thing
on top of it.
And so, the white icon centered and the red icon is
actually pegged to the upper right corner, but then
you can use the margin or to push it in so it doesn't
just ram up against the sides.
And on top of all of that, I can just have these views
be very simply positioned, and then pair with clickable
item background behind that, so when you actually
click on it something happens.
Another thing I really like using FrameLayouts for is
what I'm calling "toggle containers", so if you have
two different states that you toggle between, sometimes
you just have a single view that you actually change,
sometimes I've found it more handy to have multiple
views that you can switch between.
And so a FrameLayout's a good way to contain two things
in exactly the same spot, and then toggle between them.
So a good example of that in the Trello app is
the avatar view.
So this is whenever you represent a member of a card
or something like that, if the user has their avatar
Seth and we wanna show that, if they've never taken
a picture, then we wanna show their initials.
And so it's essentially choosing between an image
view or a text view.
Littler fancier versions of which that allow you to
render a circle, but basically lets you toggle
between these two.
So the avatar view brings up the next thing I wanna
talk about, which is view reuse.
We use this avatar view all over the application,
so these are just three screens, like the Trello
board, a open Trello card, some activity on the side,
and there's actually I think, three or four other
locations we use an avatar view within the application.
And so the question becomes, how do I reuse this
in multiple places without having to rewrite
the code everywhere, 'cause that would be kind of dumb.
So the most obvious way is to use
something called an include.
So if you've never seen it before, the include tag
allows you to point to a direct layout, and then it's
as if that layout was just copy and pasted into the
code right there.
And you can't modify much of what you're including,
but you can modify any of the layout params, that's
any of the things that starts with layout_, so that's
a nice way to be able to include something that may
have been may have been match-paired, but you don't
quite want it to be in the end.
But the problem here is that okay, you get the XML
in every single location, but you don't get
any of the logic.
So now I have to come up with some way to then apply
like, find these particular views that were in the
include, and then add the logic for actually
binding that to the view.
So what I actually prefer these days is
using custom view.
So with a custom view, I call, instead of include,
I actually just have the view in reference directly,
and then you need to write the actual custom view
itself, but it's not very hard, because this isn't
a custom view that's doing custom drawing or
anything like that, it's just taking the place
of what would have been in that include.
And so with this custom avatar view, I'm extending
Framelayout, so I'm saying the topmost is gonna be
a Framelayout, remember I'm toggling between
the two states.
I've got an image view and a text view, and then
inside of a constructor itself, it actually inflates
all the views that are underneath it.
So I don't need to, as a parent using avatar view,
I don't have to worry about what's inside of it.
It's handling all of that for me.
And then I can have this one nice bind method where
I take my member object and figure out whether I
should be loading an icon or loading the text.
So this makes my life a lot easier.
One thing worth noting though, if you're using
this sort of custom view setup, this is like
a very hand-wavy version of what would
be the included XML.
But if you include the XML like this, you end up
with a view hierarchy that looks like this.
You wend up with an avatarview on the top, it's a
FrameLayout, and then it inflates another FrameLayout, which
then has the text view and image view.
So obviously, this middle FrameLayout is pointless,
we don't really need it, the lint check in Android
is particularly harsh when you do this, something like
"has no reason to live" or something like that,
"has no reason to exist".
So we wanna get rid of that, and the way that we do
that is through a layout inflator trick.
Which is normally when you're using layout inflator,
everywhere you'll see it, there'll be a third parameter
there, and it'll be false.
And that's because most of the time that's what you want.
But in this one particular case, you want it to
be true, which happens to be the default.
And when it's true, what happens is that the XML
that's inflated tries to attach itself to the
view group that you passed in as the second parameter.
In this case, it's this.
And then in the XML, if you use something called
the merge tag instead of a FrameLayout, what happens
is, it tries to then merge these views into the
parent view group without any
interstitial frame layout.
And then so you end up with the hierarchy that
you actually want no unnecessary FrameLayouts involved.
A third view-specific piece of advice I had to do is
with custom drawing.
So this is useful in cases of, particularly complex
views, you can can save a lot of time by just
drawing yourself instead of trying to figure out
how to wedge these views into what you want
it to look like into normal views.
So a good example of this in the Trello app is the
labels, so there's these green and blue and red
and yellow and purple labels that are
on these cards.
So when we first had the Trello app out, there was like
six colors, and that was it, that was the most you
could apply to any card.
And whoever was working on it back in the day
did not know about custom drawing, and decided that
those would just be six views.
So that meant that every single card potentially
have six views inflated.
But then later on, Trello changed this, that it allowed
any number of labels to be drawn, so then you could
end up with this nightmare scenario, where every single
card could have dozens of labels on them if someone's
going really crazy.
And then we were talking about recycling those views,
it just gets really slow, and if you talk about
putting something like this on a tablet, it gets really,
really slow because you can see even more cards,
and it's rendering even more views.
So it was much simpler, then to just take all of those
views that were being rendered, and instead, have one
custom view that draws really simple shapes.
So there's sort of two steps to it, and custom views
used to be very intimidating to me, I used to be very
scared of custom views, 'cause I thought they looked
really hard, but they really are not.
And the first step is just telling the custom view
how big it should be.
That is, how much space does it need to take up?
'Cause I have my label view, which is really nice,
but no one knows exactly how much space it's
going to take up.
So on measure is what you use to tell any parent
view group how much space you need.
And it turns out, a lot of the times, you can actually
skip this whole step entirely.
And the reason I say that is because in any view,
you can specify, "I want this view to be 48 dp by 48 dp."
If it turns out that your custom view is just always
going to be the same size, like skip this entirely,
just define it in your XML, and you don't have to
worry about that.
In this particular case because the size varies based
on the number of labels, I had to write my own measure.
And so a quick way of going going through onMeasure,
the message signature that gets called, you have these
width measureSpec, and height measureSpec, which was
sort of confusing to me at first, but it turns out
that these are just packed integers.
So it's a single integer that, these two parameters
basically take the place of four parameters,
which is that a width mode in size and a height
mode in size.
So the size is just a dimension value, the mode though
is telling you how it wants you to handle
that particular size that it passed.
And there's three different MeasureSpecs for the mode.
One is exactly, which means the parent view group
wants you to be this exact size.
The other is at_most, so it take up as much space
as possible, and undefined means you get to define
whatever ideal width you would like in the situation.
And so your typical onMeasure looks something like
this, where you grab, and this is just for the
width, and then you would copy the same code
for the height.
You'd grab the mode and the size, if the measure spec
is exactly, you probably just wanna pass back the
size that it gave you.
You don't wanna screw up the parent view group
too much, or else it might get confused.
Otherwise, calculate what your desired width is,
and if the width spec is at most, then make
sure that whatever your desire width is not larger
than that size.
Otherwise, if it's undefined, you just get
to pick whatever desired width you want.
Then, once you've done this for both the width
and the height, you are by contract required to
call set measure set dimension, in order to tell
the view what you decided for the width and height.
'Cause there's no return value for onMeasure,
you just actually have to call this measure at the end.
So that's measuring how big the view is, and the second
is onDraw, and this one is pretty simple, it just
gives you a canvas, and you draw.
And so I'm gonna leave this up to you, because what am
I gonna say about canvas?
This is not a talk about how to use canvas.
Another thing worth considering, is maybe in some cases,
you don't actually need a custom view, you could just
write your own custom drawable.
And the advantage here is there you could take
this custom-written code and apply it to any different view.
So that's good if you want so me special custom
background of sorts.
In that case, onmeasure just becomes something like
get intrinsic height and get intrinsic width
on a drawable, and then ondraw becomes draw.
But again, I don't wanna spent do much time on this,
you can research more about it later, I highly recommend
Cyril Motier gave a talk about Android drawables a
few years ago, and I highly recommend that talk if
you wanna learn more about that later.
And I'll be posting these slides too, if you wanna
get the links.
Alright, styles, let's move away from views,
well, not that far from views.
But talk about kind of another layer above views,
which is styles.
So if you are applying XML to a view,
this view has no style.
Not because it's uncool, but because there is
no style tag on it.
And then, if you have a style, all it does is I'm
creating some style resources, which has the same
attribute inside of it, and then the view itself
then applies that style on top of whatever, actually,
the style's applied first and then whatever attributes
are applied on top of it.
But essentially then, in the same way that includes
or taking layout XML and just stuffing it into
a view group, styles basically take a bunch of
attributes and stuff it into a view.
And so where is this useful?
It's very efficient when you need to style a bunch
of what I call, semantically identical views the same way.
And so what I mean by semantically-identically is that
each view does exactly the same thing in your hierarchy.
So a good example of this is a calculator, because in
a calculator you want all these buttons, or at least
the main number ones to look the same.
Another way to put it is that all the style views
should change at once.
So whenever you wanna change something.
So if I wanna change the text size of one of those
buttons, my expectation is that all of them
change at once.
So that saves me a whole bundle of time.
I see a lot of people though, myself in the past,
especially, misusing styles in very inefficient ways,
ways that end up biting you in the long-run.
And one way is single-use styles, so that is you have
a view that's representing a style, and that style's
only used once.
I feel like that's just extra work that didn't
need to be there.
Some people really like separating all this code
out, but it's so easy to refactor later and create a style.
There's even a refactoring option in Android Studio
that lets you do this.
So, not really necessary.
But more importantly, is where you have two views
that are coincidentally using the same attributes.
So I've got these two text views, and I say, "Oh look,
they're using the same text color and text color hint,
great, I'll use a style here."
But if you look at the ids, you can tell that these
two mean something very different from each other,
one's supposed to be title, and one's the body.
And so what happens is, suppose later on I
decide, "Oh I want the title to be like
a different color."
Well, if I change the color of the title now, that
also incidentally changes the body.
And so this style which was supposed to be handy
is now just a hindrance, because it's very hard to modify
that style without having some
unintended consequences later.
I liken this to in Java, imagine I have two constants,
one is like the number of columns I'm gonna show
in some grid.
And the other is the number of retries I'll do
in some http request if it fails.
And so I think, "Aw, these are the same value, I'm
gonna optimize this and have a single constant."
And problematic for two reasons, one is that three
is already a constant, but the other is I've lost
all semantic meaning.
These meant something very different; if I wanna
increase the number of retries for http, suddenly now
I've changed how my UI looks as well, incidentally.
So that's mistakes people can make with styles.
So themes are sort of like styles on steroids.
Styles you can apply to individual views.
Themes are essentially things that you can
apply to multiple views at once.
And so that can be a view group, it can be an
activity, or it can be the entire application.
It allows you to apply default styles as well,
so if I come up with, I want all of my buttons to
look slightly different across the app, without themes
I would have to go take that style and actually add it
to all of my XML.
Whereas with themes I can say, I would just like to
have a default style for all buttons, and it automatically
gets inflated for everything.
And then the last lazy thing that it helps you
with, at least in the context of my talk, it allows you
to configure your system-created views.
So if you've got popup windows or toolbars or something
that the system creates, and that's one fewer thing
you have to create.
But before you could theme on a view level, there was
a lot of problems with oh, I have to create some attributes
that affect just this one weird popup, but then it screws
up another part of my app.
But it's very useful for configuring just things that
the system will create.
So there's three ways to apply it, you can apply it like
I said to the entire application, you can apply it to
individual activity, if you do that, it ends up overriding
whatever's in the application.
And on top of that, you can apply to an individual view.
And in the view case, it ends up actually overlaying,
so you can just overlay like a few changes to it,
an individual views theme.
And the view themeing is very, very handy.
I don't know if anyone here's worked in the days
of Holo probably remembers that there was a
holo.light.width action bar, and that was because
there was no way to theme just the action bar part
of the screen differently.
So you have to say in the theme, I want to define
most of the screen to be light, but I want this one
part of it to be dark.
Whereas nowadays, you can say, "I would like just
a light theme," and then manually apply a dark theme
to the tool bar itself, so it makes things so much easier.
So in terms of theming, I highly-recommend people look
into AppCompat, which is one of the support libraries
that Google puts out.
If you're not already using it, amongst other things
it makes theming a lot easier.
For one thing, it gives you material on all devices,
like that's the latest design language from Google,
and without this, there's a lot of subtle differences
between Holo and Material, in terms of spacing, and
also in terms of just the visual metaphors
that they're using.
And so it's so much easier to start from one single
baseline, and then theme from there.
Another thing is, it gives you all these baseline
themes and styles.
So you might wanna change the default look of all
your buttons, but you don't wanna have to actually
go and define a style, which defines every single
attribute that a button has to have.
You just wanna take the main one and tweak it a little bit,
like add a little padding to all of your buttons.
And so AppCompat makes it easy then, to take the
AppCompat button style and extend from that,
and then modify it.
Without that, it becomes sort of a nightmare,
especially between Holo and Material.
And the third really important thing it enables
is that it allows you to do view theming pre-Lollipop,
in XML, and that was one of my favorite things,
because Lollipop had this view theming which seemed
really cool, but I was like, "Oh, but you can't
get it backported."
They actually did manage to backport that all
the way back to I think, some API that you shouldn't
even be using anymore.
I think 11.
Sorry people who are still having to support
apps on 11.
So a few examples of things you can do with themes,
the one that gets touted everywhere is the color
theming, so in this case, instead of having to use
individual drawables for everything, I can just set
up colors, and most of the things in Android will just
get colored automatically.
So bam, it's like a broad brushstroke you can make.
These are some examples of applying default styles.
So, just in case you've never seen this before,
so for example the top line, defines the button style
for the entire application, so that gets applied
to every button.
The spinner item style is handy because what if I
just wanna use the built-in spinner item layout
row that Android provides, but I still wanna
style it a little bit, I can use that here.
Text appearance is nice because then the text
appearance can apply to text views, and then you
can still apply another style on top of that.
Another useful thing you can do with themes is
that you can set up attributes which are then
referenced in your XML.
So in these case selectable item background, which is
like one of my favorite attributes, if you refer to it
with that ?attribute/ instead of the @drawable that you
normally use, then it derives that value from the
theme instead of going to it directly.
So why is this useful, if you happen to have
an app that supports multiple themes, it makes it
very easy then to swap between those values, but more
importantly, your system might have multiple
ideas of what a selectable item backgrounds it's,
because pre-Lollipop, here wasn't any Ripple drawables.
It was just usually just a flat color that you
changed to whenever you click on something, whereas
post-Lollipop, you wanna have these ripples because
it looks really cool.
And so if you use a selectable item background,
then the theme can automatically figure out which
one it wants to take.
Alright, I'm gonna do a quick water break before
I move on to the next section.
Sorry.
Alright, resources, so, resources are you know,
all the things that go into your app that aren't
just pure Java code.
And before I can talk about resources, I wanna talk
about device configurations.
So if we look at this screenshot there's like a whole
bunch of things that one can derive about it in terms
of its configuration.
So for example, I can say it's in portrait orientation,
it's got a height of 731 density in a pint of pixels,
it's got a width of 411 of them, it's a Nexus 6p,
so it's got a density of xxxhdpi, it happens to
be in English right now, the English locale,
so it's showing English U.S. and it's version 24 'cause
it's running the latest n builds.
So these are all things that the Android system knows
about the device, and you can query this all manually
on your own if you want, but actually with resources
you can just have it select things automatically.
And some of these device things will change throughout
execution, some of them won't.
So portrait vs. landscape, unless you're locking
your orientation, that can change very rapidly.
You change the locale, people probably won't change
that often, but they can change it while your app's running.
And then some things like the density and what operating
system version probably aren't going to be changing
while you're running your app.
So what sort of things do you wanna vary on this?
Well landscape versus portrait, I think is a classic
example, because it usually presents a different
mode of operation.
The built-in calculator app, when it's in portrait,
only shows four rows, but when it's got more space
to stretch out, it can possibly show some of the
more cool functions, just by default.
Locale is a very easy one, you wanna have your app
translated in a different languages, you just have
have it select different text strings based on the
locale, so on the left it's in English, on the right
it's in Japanese.
You can have things break on the width of the screen,
so on the phone the card when it's opened is small
enough that it just decides to take up the full width,
whereas at some point, if the device gets large enough,
it just looks kind of ridiculous having it be full
width, and so we start having a break point at some
moment with width.
And another example of that would be like our search
results, we have the staggered grid view, and again,
on the wide tablet it wouldn't make sense to have
a single column, it makes sense to fill it up as much
as possible, and so we can vary the number of columns
based on that.
And then also on the mobile phone you can see the top
result is some small board display, because it's a
small device, whereas on the larger tablet, we can show
the nice big rectangle, which would look nicer if that
actually had a background, but whatever.
So you could do this all in Java code like I said
earlier, but it's a lot easier if you just leverage
the resource qualifier system.
And what this system does is you define alternative
resources for different device configuration and then
at run time Android will automatically pick the
correct resource based on the device configuration.
So it's go through an query everything and figure
out which of the resources you defined makes most
sense in this situation.
And so you define this by the names of the folders,
so in your resources directory if you have something
that's just the default values, that means it has no
resource qualifiers attached to it, it's the default
fallback in all cases.
Whereas if you do a single dash and then a resource
qualifier, so this one has one resource qualifier,
it's xxxhdpi, and you can have multiple qualifiers
if you want.
You can actually apply as many qualifiers as you
want to a single value, although usually it isn't
handy if you do it to many different values.
And one other thing worth noting is that if you do
have multiple qualifiers, they have to go in a particular
order, so look up the documentation, the documentation
has this huge table of all the different qualifiers that
you can use, and you have to put them in the order
of that table for Android to parse it correctly.
So that same documentation page also lists
out the algorithm, but pretty much it's just
like a process of elimination.
It tries to find the most specific resource, given the
current configuration.
So imagine I start with some value, and I've got something
in values with smallest width 600dp, smallest width
means that regardless of orientation, what is the smallest
width that you can possibly have for the device, which
is useful for figuring out kind of the device class,
like tablet versus phone.
And it also has to be in portrait.
So then it would select from this if those are true,
but if it turns out one or the other isn't true,
then it'll start looking to see other things
it can eliminate.
So then it'll look maybe for just the single
sw 600 dp, oh, it turns out the phone doesn't qualify
for that, so then it'll see is the phone in portrait,
and if it doesn't qualify for that, then it'll fall
back to the base values here.
So that's why it's hand to have a default value
for everything, the only thing you don't really need
a default value for is drawables because the way
that Android works it'll automatically scale if you don't
have something in the right directory.
So if you only have xxxhdpi assets and your device
happens to be mpi, it'll just scale everything down,
which isn't great performance-wise, 'cause having to
do all that extra work, but at least you don't have
to worry about that when you're developing quickly.
So in terms of using resource qualifiers in the correct
way, what I think is important is to think
of these resources as code.
In particular, to think of each resource that you're
inserting somewhere as a parameter to some method
function, a method or function, and that the parameter's
determined based on the device configuration.
So for example, if you're thinking about this in terms
of code, the code on the left is insane and dumb
because I'd have to write a new square function per
number that I want to square, whereas the one on the right
has this parameter.
So you wanna think of it more in terms of the things
on the right.
So one simple example, on that I like to use a lot,
is actually letting the resource qualifier system
determine some Boolean logic for me.
So this is a simple one where I basically just wanna
know whether it's in portrait or not.
Yes, you could query this from resources fairly
easily, but this is just an example.
So I could say, "By default, is portrait is false,
and then in particular, when it is in portrait,
then it's true."
And then I can get this Boolean value out.
But this is really handy if you have multiple
different configurations and multiple ways that
that Boolean could run, it could do all that calculation
for you, you don't have to think about it.
A more classic example is using it for different layouts.
So I could say I'm gonna call set content view, and I
have these three different versions of layout, one that's
the default, one that shows up in landscape orientation,
and one that shows up in portrait orientation.
And I made this slide far before I realized it's very
improbably to actually end up ever without it being
in landscape or portrait, you'd have to have a
square screen for that.
But basically, it's select the right one of these,
but would be more clever, 'cause this you'll probably
end up with some duplicated code.
'Cause chances are, there's not that much that
changes between portrait and landscape, so it's then
if you can use that code reuse, the include, and then
it can switch on just that part of the code that changes.
So I've got my LinearLayout, and inside of it somewhere
there's an include, and that's the only part that
changes based on orientation.
So now I can have a single activity main, and I can
have a layout that's the default one, and then the
layout that just modifies in the portrait.
Along the same lines, let's look inside that include,
suppose both those includes have text view which are
supposed to be pretty much the same thing, but all
that they really modify on is what the text size is.
But this again, this seems kind of like a waste
to have two different layouts here, if all I really
wanna modify is the text size.
So here what I can do is I can then reference a dimension,
and then that dimension can be then determined based
on the qualifiers as well.
And then to take this even a step further, let's suppose
we have style somewhere in the application, that's the
same sort of thing, all that modified is the text
size, so I can again, have this be a dimension,
but now this style can be applied all over the
the application instead of applying that dimension
trick to just one particular view.
So in this example, I have an activity main on top,
and then by default to go to to one include, but if it
happens to be in portrait, it'll go to a different include.
Both of those include a text view, which is supposed
to be ostensibly the same between both, so they use
the same style.
And then that style, based on the current configuration
determines what the text size is.
So you can really go pretty deep with this and
write very little duplicated code between all
of your layouts, if all that you're doing is changing
things based on device configuration.
And as an aside, this is why generally speaking,
you shouldn't override config changes on Android.
That's a pretty common beginner way to get
around the problem of, oh, I rotate my phone and then
my activity got destroyed and I didn't want that to
happen, 'cause where did my all my data go?
And then someone says, "Hey, if you just override
config changes, everything works out, and all
of your data stays around."
there's two problems with that, one is that it doesn't
necessarily help you because you probably only override
config changes for orientation changes, but there's
a lot of other ways that the configuration could
change on the fly.
But for two, it means that you bypass this system
entirely, cause you're basically telling the Android
system, "I've got this, don't worry about it."
This whole resource qualifier system is a major
part of the reason why when you rotate your
phone, the activity gets destroyed and recreated again,
because it wants you to re-inflate everything, because
when you re inflate everything something might have
changed based on selecting a different layout.
Alright, drawables, so this is, let's see how much
time I have, good.
Alright, so drawables is the last section I wanna
talk about, and I wanna outline sort of a nightmare
scenario that you may or may not have gone through.
I certainly gone through this, many times.
So imagine I'm interacting with design, and they
send me a mock-up of a new screen.
And in particular this is the login screen, and they
wanted to add this login with SSL thing at the bottom.
So then I look at this, and I start working on it,
but then I tell design, "I need this asset, 'cause I'm
not good at design or anything, I need you to give
this to me."
So design says, "Okay, sure, no problem."
So they send you over a zip file, you unzip it,
and you get this one file that is who knows how big
this is supposed to be.
So you tell design. "Okay, this isn't enough, I need
more than this, I need one in all the different densities."
And design says, "Oh sure," they go do some research
on how that works, and then they send you back a
file like this, and a zip that contains this.
So now you've got all the assets that you need,
but then you have to go through an rename everything
and put it in the right folders, and then import
into your project, it's kind of just a pain.
And that's a real pain to do with every asset,
and then the kicker on top of all of this is that
at the very end, design says, "Actually, I wanna
tweak the color, and here's a new set of assets."
And so now you have to go through this whole process
again, and it's a gigantic pain in the ass.
So my recommendation here, I've been working with
the design team at Trello, and we figured out a whole
bunch of ways to reduce all of this pain and all
this friction.
And it's just made things so much simpler, and it's
basically to think of assets as code, as much as possible.
Don't think of them as bitmaps that you get from
design as much as you can, think of it as things that
you can execute in your application.
Because then it's so much faster to tweak and change
things on the fly.
So the first example of this is drawable XML, which has
been in Android since the very beginning.
And drawable XML are resources that you can define,
you can do things like draw simple shapes, you can do
things like set up state selectors, so that's where
if you press a button and it looks slightly different,
that's a state selector.
You can use it as a layer list, and this is really
handy because if you have two drawables that you
actually wanna layer on top of each other with multiple
states in between both of them, you might have thought,
"Okay, now I need to design to composite all of these
images for me."
Actually, you can just set up a layer list, and then
change the two layers independently, you get that
nice composition going on.
So a detailed example for this are the login buttons
that I worked on once.
And so these login buttons are entirely done
through drawable XML.
So the first step to making these login buttons work is
that I need to create that button outline.
And the button outline itself is just its own file.
So we're not worrying about the click state right now.
And it uses a shape drawable, the first important part
is you can tell it what type of shape you want, you can
also make ovals and stuff like that.
But you're limited to very simple shapes with drawable XML.
I wanna say that it's mostly filled with transparent
space, in fact, that should be the default, but on some
older versions of Android it was not defaulting to
transparent for solid, I don't remember exactly why.
Then I do want that white outline though, so I give it
a stroke, which then determines that outline,
and I would like to have a small radius on it so it
gets that nice little pretty button look.
So that's just creating the outline, the blue actually
comes from the background of the entire screen, I just
put it there 'cause otherwise it would be really hard
to see with my white background on the slides.
So then we need to add some behavior to it.
When I click it, I wanna actually be able to tell
that I clicked it, and so we need to add a selector
to it, which you can see in this beautiful little
two frame GIF.
And so the way that I'm doing this is actually layering
a selector on top of that outline I just talked about.
So I'm using a layer list, and that layer list allows
me to take two drawables and put one on top of the other.
And I'm saying the top layer is that outline that I just
showed you, that's the previous code that I showed
you, is what's going on top.,
So that's always going to be drawn.
And then the other layer is a selector, and the selector
just has two states in it, and one of those states
is when it's pressed, then I want you to draw
this other shape.
So again, I'm using another shape drawable in order
to determine what should be drawn inside of it,
and in this case it's a little simpler, 'cause I can
just say, "I want you to have a solid color, but then
again, also have the corner so it doesn't end
up bleeding out of the corners."
And then when it's not pressed and in the default state,
then it'll just be transparent.
So that's great an all, but then in version 21 of Android
they added these nice ripple drawables which look really
pretty and doing that requires a while different
set of code.
And so for that, you end up using something that's
a ripple XML, something we added in version 21.
And that inside there is where you say what the color
of the ripple should be.
And then after that, I add, okay, I still have the same
button outline as what's actually being drawn in that
ripple, but then the last part of this is that I
define a mask.
And that mask basically says, "This is the outline
of where the ripple should appear."
And so then the actual solid color inside of it
doesn't really matter, it's just the fact that this
drawable will draw to this particular area that matters.
And so I was able to get away with all of this and
have a different version for different versions of Android,
by using the resource qualifier system, so at the
bottom there's this outline that I'm always going
to be using, but then the default button uses the state
selector, which will always work all the way back to
version one of Android, I think.
And then drawable v.21 for that ripple drawable,
I can use there.
So drawable XML, a fairly good way to skip a lot of work.
And then I just had to ask design for the colors, I didn't
have to ask them for anything else in particular.
Vector drawables, so like I said, the shape drawables
lets you draw very simple shapes, nothing complicated.
Vector drawables lets you do any sort of vector
drawing you want, or most vector drawing that you want.
And so it allows you to do very complex shapes, and
the advantage here to using a vector is that you
don't have to then worry about density of the
screen all that much.
'Cause before I was having to get these PNGs from
design that was in all the different densities,
here they can just give you a single vector,
and then it's automatically drawn at whatever's
the best resolution for that screen.
So that's a huge time-saver, but there is one
problem with the way that Android
implemented vector drawable.
Oh, so another point is that vector drawables
were added recently in Android, but there was
a back compatible library in the support libraries
for using vectors all the way back to I think,
14 or something like that.
But there is a big problem with the way that Android
did it, which is that they came up with their own
vector drawable format that is not actually SVG.
And if your designers are anything like my designers,
they know how to speak SVG really well, all of their
tool know how to output in SVG, and none of them know
how to output as vector drawables.
So you need some way to convert these SVGs that your
designers are giving you into vector drawables in the app.
So there's sort of two ways of doing that, yeah, sad
design, there's two ways of doing this.
And one is in Android Studio you can say, "I want a new
vector asset," and that'll bring up this nice little
wizard, and then you can pass in the SVG and it'll
convert that into a vector drawable as best it can.
There are some SVGs that it doesn't work very well
with and won't convert.
So that's good, but I still am even lazier than this,
because I don't wanna have to go through a wizard
every time I import a new asset.
So instead, we wrote his before all the vector drawable
back compat stuff happened, but we're still using
it, which is this Android plugin that we
wrote called Victor.
And what Victor does, is you define any number of source
sets, anywhere that you have your SVGs, and it'll just
slurp all those up, and then output something that
the system can render.
And for awhile it just output PNGs but then eventually
we were able to get to actually grab the code that
is in this new vector asset stuff and then use
that to convert it straight into vector drawables.
So that's great because actually with Trello,
our designers have their own Git repository, which is
where they put all of their compiled SVGs, and then
we can just have that as a Git submodule, and import
it, and then we just have to update a commit pointer
to get new assets from design, that's great.
And then so the last thing I'd like to say about
drawables that has really saved a lot of time
recently, is that difference between skeuomorphic
and flat design.
So skeumorphic design is where you have things
that look exactly like what they're supposed to be,
and so on the left is Andie Graph, which is an app
that my friend wrote, which makes your phone act
exactly like any TI-83, Ti-84, whatever,
and look exactly like it.
So it looks very realistic, that's very skeumorphic,
whereas on the right you have the normal calculator,
which is flat, and every button is just this flat color.
And what's really nice here is that as nice as the
thing on the left looks, on the right all the icons and
all the text is just a flat color.
And what that means is that it's very easy to tint
those colors and change them on the fly.
With the buttons on the left, it would be very
hard to tint them in any way that would be reasonable.
So actually, in the Trello app, all of our assets
are flat black colors.
So they're black on alpha, and then in code, we take
any of those, and tint it whatever color we want.
And so that's super-handy from the perspective of
design, because they don't have to create multiple assets
for whatever color they want.
Every time they wanna change the colors, we can just say,
"Oh, that's easy, let's go change it in code,"
and then we're done.
So in terms of tinting images, there's sort of a few
ways to do it, one is to do it via XML, but besides
the fact that image view has had this tint attribute
forever, which doesn't quite work all that well,
it's not backwards compatible.
It got added into recent versions of Android to be
able to tint drawables in XML, but they haven't figured
out any way to actually backport that functionality.
So I ended up doing actually most of the tinting
in code, it's very simple with image view and drawables,
you can call set colorfilter, and then you just pass
in the color.
And then it turns out that for a black icon on alpha,
you wanna use the Porter/Duff mode of source in.
That's actually why the XML imageview of the tint
attribute there, I don't like it very much, 'cause it
uses a Porter/Duff mode that isn't compatible.
I think you have to create white icons on alpha,
which we're not doing at the moment.
Now if you want a really comprehensive solution,
this exists in the support libraries is drawable
compat, and with that you actually wrap the drawable,
and then you can call set tint, or set tint list
on that wrapped drawable.
And main advantage that has over just calling color
filter directly is that it can handle tint lists,
so you can have multiple different selected states
for that wrapped drawable in color, and you can
tint all of it equally.
But since we're not actually using that in the
app, we don't end up doing that very often, and so
set color filter's just a faster, easier way of doing it.
Anyways, so that was all that I've got.
This was a slide I was told to put up as well,
to let us know what you think, click the happy face
if you liked it or the sad face if didn't like it.
And thanks very much for coming to listen to the talk,
the middle link is to my blog, where I've written
more about some of these things and then some
parts of this talk were taken from other talks
that were more detailed, in particularly the styles
and themes, I went into much more detail on that.
So you can look at my old speaker deck to find those
talks if you're more interested in learning some
of the more nitty-gritty details there.
And thank you very much.
(audience applause)
Were there any questions?
- Yes, actually there was one of them, and the
question was, whether performance was lost in virtual
machines and the.
My take on it is that, well, the question might
mean that if you use like an emulator, and you're doing
this, you'll have a slide with the graphics performance
in different layouts, will it affect emulators, do you
know that, if?
- So yeah, so let's see here.
- I guess that's what--
- I think you're talking about this one--
- [Interviewer] (Mumbles) Yeah.
Could you actually find out that your layout
are kind of--
- Through an emulator?
No, I'd use a real device for this.
- [Interviewer] Yeah, you'd use a real device.
- Yeah so I wouldn't, for things like profiling,
the emulator can really make things awkward because
it could be the case that you have this GPU, if you're
working on a Windows desktop or something, you're gonna
have a GPU that's bigger than your phone,
running the rendering.
And that's gonna throw things off a little bit, obviously.
Or it could be the case that whatever, I think it used
to be that it was translating from ARM into x86,
and then that would be really slow, and so all the CPU
operations would be really slow.
Basically yeah, I would use an actual device for all
sort of profiling purposes.
- Okay, and do you, you touched upon
this new ConstraintLayout, is that something you
played with or?
- Yeah, I played with ConstraintLayout.
- You believe it's the savior, or?
- I hope so, 'cause it does a lot of things
that, it's sort of like this composite of RelativeLayout
and LinearLayout right now, that allows you to create
very complex layouts without having too much nesting.
And so the hope is that you'll be able to create
these complex layouts without much nesting, and that
through the constraint system, it can better figure
out what are the more performing way to lay
things out quickly.
So I'm hoping that it saves us from having to
use RelativeLayout more often. (laughs)
- [Interviewer] Yeah, I think you have more questions.
Please come down and talk with them afterwards.
- Yeah.
- [Interviewer] But thank you very much, Dan, and remember
to rate the session and I can see some of the speakers
here from the Android track, so thank you very much,
it has been a pleasure.
And yeah, please give him a hand.
(audience applause)
- Thank you.