[MUSIC PLAYING]
ALAN VIVERETTE: Hi, everyone.
Thank you for joining us at
Best Practices for Themes
and Styles.
I'm Alan.
NICK BUTCHER: Hi.
My name is Nick Butcher.
ALAN VIVERETTE: And we're going
to be talking about some ways
to think about themes and
styles at a high level
and how to apply
those when you're
designing themes and styles
for your application.
So first, let's start
at a high level--
the way that designers
create mocks and the way
that developers are
interpreting them.
So here's the latest iteration
of the Material Design
theme palettes.
So if you're not using
MaterialDesignComponents
for Android yet, you may
recognize Primary as the Color
Primary attribute from
the Material theme
in the platform, Primary
Variant as something
like Color Primary Dark.
And when you get a mock
from your designer,
you may be doing a mental
mapping of, oh, well, this
is going to be my primary
dark color in my theme.
This is going to be
the primary color--
embedding those
directly in your theme.
But it's important to make sure
that your designer understands
that there are these
canonical attributes.
So if you're getting a mock that
just has a hex color specified,
it can be helpful to maybe
push back a little bit
and say, let's call this
the Primary Variants.
Or let's call this
the Primary color.
And it's great that we have
all of these predefined theme
colors with meaningful names and
meaningful contrast ratios that
have to exist between
foreground and background
and additional colors
in the Material palette.
Occasionally, you will
get a brand new color that
doesn't exist anywhere, and
finding a name for this may
be difficult. So here
we have purple color
that's a little bit lighter
than our Primary Variants.
And it has contrast
against white,
so we're going to be displaying
white text on top of it.
If we want to come up
with a name for this,
we should find something
that can generalize but is
self-documenting and
captures these constraints
so that if we decide, well,
we had a purple theme,
now we need a blue theme, we
can reuse the name of that color
and reuse those
constraints, so you
could move from purple
to blue very smoothly.
So what are we
going to call that?
Well, over in the
Material palette,
we have these On colors.
On Primary means this is a
foreground color displayed
on a primary background,
and we ensure
that it's going to meet certain
accessibility-related contrast
ratios.
So you'll be able to
read the foreground text
color against the background.
We have On Secondary, which does
the same thing for Secondary.
So if we're displaying something
on that light blue color,
it would be black text.
If we're displaying something
on this purple color,
it would be white text.
So we can use these to generate
the new name for this lighter
Primary color.
What should we call that?
Well, we're going
to use On Primary,
so that determines the
foreground color protection.
For background
color protection, we
want to make sure
that it contrasts
a little bit with Primary.
So we'll include both
of these in the name.
We can call it something
like Primary Light, which
seems incredibly
straightforward, and it is.
So you can tell
your designer, hey.
Every time we're talking
about this lighter
version of our Primary
color, let's refer
to that as Primary Light.
And it's going to
have these constraints
for foreground colors and
background color protection.
So if your designer
decides, well,
in this next screen we want it
to be blue instead of purple,
we can continue to use the
same constraints on this color
and use our semantically-defined
color name in our theme
as well.
All right.
Similarly, we provide common
patterns for text appearances.
So here you see on the left
we have Headline, Overline.
Body.
These are
semantically-defined terms.
They're meaningful names for
where these appearances are
going to be used, and that
determines the pixel size.
That determines
the font weights.
That can determine
the font itself,
and that's going to
determine the color.
On the right, we see what
these values actually map to.
So in this case, Headline
is going to be Roboto.
And in fact, we're using Roboto
throughout our entire app.
However, if we decided that
instead we wanted to use,
let's say, Comic Sans, we
could make our Headline map
to Comic Sans.
We wouldn't have to go
through and create a new style
everywhere in our
application because we
do have these names that
imply how they're used.
So always make sure to use
meaningful names when you're
extracting patterns
from your mocks
and applying those in
your implementation.
So learn the Material
Design attributes.
They're documented
at material.io.
Make sure your
designers learn them too
and use this as
a common language
when you're looking at mocks.
And when you get
something new that
may have a random
gray color in it
that you're going to see
throughout your mocks,
make sure it has a name that
you can use over and over again.
And if you happen to be
implementing dark mode
for anybody who attended
that talk earlier today,
you'll know that this gray
needs to turn into a light white
because it needs contrast
against a certain text
color or a certain
other background color.
Use meaningful attribute names.
They should be more or
less self-documenting.
Something like
Primary Light is going
to have more
constraints than just it
looks sort of like
the Primary color,
and the text color should be
consistent with Other Primary.
So go ahead and document
these somewhere.
Start a doc that
you share or start
an internal site that you
share with your designers
to make sure you're always
using a common language
when talking about the
components, the colors,
the reusable text
styles in your designs.
You'll expand beyond what's
available in Material,
I'm sure.
But that's a good
starting place to get
a sense of how to name them
and how to talk about them.
And part of that is
avoid hard-coding values.
So when you get a
mock that just says,
this is the hex color
4A36EF, make sure
that you push back
on that and say, no.
This isn't the hex color.
This color has some
sort of meaning,
and we need to be able
to smoothly transition
from this hex color to a hex
color with similar constraints.
All right.
So now Nick is going to
talk a little bit more
about what this means
in implementation.
NICK BUTCHER: Thank you.
Right.
So we talked a lot
about about supporting
different semantically-named
values in your applications.
And what that
means on Android is
essentially it
can be implemented
as theme attributes.
And Alan said something
very interesting there.
He said, what happens
if your design changes,
and all of a sudden
instead of a purple theme,
you have a blue theme?
And how do you apply that?
These semantic names
protect you, almost,
against these cases.
And it's actually
more widespread
than you might realize, and
I'll dig into that a little bit.
So if you want to have
areas of your app or screens
which can support
this kind of theming,
there are different ways
you could achieve this.
One way might be that you just
say, define different styles.
So in this one
scenario, I'm going
to set up a style, so give
it a certain appearance.
We'll call that style 1.
And in another
scenario, I'll want
it to look different, so I'll
give it another style-- style
2.
The problem with this approach
is that there's probably
going to end up being
a lot of duplication
between style 1 and style 2.
And there's now two places
for you to maintain,
or bug fix, or maybe
refactor at some point.
Using these semantical
pointers to describe what
the appearance should be like--
or in coding terms,
it's going to end up
as a theme attribute--
protects you against this.
So while it might be more work
to establish this language
and to set up your
styles to refer back
to theme attributes,
what it does
is it actually ends up with just
a single place, a single style,
that will refer back to the
theme attributes defined
for a given theme.
And the benefits of
this is it's going
to reduce that duplication.
You don't end up with two
styles which are 80% the same
but have the
potential to diverge
in the future, which they will.
It also localizes
the modifications.
So that means that if you
need to change an attribute,
you just go to that one
place where it's defined
and change that rather than
having to track it down
where it's leaked out into
all the variety of styles
you have across your code base.
It also has the benefit,
I think, of consolidation.
How many times have you
opened your colors.xml file
and found 50 different shades
of very similar-looking colors?
And then you see a mock, and
it's not quite the same color.
And you go, I don't know.
I'll just create
another color resource.
Or is it just me that does that?
It's protects you against this.
You really, really,
really want to condense it
down to this small palette
which is used consistently
throughout the applications, so
you have a nice look and feel.
So to sum up, basically
this approach really,
really helps you with the
maintainability of your app.
And so as such, I really urge
you to prefer theme attributes
where possible.
By which I mean, if you find
yourself writing code like this
top line-- so here it's saying
the text color should be this
color resource--
almost think of that
as a code smell.
Just think for a second.
Hang on.
Is this layout-- is
there a chance this might
appear in a different theme?
Could this be included
in a different area
of the application?
And if so, should I instead be
looking at this bottom form,
using the ?android:attr syntax,
which is how you refer to these
theme attributes.
So we talked about
the importance
of having these
semantical pointers.
But how might your theme vary?
So hopefully,
everyone's familiar
that you can set a theme
directly on an activity.
So in this example,
two activities
are using MaterialComponents,
which is a dark theme and then
a light variant.
Or perhaps you're
using Night Mode,
like Alan mentioned
before, and was also
talked about in the course of
the Pixel talk earlier today.
So this is a theme which will
change between dark and light
depending on the time of day.
But what happens if
you have a screen
like this, which is from--
this is the Google I/O App--
where we have largely
similar content?
So most of these list
items are the same,
but the design called
for some of them
to be light on dark and
some to be dark on light.
I mean, you can think of
ways you might implement this
as separate layouts or perhaps
setting text colors on bind
or something like that.
But both of those
basically either leak out
the sliding information
beyond where
you want it or lead to an
explosion of more maintenance.
Instead, what you
properly want to do
is look at applying
themes at the View level
rather than the
whole Activity level.
So the Theme attribute was
added-- not Theme attributes,
the attribute android:theme--
was added in API 21 but also
back-ported through
Android X. So in this,
you can set a theme on a View
or a View Group and apply
a different theme to a
subsection of your View
hierarchy.
So in these examples
here, you might
say you have a light theme and
then apply a dark portion of it
or vice versa.
You can do the
same thing in code
if need be by using the
ContextThemeWrapper, which
takes an existing
context which has a theme
and overlays a
style on top of it.
I think it's really important
to emphasize that fact
that it is overlaid.
That means that you
have an existing theme,
and any values from the
theme you set on top of it
are going to be applied on top.
So you need to be a little
bit conscious of this fact.
You don't want to
overlay a theme which
supplies too many values.
So for example, if I was using
just MaterialComponents.Light,
and I overlaid
MaterialComponents.Dark,
they define a lot of
the same attributes.
And you might be relying
on some of the attributes
you set in the first
theme to still be present,
so you might overwrite too much.
Conversely, you
want to make sure
the theme you are overwriting
sets the things you need.
So as such, you might want
to take a look at these theme
overlays, of which there
are some in AppCompat
or MaterialComponents,
or create your own theme
overlays extending from them
just to theme the attributes
that you want such
that when it's
overlaid on top of
the existing theme,
you get the resulting
combination of themes.
One thing to be
noted as well is that
while the ContextThemeWrapper
approach looks tantalizingly
close to dynamic
theming, it's not--
by which I mean that you
have to apply a theme which
you've defined at compile time.
You can't use this
technique to, say,
take some server derived
color values or whatever
and create a dynamic theme.
This is all about this
ahead-of-time thinking.
OK.
ALAN VIVERETTE: All right.
So before you start setting
a bunch of theme attributes,
it's important to understand
how those are applied
throughout the platform.
So we're going to
do a whirlwind tour
of the layers of
indirection that
occur between what
you see on screen,
and what's defined
by the platform,
and what you're setting in
your application themes.
So at any point in
your application,
it's good to have a
background understanding
that you can point
at something onscreen
and understand all of the
layers of indirection,
all of the theme attributes
and styles that were applied,
to render it this way onscreen.
So if we want to set this
color for a single button,
how would we do that?
Well, let's dive into
the way that this
gets resolved at runtime.
So we have Button
defined in layout.xml.
That gets inflated
in the Button class.
If you watch the Demystifying
Themes and Styles talk
that Chris Baines and
I gave two years ago,
you have a complete
understanding
of how this works.
If you haven't watched
that, go watch it.
The important part is that the
Button style that you see here
is pulled from the theme of the
context in which the button is
inflated.
So relevant buttonStyle
attribute here
at the bottom of the screen.
Next, where is that
buttonStyle defined?
It is defined on
the Material theme
if we have not
redefined it otherwise.
So the default value
there that you'll see
in themes_material.xml is
Widget.Material.Button.
Where do we find the definition
for Widget.Material.Button?
Over in styles_material.xml.
So we can see here
the background
is defined to be this
btn_default_material drawable.
We'll also notice that right
below that bordered ink button
style, we have a colored
bordered ink button style.
Which, if we've looked
over the Material spec,
we realize is
probably going to be
a little bit closer because it
provides a color in the button.
So let's dive into
the background that's
used there to see what
gets pulled from our theme
and what we might
be able to change.
In the btn_colored_material.xml,
you'll notice the XML file
names match up exactly
with the drawable names.
So there's an easy chain
that you can follow.
We have a shape.
It's a rectangle.
It is colored with
a tints, which
is our
btn_colored_background_material.
This gets applied to the
white color of the shape,
so it's got a solid
white fill here.
So let's dig into
that tint and see
what's going to be applied
on top of that white color.
It is our colorAccent.
So this is a color state list.
It is named btn colored
background material.xml,
which is the exact name of
the color that we were just
looking at-- again, a very
clear line of indirection.
So where does this
color accent come from?
Well, because it's
a theme attribute,
it's going to come from whatever
exists in our application
theme.
As I mentioned, that
inherits from Material.
And if we haven't
redefined colorAccent to be
anything else, it will
be the default teal.
So here are all the
levels of indirection
that we've just looked at.
Some of the important
ones are, if we
want to change the style for
all buttons that get inflated,
that's our buttonStyle in
the theme that we could set.
The buttonStyle that
we're getting by default
is Widget.Material.Button.
We can change that to something
else like Button.Colored
in our theme if we wanted
colored buttons everywhere.
We can also change
our colored accent,
which is what gets loaded
by the Colored Button style.
So if we set that in our theme,
that gets used everywhere.
We'll see that color everywhere.
But what we want to
do is one button.
So what would happen
if we sent buttonStyle
on our Activity theme?
We would see colored
buttons everywhere.
We don't want to do that.
What would happen if we set
it in the style attribute
on our Button in XML?
We would see it only
on that single button,
which is what we want.
But now we're getting
the default teal
color from our theme,
so how can we fix that?
Well, what if we set colorAccent
on our Activity theme?
Now we're getting
that blue button,
but we're also getting blue
switches and blue everything
else that inherits colorAccent.
And if you just do a search
over the platform.xml files
for colorAccent, you'll
see it in a lot of places.
So instead, we can set it in--
we could try setting
it in layout.xml.
You'll notice that this does
absolutely nothing because it's
a theme attribute,
and you shouldn't
try to set these attributes
directly in layout.xml.
Theme attributes get
set in your theme.
So what we'll actually do
is create a theme overlay,
as Nick was just talking about,
that sets just that color
and contains nothing else.
We will apply that to the
button using android:theme
in layout.xml, and we'll
get what we wanted.
So back to Nick for
more implementation.
NICK BUTCHER: Cheers.
So I said you should prefer
theme attributes where you can.
But to do so, you need to know
what is out there, what exists.
And honestly, I don't have a
great answer for this for you
other than looking
through the attrs of--
this is from the
Android platform--
or from AppCompat or
MaterialComponents.
So looking through these,
they're not that long.
You can see what is
available for you
that you can reference
rather than hard-coding.
Conversely, you can look at
some of the common themes like
themes.Material and see which
theme attributes they set--
and as such, you are able to
reference or override yourself.
So it's good to have
a look through these
and get an idea of
what's out there.
One note is it you might see
that some theme attributes
are double defined.
So the platform defines a
Android colorControlHighlight,
and then an AppCompat
defines its own
in order to provide backward
compatible functionality.
So which do you use?
Basically, the answer
is always, if there
is one defined in the
libraries like AppCompat
or MaterialComponents,
prefer that because they
will tend to set the
platform one for you
and then be available for
backwards compatibility and so
on.
You can, of course, refer to
these theme attributes in code
using something like
this where we're
obtainStyleAttributes
to get hold of it
and then using KTX's
getColorOrThrow
to make it a little
bit more convenient.
So so far, we've talked
about using platform theme
attributes, and you
absolutely should.
But you can also
create your own,
and you should do that as well
to make your applications more
themeable.
Let's walk through an example
of what this looks like.
So here from the Dev
Summit application,
we have two screens which
display very similar layouts--
a list of sessions
in both schedule
and on the speaker details.
They only differ
in basically how
far some space on the left
and the keyline, essentially,
that we want to align it to.
One needs to leave room for
the sticky header time things.
So to accomplish this,
we defined our own theme
attribute.
So here this is in our attrs
called sessionListKeyline.
And then in the different
activity themes for those two
screens, we can provide
different dimension values
for that keyline.
And then we just have to have a
single layout which references
that theme actually built
using the ?attr syntax in order
to vary it without having to
create separate layouts which
you then have to
maintain, et cetera.
Another place that theming comes
in absolute massive handedness
is theming drawables.
So we looked an example
from the platform button
about how from API 21 onwards,
all platform drawables
support tints and tint modes.
So here we're applying
the colorButtonNormal.
You can also do this in
vectors where they support
and theme attributes
both for tints as well as
for fills and strokes.
So you can use this not
just from 21 onwards
but in the backwards
compatible library as well.
And if you need to apply tints
in a backward compatible way
to exist on prior to 21,
you can use DrawableCompat
like this in order to set some
tints on an existing drawable.
DrawableCompat also
offers an override which
lets you set a tint list, a
color state list to be applied,
which is something
we have learned
is to be extremely handy.
So like the example
we will let up
before, you can define
a single color state
list which provides
these different tints
or colors for different states.
So this example is
taken from Text Primary.
And one of the things I
love about class state lists
is some of the
improvements that arrived
in Marshmallow 23, which
allowed you to separate out
the color and an
alpha component.
Why this is great is because
this really protects you
against that explosion of 20
different opacities of black
you might have in
your colors file.
By actually separating
out the color information
from the alpha
information, allows
you to define more
specifically what you want.
So here we're saying-- say this
is a light screen, so Color
Foreground will be dark.
You can say the text
should be black,
and it should have these
different alphas depending
if it's enabled or disabled
in these two examples here.
So you don't have
to have to find all
these different combinations.
You define specifically the
semantically-named things
like Disabled Alpha,
Primary Content Alpha,
and Foreground Color
separately, and then they
get combined by the color
state list at runtime.
One thing to note
is I've actually
cheated here and changed the
Android Alpha to App Alpha
because AppCompat back-ports
this new function,
new features added
to color state list.
But it requires you
to use App Alpha.
One thing to note is that the
alpha channel of the color
and the alpha combined.
They're and multiplied.
So be careful.
Don't do this, essentially.
If you specify this is, say
a 50% white text if you don't
speak hex, and a
50% alpha, you'll
end up with a 25% alphaed white.
So you probably want to refer
to colors which have full alpha
and then only supply variable
alphas via the alpha channel
separately.
And like I said,
AppCompat back-ports
this, so use AppCompat.Resources
to inflate that
in order to get this
backwards compatible behavior.
And then you can apply
those to drawables.
So overall, if you
apply this theming,
you can get to a
state like this.
So on the left-- these look
quite similar, but on the left
we have the Google
I/O app, which
we forked to create the
Android Dev Summit application.
And to retheme it to
fit in with the look
and feel of this
conference rather than I/O,
this is basically the diff.
Essentially, we
just had to change
a few of these
semantically-named colors.
And if we go back, it
just ripples outwards
to all the components.
So the fab changes.
The bottom nav changes.
Some of the tints on
drawables changes.
The star color changes.
Think about if your product
manager comes to you and says,
hey.
The company's had a rebrand, and
we're changing all the colors.
Wouldn't you rather just have
a diff that looked like this
rather than trying to go through
every layout.xml or colors file
and work out which color is
which, and where it's used,
and can I change this, and
where is it going to affect?
You really want to
limit the changes
to these semantic names.
So as a summary of this,
really, really theme
attribute all the
things, essentially.
Use them as protection.
And you should probably push
that kind of semantic system
back upwards to your designers
so that they are giving you
values that work with this.
In your layout.xmls, prefer
that theme attribute syntax--
the ?attr syntax.
Or be careful when
you're not writing
that, thinking is this a smell?
Should I be doing
something else?
And with your drawables,
think about using tints
to protect yourself against
these changes down the line.
Just think if all those
drawables had been PNGs
instead, and we had a rebrand.
Am I going to have to completely
generate all these PNGs again
with a new color which
is already baked into it?
Ignore that.
Don't do that.
Use tints instead.
Basically, if every
time you use a PNG,
think, should this be a PNG?
Is it going to be usable
or tintable later?
Maybe prefer a vector.
Do prefer vectors.
Pretty much the
only time you should
be using PNGs is for 9-patches.
So that's the summary.
Protect yourself using semantic
names and theme attributes,
and have fun with
themes and styles.
Cheers.
[APPLAUSE]
[MUSIC PLAYING]
