Inside Views
Recently, at jtribe, we’ve had a series of presentation amongst ourselves regarding how different platforms, whether it be iOS, web or Android, handle the laying out and presentation of a user interface. This kinda happened organically — one of us originally did a discussion of how AutoLayout for iOS works, and then a couple of weeks later someone decided to do a presentation about CSS.
So when it was finally my turn, which unfortunately kept on getting pushed back thanks to the Christmas break, I thought that it would be nifty to sorta complete the trifecta of talks that covered that little slice of mobile and web development: that of the technical bits of how we communicate with the users in a forward-facing way. In particular, the talk covered Views — what they are, what they do, and how they go from a big ol’ XML file to what you see on the screen.
People who aren’t entirely familiar with Android development will likely get the most benefit out of this article, as I’d imagine that most established native Android developers will be familiar with the concepts and technical details discussed below.
Anyhow, let’s start by examining what exactly a View is.
What is a View?
In Android, a View is a pretty simple concept — it’s an element that typically sits on the screen as part of an Activity. Things like Buttons, TextViews and LinearLayouts are all Views, and despite their obvious differences they all share similarities.
In the above image, we have examples of a LinearLayout, a TextView, an ImageView containing a picture of a little robot dude, a Button and a FrameLayout that’s hardcoded to be a 240px by 240px square positioned directly below the Button. The highlighting around each of the Views is due to a setting in the developer options that shows layout boundaries, padding and margins; it also makes it way easier to see where each of the elements are — even the otherwise invisible ones!
The flow of the elements on the screen can go whichever way you fancy, with top to bottom and start to end being the stock-standard ways of laying out your Views. In this case both these flows are achieved by nesting: the root of this particular screen imposes a vertical orientation, while the TextView and the ImageView that sit side-by-side are inside a LinearLayout: a specialisation of a type of View known as a ViewGroup that can house other Views. LinearLayouts are amongst the most straightforward ViewGroup variants; they let you position Views linearly one after another from top to bottom or from start to end. They’re analogous to UIStackView in iOS, or linearly positioning elements in a div
in HTML/CSS.
While the above layout may look simple, it’s actually pretty powerful. We’ve got a number of elements on the screen that could all be part of a cohesive user flow if we wanted to put in the work and prettify it. For example, we’ve got a TextView that could easily prompt the user to press the button to select an image, a Button could open a file picker and an ImageView that might display the image chosen — perfect for an image uploader that connects to some online service.
What does a View actually do?
So now that we know what Views can look like, what exactly are their responsibilities? What do they do?
Three things come to my mind! Some of these might be pretty obvious, others not so much:
- A View is responsible for drawing itself within the bounds it negotiates with its parents;
- A View is responsible for handling certain events;
- A View is responsible for saving its state when asked to;
That’s about it.
Drawing
The first item on the list is probably the most intuitive of the lot, other than maybe the last bit. A View like a Button needs to represent itself, right? Buttons in particular have multiple states that are entirely dependent on which state of interaction with the user it’s in. Give it a go! Fire up an Android app and press your finger down on a Button. When the Button changes its appearance, that’s the result of it handling that touch event and redrawing itself accordingly.
That’s pretty straightforward. But what about that last part? What’s this about parents?
A ViewGroup is considered to be the parent to any of the Views nested inside of it. So if we have a TextView inside a LinearLayout, that TextView is the child of that LinearLayout; likewise, the LinearLayout is the parent of that TextView. If you think back to the very first example in this article, you’ll recall that we had a TextView inside a LinearLayout.
The ImageView adjacent to the TextView is likewise a child of the LinearLayout; therefore, the LinearLayout is the ImageView’s parent.
Coincidentally, that LinearLayout is also the child of a much bigger LinearLayout.
When a child view wants to measure itself out so it can draw itself on the screen, what typically happens is a little negotiation process between the View and its parent. To put it simply, the child wants to grab a certain amount of space (in most cases it’ll be either as much space as it can possibly be allowed or just enough to fit its contents), while the parent view will only have so much space to give. There’s obviously a little mismatch of goals going on here, so Views might end up being measured a few times as things are sorted out before everything is done and dusted.
Once the amount of space allotted to a child view is sorted out, it’ll then be able to draw itself.
Events
Unless a View is a piece of minimalist art, it’ll need to be able to respond to certain kinds of events — touch gestures, maybe listening to layout passes, changes in state — and it is the responsibility of a View to do this.
An example of this is a Button. A Button has to respond to taps on it, otherwise it’s not much of a Button. So when we put our finger down on one and it does its little hover effect and then we lift our finger off it and the Button executes some kind of action, there must be something handling it.
As it turns out, there’s a method that does all that called onTouchEvent
which calls appropriate listeners depending on what we’re doing with our fingers. Inside there is the branch that handles “click” events — a down press followed by a finger up — and sets a whole lot of state, which is used to determine and draw the appropriate visual representation of the View. It can also execute a callback if you provide it with one by calling setOnClickListener
for the View in question; for example, if you wanted to execute an API call you might stick the code for doing that inside the OnClickListener
you set for that button.
Saving State
This last responsibility might not be immediately obvious unless you’re familiar with how Android works in regards to memory management and how the operating system treats apps and Activities that reside in the background, but for the purposes of giving you a general idea, here’s an explanation in twenty words or less:
When an Activity isn’t the active thing on the screen, Android can make it croak.
But that isn’t all.
When the Activity gets destroyed by Android, certain details about it can get stashed away for safekeeping. These are typically quite simple things: numbers, Strings, maybe some more complicated objects if they implement the right interfaces (though you should keep in mind that there are limits on how much you can stash). This allows us to persist details across different instantiations of an Activity if the OS decides to kill it without us having any say in it whatsoever.
So let’s say we have a slider bar. When the Activity gets nuked, we might want to retain the position of the bar so that it can be restored. It would be entirely reasonable for the View to handle this, and by overwriting the BaseSavedState
class and storing what we need, we can then plonk it into the Parcelable
we use to persist the state so that if the Activity dies, that particular detail can be extracted and plugged back into the View. It will pick up where the previous iteration left off.
To illustrate, in the GIF below I have set my emulator to destroy any Activities that get backgrounded:
How do we get all of those Views on the screen?
In most cases, Android View hierarchies are inflated from layout resources.
Not the most intuitive explanation, yeah? Well, in most cases manually constructing your layout in code, while doable, is the antithesis of fun. Imagine a complex user interface with a list of material design cards that each house an image and a little paragraph of text, like items in a newsfeed or something, with perhaps a couple of floating action buttons down the bottom of the screen and a toolbar up the top complete with a custom overflow menu.
Imagine how nasty trying to put that all together in code would be:
You’d create the layout that encompasses everything, then the list, then the things that go into the list, then the floating action buttons, and oops, we forgot the toolbar so we better go and slot that in too! But to get the toolbar to look and behave the way we want, we have to wrap that in its own little layout. So suddenly you’re programmatically instantiating a whole slew of things that you have to keep track of, each of which use certain kinds of LayoutParams that are determined by what the parent ViewGroup is.
What if you then wanted to rearrange some of these elements at some stage, like if you wanted to add in a navigation drawer? There’s no simple place to start. And this is why we use what’s known as layout inflation.
Layout inflation is an extremely important concept in Android. It’s the process of taking a layout resource, which will be a pre-processed XML file describing the View hierarchy we wish to instantiate, and turning it into a View object which represents the root of that hierarchy. In other words, we turn the layout represented by this XML:
into
This process is handled by the LayoutInflater. It walks through the hierarchy represented by the XML, instantiating the Views contained within.
This process does tend to utilise reflection, which is traditionally quite slow on Android, but for the most commonly-used Views it’s not really an issue. A LayoutInflater will have a little factory inside it for manufacturing different kinds of Views, so for widespread things like TextViews and Buttons it will just be calling through to a plain old hardcoded constructor (so in the case of the aforementioned support library, it may eventually land in a bit of code that has a switch statement just like this).
In the case of custom Views, reflection is a necessity; however, because the whole process is highly optimised, the difference in performance between manually instantiating the hierarchy in code and via inflation is negligible. Constructors fetched through reflection are also cached, so if a custom View is reused multiple times in a screen it will simply reuse the constructor that was grabbed the first time ‘round.
Additionally, Android XML layout resources support creating layouts by composition. You can easily reference multiple layouts in one parent layout by utilising <include/>
tags. This can be useful for keeping your layouts down to a manageable size as well as encouraging layout reuse.
You might also note that in the example gist above each of the views has a number of different properties (in the above example these are prefixed by the android:
namespace) defined in their XML. These are collected by the LayoutInflater and plugged into the View as a set of attributes, which then makes use of them to determine things like height, width, which text to display, gravity and so on.
In a nutshell, it creates the View hierarchy for you — removing all of the pain that comes with programmatically setting up your layout.
Whoa, that’s a lot!
Yep! Though this is an extremely filtered-down description of Views and how we use ’em, it’s still pretty dense.
That’s the nature of it though. How Views work is a massive topic, and if we were to delve down into the specifics of how measurement, drawing and the View lifecycle works we’d have a book on our hands. I’d hope, however, that I’ve presented what could be a reasonable starting point for further exploration, especially for technical folk who might not yet be well-established Android developers.
About Us
At jtribe, we proudly create software for iOS, Android and Web and are passionate about what we do. We’ve been working with the iOS and Android platforms since day one, and are one of the most experienced mobile development teams in Australia. We measure success on the impact we have, and with over six-million end users we know our work is meaningful. This continues to be our driving force.