A well-written, efficient layout can increase performance and simplify maintenance. In this GOTO Copenhagen talk, Daniel Lew goes over how to write the best layouts for your application. We’ll cover a range of improvements including choosing the right ViewGroup, effectively leveraging resource qualifiers, avoiding code duplication, and more tips and tricks. Come and learn how to get the most power out of every View!
Introduction
I’ve been doing Android drawup for about seven years, first at the Travel App company, then Expedia, and now currently at Trello. This talk is about efficient Android layouts, and when I was writing it, what I found was 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.
I started thinking about it in the way that Archimedes was referring to how fulcrums work, where if you give him a proper place to stand, he can move the Earth. That’s the focus of this talk. 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.
ViewGroups
Let’s talk about picking which ViewGroup to use for any particular layout. I think the main thing is, the simpler the ViewGroup you can get away with, the better, because the more complex ones require a lot more maintenance, and you can run into a lot more bugs.
On the high end of things, RelativeLayout is one of the most complex. (ConstraintLayout 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 at the bottom is FrameLayout, which is one of my favorites because it’s so simple.
There are many other views, but these are the main building blocks for most applications. RelativeLayout and ConstraintLayout sort of occupy the same space in Android right now, which is that they position views relative to each other. RelativeLayout is limited, but it’s what we’ve had since the beginning, whereas ConstraintLayout is new and can do all these amazing things.
But there are key problems with both of them, besides the fact that they’re fairly complex. RelativeLayout is slow, and ConstraintLayout is alpha-ish at the moment. They haven’t officially released it yet. There are alpha builds out on Maven Central, but a few times they’ve completely changed the API around, so it’s not production ready.
LinearLayout is great for stacking views vertically and horizontally; you can distribute the weight. This is a simple view where rows are stacked vertically, and then also I distributed the weight between those two spinners equally.
I’m actually okay with nested LinearLayouts as opposed to RelativeLayout. Yes, LinearLayouts are sometimes slow. If you use layout weight, and you nest them pretty deep, then they can get pretty slow. But that’s only a sometimes thing, whereas RelativeLayout always has to do two passes. It’s always slow. The hope is that eventually ConstraintLayout will be our savior and save us from having to decide between the two of them.
In the meantime, I think what’s most important is to focus on profiling. Whatever layout you end up with, turn on profile GPU rendering and see if things are running fast enough on your test device. If you’ve never used profile GPU rendering, I highly recommend looking into that because then you get these nice bars that show you whether or not you’re hitting 60 frames a second, and if you don’t, what sort of things you’re spending too much time on.
FrameLayout 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. 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. There’s a lot you can do with this. It turns out that if you want to have a simple progress bar in the center of some large screen, 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 you want to have multiple views that compose a single thing that you click, it’s good to have a FrameLayout as the parent. That can actually have the click detection so when you click on it, it looks like something is happening.
A good example of this, like in the Trello application, is the notification bar in the upper right corner. This is always present on the screen. It’s a single FrameLayout, and there’s an icon inside of it. That white icon is always present, and then if you have unread messages, it’ll put that little red thing on top of it. The white icon is centered and the red icon is actually pegged to the upper right corner, but then you can use the margin or push it in so it doesn’t just ram up against the sides. 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.” If you have two different states that you toggle between, sometimes you have a single view that you actually change. I’ve found it handy to have multiple views that you can switch between. A FrameLayout is a good way to contain two things in exactly the same spot and then toggle between them.
A good example of that in the Trello app is the avatar view. This is whenever you represent a member of a card or something like that. If the user has their avatar set, then we want to show that. If they’ve never taken a picture, then we want to show their initials. It’s essentially choosing between an image view or a text view.
View reuse
The avatar view brings up the next thing I want to talk about, which is view reuse. We use this avatar view all over the application. These are just three screens, like the Trello board. An open Trello card, some activity on the side, and there’s a few other locations where we use an avatar view within the application.
The question becomes, how do I reuse this in multiple places without having to rewrite the code everywhere? The most obvious way is to use something called an include.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<include
layout="@layout/avatar_view"
android:layout_width="48dp"
android:layout_height="48dp"
/>
<!-- Rest of layout here... -->
</LinearLayout>
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. You can’t modify much of what you’re including, but you can modify any of the layout parameters–that’s any of the things that starts with layout_
. That’s a nice way to include something that may have been match-paired, but you don’t quite want it to be in the end.
The problem here is that you get the XML in every single location, but you don’t get any of the logic. Now you have to come up with some way to find these particular views that were in the include, and then add the logic for actually binding that to the view. What I actually prefer these days is using custom view.
<com.trello.view.AvatarView
android:id="@+id/avatar_view"
android:layout_width="48dp"
android:layout_height="48dp"
/>
With a custom view, I call instead of include. I have the view in reference directly. Then I 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.
With this custom avatar view, I’m extending FrameLayout, so I’m saying the topmost is going to be a FrameLayout. Remember, I’m toggling between the two states.
public class AvatarView extends FrameLayout {
ImageView icon;
TextView initials;
public AvatarView(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.view_avatar, this);
icon = (ImageView) findViewById(R.id.icon);
initials = (TextView) findViewById(R.id.initials);
}
public void bind(Member member) {
// ...Load icon into ImageView...
// OR
// ...Setup initials in TextView...
}
}
I’ve got an ImageView
and a TextView
, and then inside of a constructor itself, it actually inflates all the views that are underneath it. As a parent using avatar view, I don’t have to worry about what’s inside. It’s handling all of that for me. 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.
This makes my life a lot easier. One thing worth noting, though: if you’re using this sort of custom view setup, this is a very hand-wavy version of what would be the included XML.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/initials"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<ImageView
android:id="@+id/icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
But if you include the XML like this, you end up with a view hierarchy with an avatar view on the top. It’s a FrameLayout and then it inflates another FrameLayout, which then has the ImageView
and TextView
. The middle FrameLayout here is pointless. We don’t really need it.
The lint check in Android returns a particularly harsh message when you do this, something like “has no reason to live” or “has no reason to exist.” The way that we get rid of that is through a LayoutInflater
trick.
LayoutInflater.from(context).inflate(R.layout.view_avatar, this);
Normally when you’re using LayoutInflater
, everywhere you see it, there’ll be a third parameter there, and it’ll be false. 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.
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 FrameLayout.
<merge xmlns:android="http://schemas.android.com/apk/res/android"
>
<TextView
android:id="@+id/initials"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<ImageView
android:id="@+id/icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</merge>
And then you end up with the hierarchy that you actually want: no unnecessary FrameLayouts involved.
Custom drawing
This third view-specific piece of advice has to do with custom drawing, which is useful in cases of particularly complex views. You 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 in normal views.
A good example of this in the Trello app is the labels. There are green and blue and red and yellow and purple labels on these cards. When we first launched the Trello app, there were 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. That meant that every single card potentially had six views inflated.
Later on, Trello changed this. It allowed any number of labels to be drawn, so then you could end up with a nightmare scenario where every single card could have dozens of labels on them if someone’s going really crazy. 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 slow because you can see even more cards and it’s rendering even more views. 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.
There’s two steps to it. Custom views used to be very intimidating to me, because I thought they looked really hard, but they really are not. The first step is telling the custom view how big it should be.
How much space does it need to take up? I have my label view, which is really nice, but no one knows exactly how much space it’s going to take up. onMeasure
is what you use to tell any parent view group how much space you need. You can often skip this step 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 always going to be the same size, 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 measures.
void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
With onMeasure
, you have this widthMeasureSpec
and heightMeasureSpec
, which was sort of confusing to me at first. It turns out that these are just packed integers. It’s a single integer. These two parameters basically take the place of four parameters, which are a width mode and size and a height mode and size.
The size is just a dimension value, but the mode is telling you how it wants you to handle that particular size that it passed. There are three different MeasureSpec
s 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.
Your typical onMeasure
looks something like this for the width, and then you would copy the same code for the height.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int width;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
}
else {
int desiredWidth = 500; // Whatever calculation you want
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(desiredWidth, widthSize);
}
else {
width = desiredWidth;
}
}
// ...to be continued...
}
You grab the mode and the size. If the MeasureSpec
is EXACTLY
, you probably just want to pass back the size that it gave you. You don’t want to screw up the parent view group too much, or else it might get confused.
Otherwise, calculate what your desiredWidth
is, and if the width spec is AT_MOST
, then make sure your desiredWidth
is not larger than that size. Otherwise, if it’s undefined
, you just get to pick whatever desiredWidth
you want.
Then, once you’ve done this for both the width and the height, you are required to call setMeasuredDimension
in order to tell the view what you decided for the width and height.
@Override
protected void onMeasure(int widthMeasureSpec,
int heightMeasureSpec) {
int width;
int height;
// ...Calculate width and height...
setMeasuredDimension(width, height);
}
Because there’s no return value for onMeasure
, you just have to call this measure at the end. 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.
Another thing worth considering is that in some cases, you don’t actually need a custom view: you could just write your own custom drawable. And the advantage there is, you could take this custom-written code and apply it to any view. That’s good if you want some special custom background. In that case, onMeasure
becomes something like getIntrinsicHeight
and getIntrinsicWidth
on a drawable, and then ondraw
becomes draw
.
Styles
Let’s move away from views and talk about kind of another layer above views, which is styles. If you’re applying XML to a view, this view has no style. Not because it’s uncool, but because there is no style tag on it.
No style
<View android:background=“#FF0000” />
Style
<!--- some_layout.xml -->
<View style="@style/MyStyle" />
<!--- styles.xml -->
<style name="MyStyle">
<item name="android:background">#FF0000</item>
</style>
If you have a style, all that means is I’m creating some style resource, which has the same attribute inside of it, and then the view itself then applies that style on top. The style’s applied first, and then those attributes are applied on top of it. In the same way that includes take layout XML and stuff it into a view group, styles take a bunch of attributes and stuff them into a view.
Where is this useful? It’s very efficient when you need to style a bunch of semantically identical views the same way. What I mean by semantically identical is that each view does exactly the same thing in your hierarchy. 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. If I want to change the text size of one of those buttons, my expectation is that all of them change at once. That saves me a whole bundle of time.
I see a lot of people misusing styles in very inefficient ways that end up biting you in the long-run. And one way is single-use styles.
<TextView
android:id="@+id/title"
android:textColor="@color/blue_200"
android:textColorHint=“@color/grey_500" />
<TextView
android:id="@+id/body"
android:textColor="@color/blue_200"
android:textColorHint=“@color/grey_500" />
Here we have a view that’s representing a style, and that style’s only used once. 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.
This is more crucial when you have two views that are coincidentally using the same attributes. Say 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
.
Suppose later on I decide, “Oh, I want the title to be a different color.” Well, if I change the color of the title now, that also 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 an example in Java: imagine I have two constants. One is the number of columns I’m going to show in some grid. And the other is the number of retries I’ll do in some HTTP request if it fails.
// static final int NUM_COLUMNS = 3;
// static final int NUM_RETRIES = 3;
static final int NUM_THREE = 3;
And so I think, “Aw, these are the same value, I’m going to optimize this and have a single constant.” This is problematic for two reasons: one is that three is already a constant, but the other is I’ve lost all semantic meaning. These numbers meant something very different. If I want to increase the number of retries for HTTP, suddenly now I’ve changed how my UI looks as well, incidentally. Those are mistakes people can make with styles.
Themes
Themes are sort of like styles on steroids. Styles apply to individual views. Themes 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 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. With themes I can say, “I want a default style for all buttons,” and it automatically gets inflated for everything.
Themes also help you with configuring your system-created views. If you’ve got popup windows or toolbars or something that the system creates, that’s one fewer thing you have to create. But before you could theme on a view level, there were 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 a theme is very useful for configuring just things that the system will create.
There are three ways to apply a theme.
<application
android:theme="@style/Theme.AppCompat">
<activity
android:theme=“@style/Theme.AppCompat.Light”>
<Toolbar
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
/>
You can apply it to the entire application, or to individual activity–if you do that, it ends up overriding whatever’s in the application. You can also apply it to an individual view. And in the view case, it ends up actually overlaying, so you can overlay a few changes to an individual view’s theme.
View theming is very handy. Remember the days of Holo? 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. You had 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 a light theme,” and then manually apply a dark theme to the tool bar itself. It makes things so much easier.
AppCompat, one of Google’s support libraries, makes theming a lot easier. It gives you Material on all devices. That’s the latest design language from Google. There’s a lot of subtle differences between Holo and Material in terms of spacing, and also in the visual metaphors that they’re using. It’s so much easier to start from one single baseline and then theme from there.
Another thing AppCompat gives you is baseline themes and styles. You might want to change the default look of all your buttons, but you don’t want to have to actually go and define a style, which defines every single attribute that a button has to have. You just want to take the main one and tweak it, like add a little padding to all of your buttons. AppCompat makes it easy to take the AppCompat button style and extend from that and modify it. Without that, it becomes sort of a nightmare, especially between Holo and Material.
The third really important thing AppCompat enables is that it allows you to do view theming pre-Lollipop, in XML. 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 some API that you shouldn’t even be using anymore, I think 11. (Sorry, people who are still having to support apps on 11.)
Here are a few examples of things you can do with themes. ColorTheming gets touted everywhere.
<style name="ColorTheme" parent="Theme.AppCompat">
<item name="colorPrimary">#F00</item>
<item name="colorPrimaryDark">#0F0</item>
<item name="colorControlNormal">#00F</item>
</style>
Instead of having to use individual drawables for everything, I can just set up colors and most of the things in Android will get colored automatically.
These are some examples of applying default styles.
<style name="AppTheme" parent="Theme.AppCompat">
<item name="buttonStyle">@style/MyButton</item>
<item name="android:spinnerItemStyle">@style/MySpinnerItem</item>
<item name="android:textAppearance">@style/MyText</item>
<item name="android:textAppearanceInverse">@style/MyTextInverse</item>
</style>
Just in case you’ve never seen this before, the top line defines the button style for the entire application. That gets applied to every button. The spinner item style is handy because what if I want to use the built-in spinner item layout row that Android provides, but I want to style it a little bit? I can use that here. Text appearance is nice because that 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 set up attributes which are then referenced in your XML.
<style name="AttrTheme" parent="Theme.AppCompat">
<item name="selectableItemBackground">@drawable/bg</item>
</style>
<!-- some_layout.xml -->
<Button android:background="?attr/selectableItemBackground" />
So in this case, selectableItemBackground
(one of my favorite attributes) is referenced with that ?attribute/
instead of the @drawable
that you normally use, and it derives that value from the theme instead of going to it directly.
Why is this useful? If you happen to have an app that supports multiple themes, it makes it very easy to swap between those values. More importantly, your system might have multiple ideas of what a selectableItemBackground
is, because pre-Lollipop there weren’t any Ripple drawables. It was just a flat color that you changed to whenever you click on something. Post-Lollipop, you want to have those ripples because it looks really cool. If you use a selectableItemBackground
, the theme can automatically figure out which one it wants to take.
Resources
Resources are all the things that go into your app that aren’t just pure Java code. Before I talk about resources, I want to talk about device configurations.
If we look at this screenshot, there’s a whole bunch of things that one can derive about it in terms of its configuration. 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 US locale, so it’s showing en_US
, and it’s version 24. These are all things that the Android system knows about the device, and you can query this manually on your own if you want. With resources you can have it select things automatically.
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. Users probably won’t change the locale 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 want to vary on this? Landscape versus portrait 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 show some of the cooler functions by default.
Locale is a very easy one: if you want to have your app translated into different languages, you just have have it select different text strings based on the locale. On the left here it’s in English, and 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.
Another example of that would be 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. Then 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.
You could do all this in Java code, like I said earlier, but it’s a lot easier if you just leverage the resource qualifier system. You define alternative resources for different device configurations, and then at run time Android will automatically pick the correct resource based on the device configuration. It goes through and queries everything and figure out which of the resources you defined makes the most sense in this situation.
You define this by the names of the folders. 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 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 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.
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; there’s a 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.
That same documentation page also lists out the algorithm, but it’s just 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 the device class, like tablet versus phone. And it also has to be in portrait.
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. Then it’ll look maybe for just the single sw600dp, oh, it turns out the phone doesn’t qualify for that, so then it’ll check if the phone is in portrait, and if it doesn’t qualify for that, then it’ll fall back to the base values here.
That’s why it’s handy to have a default value for everything. The only thing you don’t 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 because of all that extra work, but at least you don’t have to worry about that when you’re developing quickly.
In terms of using resource qualifiers in the correct way, it’s important to think of these resources as code. Think of each resource that you’re inserting somewhere as a parameter to some method or function, and that the parameter’s determined based on the device configuration. For example, 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 code on the right has this parameter. You want to think of it more in terms of the code on the right.
One simple example that I like to use a lot is letting the resource qualifier system determine some Boolean logic for me. This is a simple one where I just want to 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 when it is in portrait, it’s true.” Then I can get this Boolean value out. This is really handy if you have multiple different configurations and multiple ways that Boolean logic could run. It could do all that calculation for you.
A more classic example is using it for different layouts. So I could say I’m going to call setContentView
, 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 before I realized it’s very improbable to actually end up ever without it being in landscape or portrait, you’d have to have a square screen for that.)
It will select the right one of these, but you’ll probably end up with some duplicated code, because chances are there’s not that much that changes between portrait and landscape. Then if you reuse code with an include
, it can switch on just that part of the code that changes. In this example 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. 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 views which are supposed to be pretty much the same thing, but modify depending on the text size. Here, what I can do is reference a dimension, and then that dimension can be determined based on the qualifiers as well.
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textSize="@dimen/welcome_text_size"
android:textColor="#FF00FF"
/>
To take this a step further, let’s suppose we have style somewhere in the application. 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 application instead of applying that dimension trick to just one particular view. In this example, I have an activity main on top, and then by default go to one include, but if it happens to be in portrait, it’ll go to a different include. Both of those include the same text view, so they use the same style. Then that style, based on the current configuration, determines what the text size is.
You can go pretty deep with this and write very little duplicated code between all of your layouts if all you’re doing is changing things based on device configuration. And 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, because 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 are 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. Two, it means that you bypass this system entirely, because 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, and when you re-inflate everything, something might have changed based on selecting a different layout.
Drawables
I want to outline a nightmare scenario that you may have gone through. (I certainly have gone through this many times.) I’m interacting with design, they send me a mock-up of a new login screen, and they want to add this “login with SSL” thing at the bottom.
I start working on this, but I tell design, “I need this asset, because I’m not good at design or anything, I need you to give this to me.” So design says, “Okay, sure, no problem.” And they send over a zip file, I unzip it, and I get this one file that is–who knows how big this is supposed to be. So I 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 back a file like this, and a zip that contains this.
Now I’ve got all the assets I need, but then I have to go through and rename everything and put it in the right folders, and then import into my project. That’s a real pain to do with every asset. Then the kicker on top of all of this is that at the very end, design says, “Actually, I want to tweak the color, and here’s a new set of assets.” And so now I have to go through this whole process again, and it’s a gigantic pain in the ass.
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. Think of assets as code as much as possible. Don’t think of them as bitmaps that you get from design. Think of them as things that you can execute in your application. Because then it’s much faster to tweak and change things on the fly.
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 draw simple shapes and set up state selectors. (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 want to layer on top of each other with multiple states in between both of them, you might have thought, “Okay, now I need 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, and you get that nice composition.
A detailed example for this are the login buttons that I worked on once. These login buttons are entirely done through drawable XML. The first step to making these login buttons work is creating that button outline.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
>
<solid android:color="@color/transparent" />
<stroke
android:width="1dp"
android:color="@color/white"
/>
<corners android:radius="@dimen/corner_radius_tiny" />
</shape>
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 want to 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. Then I do want that white outline, 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 because otherwise it would be really hard to see with my white background on the slides.
Then we need to add some behavior to it.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<selector>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="@color/blue_200" />
<corners android:radius="@dimen/corner_radius_tiny" />
</shape>
</item>
<item android:drawable="@color/transparent" />
</selector>
</item>
<item android:drawable="@drawable/btn_welcome_outline" />
</layer-list>
When I click it, I want to 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. I’m doing this by layering a selector on top of that outline I just talked about.
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 always going to be drawn. Then the other layer is a selector, and the selector just has two states in it. When it’s pressed, I want to draw this other shape.
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, because I can just say, “I want you to have a solid color, but also have the corner so it doesn’t end up bleeding out of the corners.” Then when it’s not pressed and in the default state, it’ll just be transparent.
That’s great and all, but then in version 21 of Android they added these nice ripple drawables which look really pretty and doing that requires a different set of code.
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/blue_200"
>
<item android:drawable="@drawable/btn_welcome_outline" />
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<solid android:color="@color/white" />
<corners android:radius="@dimen/corner_radius_tiny" />
</shape>
</item>
</ripple>
For that you use ripple XML, which was added in version 21. Inside there is where you specify the color of the ripple. After that, I still have the same button outline as what’s actually being drawn in that ripple, but then the last part of this defines a mask. And that mask basically says, “This is the outline of where the ripple should appear.” 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. 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. Drawable XML is a fairly good way to skip a lot of work. And then I just had to ask design for the colors. I didn’t need anything else.
Vector drawables. The shape drawables let you draw very simple shapes. Nothing complicated. Vector drawables let you do most any sort of vector drawing you want. It allows you to do very complex shapes, and the advantage to using a vector is that you don’t have to worry about screen density all that much. Because before I was having to get these PNGs from design that were 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. That’s a huge time-saver, but there is one problem with the way that Android implemented vector drawables.
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 14 or something like that. And 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 tools know how to output in SVG, and none of them know how to output as vector drawables. You need some way to convert these SVGs that your designers are giving you into vector drawables in the app.
There are two ways of doing that. 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 don’t want to have to go through a wizard every time I import a new asset. So instead–we wrote this before all the vector drawable back compat stuff happened, but we’re still using it–there’s this Android plugin that we wrote called Victor.
Victor lets you define any number of source sets, anywhere that you have your SVGs, and it’ll slurp all those up and then output something that the system can render. For a while it just output PNGs, but then we were able to actually grab the code that’s in this new vector asset stuff and use that to convert straight into vector drawables. That’s great because with Trello, our designers have their own Git repository which is where they put all of their compiled SVGs. 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.
The last thing I’d like to say about drawables, that has really saved a lot of time recently, is the difference between skeuomorphic and flat design. Skeumorphic design is where you have things that look exactly like what they’re supposed to be. On the left is Andie Graph, which is an app that my friend wrote that makes your phone act exactly like a Texas Instruments scientific calculator and look exactly like it. It looks very realistic. That’s skeumorphic.
On the right, you have the normal calculator app, which is flat, and every button is just this flat color. As nice as the thing on the left looks, on the right all the icons and all the text are just flat colors. 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.
In the Trello app, all of our assets are flat black colors. They’re black on alpha, and then in code, we take those and tint them whatever color we want. 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 want to change the colors, we can just change it in code.
In terms of tinting images, there are a few ways to do it.
One is to do it via XML, but besides the fact that the image view tint attribute doesn’t work very 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.
I ended up doing most of the tinting in code.
Simple
drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
Comprehensive
Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
DrawableCompat.setTint(wrappedDrawable, color);
It’s very simple with image view and drawables. You can call setColorFilter
and just pass in the color. Then, for a black icon on alpha, you want to use the PorterDuff.Mode
of SRC_IN
.
Now if you want a really comprehensive solution, the libraries support DrawableCompat
. With that you actually wrap the drawable and then call setTint
or setTintList
on that wrappedDrawable
. The 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 is just a faster, easier way of doing it.
Conclusion
On my blog at danlew.net I’ve written more about some of these things, and some parts of this talk were taken from other talks that were more detailed, in particularly the styles and themes. 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. Thank you very much.
Receive news and updates from Realm straight to your inbox