This chapter teaches you how the resource system works in Android, including the use of graphics. One of the considerations when developing for Android is that there are so many different devices out there. You have to consider displays of various densities, screens of all sizes, whether a device has a hardware keyboard, what orientation it is held in, and even what language should be displayed. Fortunately, Android’s resource system makes all this easy.
A solid understanding of Android’s resource system is not only essential to developing good apps, it’s vital to saving your sanity. If you had to programmatically check every feature of the device every time you did anything, your code would be a mess and you would lose sleep at night (assuming you get any now). To ensure a good user experience, you should generally make adjustments for things such as the size of the screen and the orientation of the device. By using resource “qualifiers,” you can let Android take care of this for you.
A qualifier is a portion of a directory’s name that marks its contents as being used for a specific situation; this is best illustrated with an example. Your Android project contains a
res directory (resources) that can contain several other directories for each of the resource types. Your layouts go in a directory called
layout. If you have a layout specifically for landscape orientation, you can create a directory in res called
layout-land, where “land” designates it as being used for landscape orientations. If you have a layout called
main.xml in both directories, Android automatically uses the version appropriate to the given device orientation.
Okay, so you can have a different layout for landscape and portrait orientations, but what else? Actually, there are a lot of qualifiers, and they can be applied to any resource directory. That means you can have the images or even strings change based on orientation. It’s important to know what folders go in the
res directory and what content they contain before diving into how that content can differ based on qualifiers. Here’s a list of the folder names:
animator— Property animations defined in XML
anim— View animations defined in XML
color— State lists of colors defined in XML
drawable— Drawable assets that can be defined in XML or image files (PNG, GIF, or JPG)
layout— Layouts defined in XML
menu— Menus such as the app bar menu defined in XML
mipmap— Drawable assets just like the
drawablefolder. The difference is that the
drawablefolders for specific densities can be excluded based on your configuration, letting you make density-specific builds, but the
mipmapfolders are always included regardless of density
raw— Any raw files such as audio files and custom bytecode
values— Various simple values defined in XML, such as strings, floats, and integer colors
xml— Any XML files you wish to read at runtime (common for configurations such as for app widgets)
By no means are all of these required in any given app. In fact, it is not uncommon to only have
values directories in an app, although you will nearly always have multiple
mipmap folders to accommodate different densities. The various resource folders are generated for you when needed (e.g., creating an XML menu in Android Studio creates the menu folder), but you can also create them yourself. For every file in these directories, Android’s build tools will automatically create a reference in the
R class (short for resources) that is an int identifier, which can be used by a variety of methods. That also means your files should be named in all lowercase and underscore separated.
Available resource qualifiers:
- Mobile country/ code:
- Language Region:
- Language direction:
- Smallest width:
- Available width:
- Available height:
- Screen size:
- Screen aspect:
- UI mode:
- Night mode:
- Touchscreen type:
- Keyboard availability
- Hardware keyboard type:
- Navigation key availability:
- Primary non-touch navigation method:
- Platform version:
Regardless of how many or how few qualifiers you use, the
R class will only have one reference to a given set of resources. For example, you might have a file at the path
res/drawable-xhdpi/header.png, and the reference would be R.drawable.header. As you can probably see, the format is
R.[resource type without qualifiers].[file name without extension]. Perhaps this header file also contains text, so you have language-specific versions such as
res/drawable-es-xhdpi/header.png. Within the Java portion of your app, you will always refer to the resource as
If the device’s language is set to Spanish, the reference automatically points to the Spanish version, so you do not have to change anything in your code. If the device is an HDPI device, it would first look in the
drawable-hdpi directory before the
drawable-xhdpi directory. When you refer to a resource like
R.drawable.header, you’re asking the system to use whichever header drawable best fits the current device configuration.
Density is one of the most important aspects of an Android device to understand when it comes to design and it’s worth covering in detail. Early Android devices had approximately 160 dots per inch (dpi)—and that is considered medium density (MDPI) now. Android 1.6 added support for both low density (LDPI or 120dpi) and high density (HDPI or 240dpi). Android 2.2 added extra high density (XHDPI or 320dpi) to the mix.
What do all these letters and numbers mean to you? A given image will appear larger on a screen with a lower density and smaller on a screen with a higher density. Fortunately, Android makes handling this easy for you. Instead of specifying dimensions in raw pixels, you will use either density-independent pixels (referred to as dip or dp) or scale-independent pixels (sip or sp). Density-independent pixels are based on MDPI, so 1dp is 1px at medium density. The difference between one sp and one dp is that sp takes into account the user’s preferred font size. Therefore, all font sizes should be specified in sp and all other dimensions should be in dp.
There are a lot of numbers and terms to remember, so it can be helpful to break this down more. Most apps can ignore LDPI because it is uncommon (of standard phone sizes and tablet sizes, LDPI represents a fraction of a percent). For the rest of these, you can think of them relative to one another. The ratio from MDPI to XXXHDPI is 2:3:4:6:8.
For the longest time, Android supported only “raster” images natively. Raster images are a series of pixels, which makes them efficient for displaying on the screen, but that means they do not resize to arbitrary sizes well. The primary alternative is vectors, which represent images as drawing instructions (e.g., “draw a black line from 0, 0 to 5, 0 that is 2 units thick”). The advantage of vectors is that they are infinitely resizable because all the values are relative.
One of the many changes announced for Android 5.0 (Lollipop) was native support for vectors. The format supported by Android is a subset of the well-known SVG format, but it will handle most typical uses. The particularly exciting thing about this vector support is that Android also has support for animating between two vectors.
Android supports JPEGs, PNGs, and GIFs natively. These formats are all “raster” format with red, green, and blue channels (and an alpha channel that represents level of transparency for PNG and GIF images). If you use eight bits (one byte) for each channel, a pixel is 24-bit (without alpha) or 32-bit (with alpha). That means a single 1920×1080 image is over eight megabytes! Fortunately, all of these formats are compressed, so they don’t store each individual pixel on disk.
JPEGs use lossy compression, meaning that some of the detail is lost to decrease the file size. PNGs and GIFs use lossless compression (no detail lost). That means you should use JPEGs for large photographs and any images already saved as JPEGs and PNGs for everything else. Although GIF is supported, PNG is a better file format and should be used instead.
Android’s vector support that came in Android 5.0 (Lollipop) has been long awaited by designers and developers alike. Devices now are powerful enough to handle typical vector uses (such as for icons in a toolbar) without a problem; however, you still have to be aware of the performance costs.
Each vector image is cached as a bitmap representation, which means it can very quickly be drawn and even moved around on the screen, but changing it in any way that invalidates that cache (such as scaling it) causes Android to have to recreate that bitmap. For smaller icons and infrequent animations, this won’t be any problem (in fact, animating between vectors is one of the coolest features of Android 5.0), but trying to continuously animate a full-screen vector is going to have a noticeable performance hit.
Oftentimes, you do not know ahead of time how large an image should be. For example, a button will need the ability to be of different sizes to accommodate different languages and text labels. Even if you tell it to be as wide as the screen, there are still many possible widths. It would be a huge amount of work to create an image for every possible width, and your app will look terrible if you hard-code the button to a specific size in pixels. The nine-patch image solves this problem.
If you think of a typical button, there are nine pieces to it: the four corners, the four sides (left, right, top, and bottom), and the center area where the text typically goes. To support a wider button, the top, center, and bottom portions have to be duplicated. For a taller button, the left, center, and right portions would be duplicated. This allows you to preserve the corners and edges while expanding the content area.
A nine-patch is actually just a PNG where the 1px border around the outside of the image consists of pixels that are either fully transparent or fully black. The left and top of the image can contain black pixels to describe how to enlarge the image.
Another feature of nine-patch images is that the right and bottom of the image specify content areas. In the simplest case, you can think of the black part as where content goes and the transparent part as the padding. When you set a view’s background to a nine-patch image, the padding of that image will be applied automatically; however, you can still override it.
One last thing to note is that you can specify nine-patch images in XML (though it is uncommon). The image itself is still a standard nine-patch PNG, but the XML file allows you to specifically enable or disable dithering, which is a way of adding “noise” to an image to reduce artifacts such as banding, which is caused by low-bitrate displays.
In addition to standard image files, Android supports a variety of XML drawables (the term “drawable” refers simply to something that can be drawn to the screen). Some of these drawables are ways of using multiple image files for one resource; others allow you to actually specify colors within XML.
Each type of drawable that can be defined in XML uses a different root node (which tells Android which class’s
inflate method to call). They are all inflated to specific drawables, but you can interact with them via the abstract
Drawables that display more than one drawable define each one with the
item tag. The
item tag can typically take offsets (
android:bottom), which is useful for specific visual effects and for supporting images of different sizes.
A layer list is an array of drawables defined in XML that creates a
LayerDrawable instance when used. Each drawable can be offset on the left, top, right, and/or bottom by a different amount. The drawables are drawn in the order they are declared (just like views) and will be scaled to fit the available space.
If you do not want the drawables to be scaled, you can use gravity. Using gravity allows you to define the anchor point of the drawable. For example, you might be using a drawable as the background of a full screen view. The default behavior is to scale to the size of the view, but you can instead specify a gravity to align the image to a specific location such as the right side of the view. Notice that this requires a separate bitmap node that contains the gravity; the gravity does not go within the
item tag itself.
StateListDrawable, defined by the selector XML node, allows you to specify different drawables for different states. For example, a standard button will have different appearances based on whether it is enabled, focused, pressed, and so on. You can specify as few or as many drawables as you like, and you can also combine states (e.g., show a particular drawable only if it is both focused and checked). You can use colors in a selector as well. You might decide that your button’s text should normally be white but it should be gray when it is disabled.
It is important to note that the drawable used will be the first that matches, which might not be the best match. For example, if the first item requires a state of pressed and the second item requires both pressed and enabled, the second item will never be used because any image that matches pressed, whether enabled or not, would match the first item immediately. Therefore, you should put the most specific states first and your last state should contain no state requirements.
Here is a list of the most common states:
android:state_activated— Added in API 11, this indicates the item is the activated selection. For example, on a tablet where you have a list of articles on the left and the full article on the right, the list item on the left that represents the article being displayed on the right would be activated. All other items in that list would be false for being activated
android:state_checkable— Indicates whether the item can be checked. This is really only useful when a view can change between being checkable and not checkable, which is relatively uncommon
android:state_checked— Indicates whether the item is currently checked
android:state_enabled— Indicates whether the item is enabled or disabled, which is especially useful with buttons that are only enabled if a certain condition is met (such as a text field being filled out)
android:state_focused— Indicates whether the item is focused. Focus usually happens after an input has been tapped, such as an
EditText, or after navigating to a given view by means other than touch (e.g., a directional pad or trackball). Drawables that represent a focused state usually have a glow or generally highlighted appearance
android:state_hovered— Added in API 14, this indicates whether the item is currently being hovered over by the cursor. Typically, this is visually indicated in the same way as focus
android:state_pressed— Indicates whether the item is being pressed. This state happens when the item is clicked or touched and is usually shown by a visually depressed state (like a button being pushed in) or a brightened/colored appearance
android:state_selected— Indicates that the item is currently selected. This is very similar to focus but slightly more specific. A particular view group (e.g.,
ListView) can have focus while a specific child is selected
android:state_window_focused— Indicates whether the app’s window is focused. This is generally true unless it is obstructed, like when the notification drawer has been pulled down
LevelListDrawable manages any number of drawables, assigning each one to a range of integer values. You can set the current level of the
setLevel(int) method. Whichever drawable fits in that range will then be used. This is particularly useful for visual indicators where you want to show some difference based on a value but do not want to worry about the exact image to display in your code.
TransitionDrawable allows you to specify two drawables that you can then crossfade between. Two methods
reverseTransition(int) allow you to control the transition between the two drawables. Both methods take an int, which defines the duration in milliseconds.
InsetDrawable allows you to inset, or push in, another drawable. It is useful when you have a drawable that you would like to appear smaller than a view or to appear padded. This might seem useless because you could just add transparent pixels to an image to accomplish the same thing, but what if you want to use that same image in one place without that extra spacing and in another with it? Code below is a simple example that insets a drawable by 16dp on all sides.
ClipDrawable takes a single drawable and clips, or cuts off, that drawable at a point determined by the level. This is most frequently used for progress bars. The drawable that this
ClipDrawable wraps would be the full progress bar (what the user would see at 100 percent). By using
setLevel(int), your code can reveal more and more of the bar until it is complete. The level is from 0 to 10,000, where 0 does not show the image at all and 10,000 shows it completely without clipping.
You can specify whether the clipping is vertical or horizontal as well as the gravity. For example, a
gravity set to
clipOrientation set to
horizontal will start drawing from the left side (see Listing 4.8 for an example). By calling
setLevel(5000) on this drawable, it will draw the left half of the image.
ScaleDrawable allows you to use
setLevel(int) to scale the drawable at runtime (i.e., while the app is running). This is sometimes also used for progress bars as well as general cases in which you want to scale a drawable based on some other value.
You specify a
scaleWidth and a
scaleHeight, which is the size of the drawable when the level is 10,000. For example, code below shows a
ScaleDrawable with both scale values set to 100 percent. If this drawable has
setLevel(5000) called on it, it will display at 50 percent width and 50 percent height.
ShapeDrawable is a rectangle, oval, line, or ring that is defined in XML. If it is a rectangle, you can define rounded corners. If it is a ring, you can specify the
innerRadius (i.e., the radius of the hole) or
innerRadiusRatio (the ratio of the shape’s width to the inner radius) and the
thicknessRatio. For all shapes, you can specify a stroke (i.e., the line around the shape), solid fill color, or gradient fill colors, size, and padding.
A VectorDrawable is a drawable that represents an image using vector data. This was introduced in Android 5.0 (Lollipop) and is primarily intended for icons and other small assets. To use a vector in Android, you create an XML file similar to code below The bizarre path data comes from the SVG file format. You’ll likely be creating the vector image in a separate app and then copying the values from that file.
Android 5.0 also added
AnimatedVectorDrawable, which is a way of easily animating a vector, often by manipulating the path(s) of one vector to become another. You will first define a VectorDrawable, giving a name to any of the elements you want to animate (such as a path). The
AnimatedVectorDrawable then has target nodes that specify a name and an animation to apply.
One more drawable type that Android 5.0 added is
RippleDrawable. This is the default drawable used to indicate touch as a growing ripple. When you first touch this drawable, the background appears, expanding out in a circle (which can be drawn using a custom mask that you define) extremely fast. Then, a circle grows slowly, starting from your finger, filling the background. If you tap quickly, that circle expands quickly. If you move your finger away, the drawable fades out.
This beautiful visual actually required a new thread (called the
RenderThread) to be created for Android 5.0. Without it, tapping a view would pause the animation of the ripple because the animation and UI work (such as loading a new activity or fragment) were on the same thread.
You should specify all user-facing strings in XML. Typically, you put these all in a file called
res/values, although you can call it something else. Putting your strings into an XML file will allow you to easily localize your app at any point, even if you do not intend to in the first version. In addition, it will allow you to keep your vocabulary consistent across the app by reusing strings, which will make your app more accessible to everyone, especially those with a limited understanding of the language that it is in.
In most cases, you will refer to strings in your layouts, setting the text for a
TextView in XML, but you can also set it in code. Further, the
Context class (which
Activity extends) has a
getString(int) method, where passing it the resource identifier for the string you desire (e.g.,
R.string.hello) will return the applicable string (“Hello” or “Hola,” depending on the device’s language). The
getString(int) method in
Context is actually just a convenience method for calling
getResources().getString(int) and there is a similar method in
These strings also support substitutions. By including
%s in the string, you can then call the
getString(int, Object...) method to substitute a string (you can also use any other substitution supported by
String.format() such as
%d for a number). For example, if the “hello” string was actually “Hello, %s” then you could call
getString(R.string.hello, "Andy"), which would give you either “Hello, Andy” or “Hola, Andy” (depending on device’s language).
You can also include multiple substitutions by numbering them such as “Hello %1$s, do you like %2$s?” and call
getString(R.string.hello, "Andy", "bacon") to get “Hello Andy, do you like bacon?”
Android also supports plurals across locales. For example, English treats the number one specially (e.g., you would say “word” when referring to a single word but for all other amounts, including zero, you say “words”). There are other languages that treat other numbers in different ways. Using the
plurals tag, you can easily support these. The supported quantities are
To use the “
child_count” string, you would call one of
getQuantityString(int, int) (for retrieving a string with no substitution),
getQuantityString(int, int, Object...) (for retrieving a string with substitution), or
getQuantityText(int, int) (for retrieving a CharSequence).
For example, you might call
getQuantityString(R.plurals.child_count, 7) to get “
7 children” back. Notice that it is
R.string because of the XML node name.
You can define arrays in XML, which is most often helpful for defining sets of data, such as for populating a
Spinner or a
ListView. When defining a string array, you use the
string-array XML node with item child nodes.
If you want to access the string array in code, you can use the
getStringArray(int) method of
Resources. The resource identifier in this case would be
R.array.sample_array. Android also supports integer arrays (using the
integer-array node) as well as
TypedArrays (using the
All of your colors should be specified in XML to ensure consistency and make design changes easy to propagate throughout the app. Colors are specified as alpha, red, green, and blue components in hex with two digits each. Although you can specify your colors as AARRGGBB, RRGGBB, ARGB, or RGB, it is best to be consistent, usually sticking with AARRGGBB.
You can also use the
Color class to use predefined colors and create colors from individual components. To use colors in code, you will typically use the
getColor(int) method of
Resources. Usually, you will specify your colors in
res/values/colors.xml, but the name is a convention.
Dimensions are yet another value you can define in XML, and they are far more valuable than they would appear at first glance. For example, you could define three primary font sizes in a
dimens.xml file that you store in
res/values. When you test the app on a 10” tablet, you would likely find those font sizes a little small. Instead of having to redefine all the
TextViews, you could easily just add a new configuration-specific
You can access these dimensions in your code via the
Resources class. If you want the exact dimension as a float, you use
getDimensions(int). In some cases, you only want the whole portion of the dimension as an integer, dropping off any fractional portion, and that’s what
getDimensionPixelOffset(int) is for. If you want the dimension as an int rounded up to ensure that you don’t get any zero values due to fractions, you can use
Animations can be specified in XML as well as the resources that have been discussed so far; however, they are not covered here because they are covered in depth in Chapter 9, “Polishing with Animations.”
You’ll typically create your IDs in your layout files using the typical
android:id="@+id/name" format, but you can also specify them like any other XML resource. The convention is to put these in an
ids.xml file in
This is a good practice when you need to programmatically assign an ID to a generated view or change an ID dynamically. It can also be used for the
View.setTag(int, Object) and
Android has supported XML menus since the beginning, but their use shifted in Android 3.0 when they went from being something triggered by a hardware menu key to something displayed in the app bar. Both activities and fragments can contribute to the app bar and you can dynamically change it as needed, including combining multiple menus defined in XML.
menu root node will contain one or more
item nodes. Each
item becomes a
MenuItem in Java when the menu is inflated and should have an id, icon,
showAsAction, and title defined at a minimum. The
showAsAction attribute controls whether the item appears on the app bar or in the overflow menu (the menu represented by three vertical dots on the app bar). If an item is commonly used such as share, you will set
ifRoom, meaning that the item will be displayed as an action icon in the app bar (instead of listed in the overflow menu) if there is room for it.
The value of understanding Android’s resource system cannot be overstated. You should never hard-code any user-facing strings, dimensions, or any other value that can be specified in resources. Even if you never expect to support a language other than your native language or a device other than what you have in your pocket, you should follow the best practices of properly using the resource system; your code will be cleaner for it. What’s more, should you decide to support other device configurations, you’ll be quite glad you did.
(To Be Continued)