For this lab, you will learn to use Android Styles & Themes to easily modify and abstract the appearance of an app's user interfaces---that is, the XML resource attributes that define what your Views
look like.
- I recommend you keep the above documentation guide open as a reference, though this lab will walk you through the steps as well.
Check out and open the code as normal. You'll be working almost exclusively with the XML resources (e.g., res/layout/activity_main.xml
), so make sure to read over that first: it includes a very simple screen showing a pile of TextViews
organized in a RelativeLayout
.
If you look at the TextViews
, you'll see that they share a lot of the same attributes: text sizing, width and height, boldness, etc. If I told you that all the text should be bigger (e.g., for readability), then you'd need to change like 6 different attributes, which is a lot of work.
Enter Styles. Styles encapsulate a collection of XML properties, allowing you to define a set of properties once and then use a single attribute to apply all of those properties to a view.
- This is almost like a CSS style, but without the "cascading" part.
Styles are themselves defined as an XML resource---specifically a <style>
element inside the res/values/styles.xml
file.
-
This XML file was created for us when Android Studio created our project. Open the file, and you can see that it even has some content in it!
-
Style resource files, like String resource files, use
<resource>
as a top-level element, declaring that this XML file contains (generic-ish) resources to use.
Styles are declared with a <style>
tag, which represents a single style. You can almost think of this as a "class" in CSS (though again, without the cascading behavior).
-
The
<style>
element is given aname
attribute, similar to how we'd define a CSS class name. Names are normally written using PascalCase; they need to be legal Java identifiers since will be compiled intoR
, and since they are "classes" we capitalize them! -
We'll get to the
parent
attribute in the next part.
Inside the <style>
element we define <item>
elements. Each <item>
represents a single attribute we want our style to include. This is similar to each item being a property of a CSS class.
-
<item>
elements get aname
attribute which is the the name of the property you want to include. For example:name="android:layout_width"
to specify that this item refers to thelayout_width
attribute. -
The content of the
<item>
tag is the value we want to assign to that attribute, e.g.,wrap_content
(not in quotes, because the content of an XML tag is already a String!) -
Again, see the resource documentation for more examples.
Finally, you can specify that you want a particular View
(e.g., in your layout
) to have a style by giving that view a style
attribute, with a value that references the style that you've defined (using @style/...
, since this resource is of type "style").
- Note that the
style
attribute does not use theandroid
namespace!
Define a new style (e.g., TextStyle
) that defines the attributes shared by the 6 TextViews
: the text size, the with, and the height. Additionally, have the style define the text color as UW purple.
Refactor these TextViews so that they use the style you just defined instead of duplicating attributes. See how much code you've saved?
After you've done that and demonstrated it works, go ahead and change the size of all the TextViews
to be 22dp
. You should be able to make this change in exactly one place!
This is a good start, but we still have some duplicated attributes--in particular, the "labels" share a couple of attributes (e.g., they are all bold). Since each View can only have a single style and there is no cascading, if we wanted to create a separate "TextLabel" style, it would end up having some duplicated attributes between them (size and color). It would be nice to not have to redefine a style if it only changes a little bit.
Luckily, while styles don't cascade, they can inherit from one another (a la Java inheritance, e.g., extends
). We can do this by specifying the parent
attribute for the <style>
, and having it reference (with @
) the "parent" style:
<style name="ChildStyle" parent="@style/ParentStyle"> ... </style>
This will cause the ChildStyle
to include all of the <item>
tags defined in the parent
style.
- We can then "override" the inherited properties by redefining the
<item>
you want to change, just like when inheriting and overriding Java methods.
When inheriting from our own custom styles (e.g., ones that we've defined within the same package), it's also possible to use Dot Notation instead of the parent
attribute. The dot notation is used to "namespace" the inherited class like it was a "nested" class we wanted to reference.
-
For example, naming a style
ParentStyle.ChildStyle
will define a style (ChildStyle
) that inherits fromParentStyle
. This would be referenced in the layout as@style/ParentStyle.ChildStyle
. -
We can chain these together as much as we want:
MyStyle.Red.Big
(though that would be distinct from theMyStyle.Big.Red
style! It's not doing CSS combining, but Java class inheritance!)
Define another style (e.g., Label
) that inherits from your first style to encapsulate attributes shared by the labels (e.g., boldness). Refactor your layout
so that the labels use this new style.
Define another style (e.g., Gold
) that inherits from your Label
's style and has a background color that is UW Gold and a text color of black. Apply this style to one of your labels.
Android also includes a large number of built-in platform styles that we can apply and/or inherit from. These must be inherited via the parent
attribute (you can't use dot notation for them). They are referenced with the format:
<style name="MyStyle" parent="@android:style/StyleName">...</style>
There are a bunch of these... and Android's recommendation is to understand them by browsing the source code and seeing how they are defined.
-
This makes discoverability difficult---it's a lot like trying to learn Bootstrap by reading the CSS file.
-
You can also look at a list of defined in the R.style class.
-
Honestly, most of the styles are not very effective bases for inheritance; you're often better using your own.
- But look and find the
TextAppearance
style that the docs recommend. Does it seem worthwhile to inherit from this?
- But look and find the
As practice, define a new style for the Button
at the bottom of the screen that inherits from MediaButton
, but has a size of 22dp
. What does the inheritance do to the appearance?
Unlike CSS, Android styles do not cascade: that is, if you apply a style to a ViewGroup
(a layout), that style will not be applied to all the components of that ViewGroup
.
But what we can do is have a style apply to every View
in a Context
(an Activity or an Application) by applying that style as a Theme
- Themes are styles that are applied to every single
View
in the whole application or in a particular Activity; you can't get any finer granularity (without moving to per-View Styles). Theme styles will apply to every View in the context, though we can overwrite specify styling for a View as normal.
Themes are styles, and so are defined the exact same way (as <style>
elements inside a resource XML file. You can define them in either styles.xml
, theme.xml
, or any other values
file---resource filenames are arbitrary, and their content will still be compiled into R
no matter what file that content is in.
Themes are applied to an Activity or Application by specifying a android:theme
attribute in the Manifest
(where the Activity/Application is defined).
- If you look at the project's
Manifest
created by Android Studio, you'll see that it already has a theme (AppTheme
). In fact, this is the<style>
that was provided insidestyles.xml
!
Experiment with removing the theme attribute from the application How does your app's appearance change? NOTE: You will need to change MainActivity
to subclass Activity
, not AppCompatActivity
.
- You might also try commenting out the stylings you applied to the individual
TextViews
to really see what happens.
Along with styles, Android provides a number of platform-specific themes. And again, the somewhat unhelpful recommendation is to understand these by browsing the source code, or the list in R.style (scroll down to constants that start with Theme
).
One of the most useful set of Android-provided themes are the Material Themes. These are themes that support Google's Material Design, a visual design language Google uses (or aims to use) across its products, including Android.
-
Material Design includes a set of themes, widgets (lists and cards), animations, etc. Explore the Material design docs for more details.
-
Material Themes are only available in API 21+, though there are some compatibility options.
There are two main Material themes:
@android:style/Theme.Material
a Dark version of the theme@android:style/Theme.Material.Light
a Light version of the theme
And a bunch of variants:
@android:style/Theme.Material.Light.DarkActionBar
a Light version with a Dark action bar@android:style/Theme.Material.Light.LightStatusBar
Variant of the material (light) theme that has a light status bar background with dark status bar contents.@android:style/Theme.Material.Light.NoActionBar
Variant of the material (light) theme with no action bar.- ... etc. See R.style for more (do a ctrl-f "find" for
Material
)
Experiment with applying different material themes to your application How does your app's appearance change?
One of the big advantages of Themes is that they can be used to define attributes that can be referenced from inside individual, per-View Styles.
- For example, a Theme could define a "color scheme" as a set of variables; these variables can then be references by the
Style
, allowing the style to use "the primary color of the current theme".
Theme-level attributes are referenced in the XML using the ?
symbol (in place of the @
symbol). For example: ?android:textColorPrimary
will refer to the value of the <item name="textColorPrimary">
element inside the theme.
Indeed, one of the advantages of the Material Themes (and Material-themed widgets) is that they are implemented to utilize a small set of color theme attributes, making it incredibly easy to specify a color scheme for your app. See Customize the Color Palette for a diagram of what attributes color what parts of the screen.
- It is possible to apply a Theme to an individual
View
orViewGroup
, but all that "cascades" is these theme attributes, which need to be explicitly references by the componentViews
.
Redefine the colors in your custom Styles (from Task 1 and 2) so that they reference the theme colors instead of purple and gold. What happens now when you change the application's Material theme between light and dark?
- Can you have the logo image reference those color attributes as well? Hint: use the
tint
attribute.
Lastly, set the theme of your app back to the provided AppTheme
. Return to the styles.xml
page and modify the custom AppTheme
style:
-
Have it inherit from a Material Theme (your choice of which)
-
Have it define theme attribute colors using the UW colors (purple and gold). These theme colors should now have references by your custom Styles, allowing you to once again Huskify your app.
That covers the basics of Styles and Themes. These are not necessary (you can just define XML attributes as we've been doing all quarter long), but can make it easier to customize and adjust your layouts as they change through the power of abstraction.