In this chapter you will learn how to bring those views together into one layout and how to use the Fragment class to inflate and interact with those layouts. You will also learn about the variety of view groups available for you to combine views as needed.
ViewGroup class is for views that can contain one or more child views.
ViewGroup provides the standardized methods for these classes to use so that they can perform tasks such as adding, removing, getting, and counting child views. The primary method you will use to find a child is
findViewById(int), which is actually defined in the
Each child class of
ViewGroup has a different means of positioning the views it contains, as detailed shortly, but (with very few exceptions) views are drawn in the order they are added to a view group. For example, if you have an XML layout that defines a
ImageView, and a
Button, those views will be drawn in that exact order regardless of their position on the screen.
One more useful thing to know is how to iterate through all the views belonging to a given
ViewGroup. To do so, you will use
getChildCount() and then a traditional for loop with
If you wanted to start off with something easy, this is the view to do it. The
FrameLayout class just aligns each child view to the top left, drawing each view on top of any previous views. This might seem a bit silly as a way of grouping views, but this class is most commonly used as a placeholder, especially for fragments, which are covered later in the chapter.
LinearLayout aligns its children one after another, either horizontally or vertically (depending on its
orientation attribute). You can specify
gravity, which controls how the layouts are aligned within this view group (e.g., you could have a vertical series of views aligned to the horizontal center of the view group). You can also specify
weight, a very useful technique for controlling the way views in a
LinearLayout grow to use the available space.
One more thing to note is that
weight is taken into account after all the views are measured. If you have three views that are 20dp and a total of 90dp of space to put them in, setting a weight of 1 on one of those will make that view take the remaining 30dp of space to be 50dp total. If the views had all been 30dp, the weight of 1 would have made no difference because there would be no extra space to use.
If you apply
weight to more than one view, each view will grow in proportion to its
weight. To calculate the ratio that it grows, you divide the
weight of the view by the
weight of all children in that
As the name indicates, you specify its children relative to each other or to the
RelativeLayout itself. Not only is this an extremely efficient way to create semicomplex layouts that adapt to a variety of screens, it also allows you to create overlapping views and views that appear to float on top of others.
Sometimes you have a large data set to work with and creating views for every piece of data is impractical. Other times you simply want an easy and efficient way of creating views for some collection of data. Fortunately,
AdapterView was created for these types of scenarios.
AdapterView itself is abstract, so you will use one of its subclasses such as
ListView, but the overall idea is the same. You have a data set, you throw it at an
Adapter, and you end up with views in your layout.
ListView presents a vertically scrolling list of views that can be reused. Figure blow illustrates what happens when this view is scrolled.
There is also a special version of
ExpandableListView, which is used when you have two levels of content. For example, you might list all the countries of the world and then you could expand each country to show its states or provinces.
ExpandableListView requires an
A GridView is a two-dimensional grid of views populated by the associated
ListAdapter. One nice feature is that you can let the number of columns be automatically determined based on size, which makes this view group easy to use. Most commonly, you will see this used for a series of icons or images, although it is not limited to that functionality.
When you need to give the user an easy way to select from multiple predefined choices, a
Spinner is often a good solution. This class shows the currently selected choice and, when tapped, presents a drop-down menu of all the choices. A
Spinner requires a
SpinnerAdapter, which determines what the drop-down choices look like and what the currently selected item looks like when closed.
Gallery class provides a way to show horizontally scrolling views backed by an
Adapter. The original purpose was for, as its name states, displaying a gallery of (center-locked) photos. Each view was an
ImageView. Because of this,
Gallery does not recycle any of its views and is extremely inefficient.
Gallery has been deprecated, and you should not use it. Instead, consider
Adapter is the interface that takes a data set and returns views representing that data. The adapter is able to say how many items there are, return an item for a specific position, and return the view associated with a position, among other things.
The most important method of
getView(int position, View convertView, ViewGroup parent). This is where the adapter provides the actual view that represents a given position. The
convertView parameter is for passing in any existing view of the same type that can be reused, but you must handle the case of this being
null because the first calls to this method will not have an existing view to reuse and
AdapterView does not require recycling views when it is extended. The third parameter,
parent, is the
ViewGroup that the view you’re building will be attached to. You should not attach the view yourself; instead, the
parent is meant to be used for supplying the appropriate
You will commonly use one of these
AdapterView subclasses to allow each item to be interacted with. Instead of manually assigning event listeners to each view you generate, you can instead set a listener on the
Being able to swipe horizontally through full pages of content has been common behavior since before Android, but its prevalence (e.g., the default launcher in each Android version) did not mean that it was supported by a native component. Instead, this pattern, which was originally referred to as “workspaces,” was implemented directly without abstraction.
ViewPager was added to the support library (http://developer.android.com/tools/extras/support-library.html), so you can add it to any project that runs Android 1.6 or newer. A common use of this class is in apps that uses tabs for navigation; the user can swipe across each page or tap a tab to jump to a specific page.
ViewPager takes a
PageAdapter that supplies the views, and one of the most common uses is to actually provide fragments via the
As mentioned in the first chapter, most apps have a toolbar at the top called the app bar. When this concept was introduced into the Android framework in Android 3.0 (referred to as the action bar at that time), its implementation made it part of the window décor, causing it to behave differently from other views and making a lot of desirable features exceedingly difficult to implement. In fact, that implementation caused a very specific problem: a navigation drawer could not slide in front of the app bar.
Android 5.0 introduced the solution: the
Toolbar class. With this class, you can easily slide a navigation drawer in front of the rest of your app, animate the size of the app bar dynamically, and add whatever features you want. Because
Toolbar is just another view, it can be included anywhere in your layouts and you have much better control of how you use it. What’s even better is that this class is included in the support library, so you can use it for older versions of Android as well.
AbsoluteLayout— Deprecated layout that was used to position views based on exact pixels. Do not use this layout, but be aware that it exists so that you can shame developers who do use it
AdapterViewAnimator— Switches among views that are supplied by an Adapter, using an animation for the transition. Introduced in API level 11
AdapterViewFlipper— Similar to
AdapterViewAnimatorbut supports automatically changing the view based on a time interval (e.g., for a slideshow). Introduced in API level 11
AppWidgetHostView— Hosts app widgets, so you will probably only use this if you create a custom launcher
DialerFilter— Hosts an EditText with an ID of android.R.id.primary and an EditText with an ID of android.R.id.hint as well as an optional ImageView with an ID of android.R.id.icon to provide an easy means of entering phone numbers (including letters that can be converted to numbers). You will probably never use this
FragmentBreadCrumbs— Simplifies adding “breadcrumbs” (like displaying “Settings > Audio” as the user navigates deeper into content) to the UI, but it was deprecated for Android 5.0
GestureOverlayView— Exists on top of one or more other views to catch gestures on those views
GridLayout— Organizes its children into a rectangular grid to easily align multiple views. Introduced in API level 14 but exists in the support library
HorizontalScrollView— Wraps a single child view (usually a
ViewGroup) to allow it to scroll horizontally when the content is larger than the view’s visible dimensions
ImageSwitcher— Switches between images with an animation (see ViewSwitcher)
MediaController— Contains views to control media such as play, pause, fast forward, and a progress indicator
PagerTabStrip— Provides interactivity to a
PagerTitleStrip, allowing users to tap on a page title to jump to that page. Included in the support library
ScrollView— Wraps a single child view (usually a
ViewGroup) to allow it to scroll vertically when the content is larger than the view’s visible dimensions
SearchView— Provides a UI for allowing the user to search with the results coming from a
SearchProvider. Introduced in API level 11 but also included in the support library
SlidingDrawer— Holds two views: One is a handle and the other is the content. The handle can be tapped to show or hide the content, and it can also be dragged. This is the original app drawer in Android 1.x and is a very dated view. This class was deprecated in API level 17 and should not be used anymore
StackView— Stacks multiple views that can be swiped through (so you can get an effect like multiple physical photos in a stack). The views are provided by an Adapter and are offset to show when more are below the top view. This is most commonly used as an app widget, and it was introduced in API level 11
TabHost— Hosts tabs and a single
FrameLayoutfor the content of the currently active tab. This was used for most tabbed interfaces prior to Android 3.0; most tabbed interfaces now use tabs in the app bar
TabWidget— Lives within a
TabHostand provides the tab event triggers
TableLayout— Allows you to organize content in a tabular fashion, although you should generally use a
GridLayoutbecause it is more efficient
TableRow— Represents a row in a
TableLayout, although it is essentially just a
TextSwitcher— Animates between two TextViews. This is really just a
ViewSwitcherwith a few helper methods
ViewAnimator— Switches among views, using an animation
ViewFlipper— Similar to
ViewAnimatorbut supports automatically changing the view based on a time interval (e.g., for a slideshow)
ViewSwitcher— Animates between two views, where one is shown at a time
ZoomControls— Controls zoom. No, really. It provides zoom buttons with callbacks for handling the zoom events
One problem that plagued Android a bit early on was that there was no standardized way to encapsulate view logic for use across activities. This was not a major issue because one screen was typically represented by one activity and one layout; however, it started to become a problem when tablets gained popularity. Where you might display a list of news articles on the phone that you can tap to go to a full detail page, you would probably show that list on the left side of the tablet and the details on the right, so they’re always both visible. That presented a challenge because your code to populate the list was likely to be living in one activity and the detail page code was in another, but the tablet was only ever showing one activity and needed the logic from both. Enter the
Fragment is another one of those classes that is a bit tough to describe up front but quickly makes sense as you use it. Think of a fragment as a chunk of your UI, containing the code necessary to inflate or construct a layout as well as handle user interaction with it. The fragment might even load content from the web or other source. A fragment can be simple, such as a full-screen
ImageView, perhaps with a caption, or it can be complex, such as a series of form elements containing all the logic to validate and submit form responses. In fact, a fragment does not even have to be used for UI; it can be used to encapsulate application behavior needed for activities.
Like activities, fragments have a lifecycle. In fact, activities are closely tied to fragments, and the activity lifecycle influences the lifecycle of the fragment associated with it. First, the fragment runs through this series of lifecycle events in the order they are presented here:
onAttach(Activity)— Indicates that the fragment is associated with an activity; calling
getAcitivity()from this point on will return the
Activitythat is associated with the fragment
onCreate(Bundle)— Initializes the fragment
onCreateView(LayoutInflater, ViewGroup, Bundle)— Returns the view associated with the fragment.
onActivityCreated(Bundle)—Triggered to coincide with the activity’s
onViewStateRestored(Bundle)— Triggered to indicate that the state of views (such as the text in an
EditTextinstance from another orientation) has been restored
onStart()— Triggered to coincide with the activity’s
onStart()method and displays the fragment
onResume()— Triggered to coincide with the activity’s
onResume()method and indicates the fragment can handle interaction. After the fragment has “resumed,” it will stay in that state until a fragment operation modifies that fragment (such as if you are removing the fragment from the screen) or its activity is paused. At that point, it will run through this series of lifecycle events in the order presented
onPause()— Triggered to coincide with the activity’s
onPause()method or when a fragment operation is modifying it
onStop()— Triggered to coincide with the activity’s
onStop()method or when a fragment operation is modifying it
onDestroyView()— Allows the fragment to release any resources associated with its view; you should null out any view references that you have in this method
onDestroy()— Allows the fragment to release any final resources
onDetach()— Gives the fragment one last chance to do something before it is disassociated from its activity; at this point getActivity() will return null and you should ensure that you do not have any references to the activity
One of the great things about fragments is that the system manages them for you. Things like configuration changes (e.g., orientation changes) are easily handled because fragments can save state and restore state. To do so, they must have a default constructor (i.e., a constructor that has no parameters). So, how do you pass data to them if they require a default constructor? The standard way is via a static
newInstance() method that sets up the fragment’s arguments before it is attached to an activity.
You can see that the static
newInstance(String) method creates the fragment using the default constructor and then it creates a new
Bundle object, puts the text into that bundle, and assigns that bundle as the fragment’s arguments. The bundle is maintained when the fragment is destroyed and will be automatically set for you if it’s created again (e.g., when a rotation triggers a configuration change, your fragment is destroyed, but a new one is created and the bundle is assigned to its arguments).
Obviously, using a fragment just for a
TextView is contrived, but it illustrates how you can set data on a fragment that is retained across configuration changes. In doing this, you can easily separate your data from its presentation. Ideally,
onCreateView(LayoutInflater, ViewGroup, Bundle) would inflate an XML layout, which might be different for landscape versus portrait. With your code designed in this way, the orientation change will just work with no extra effort on your part.
Fragments can also be set to be retained across activities with
setRetainInstance(true). This allows you to keep data around that isn’t configuration-specific and is otherwise hard to put into a
Bundle. When using this feature, the
onDestroy() method is not called when the activity is destroyed and the subsequence
onCreate(Bundle) is not called, because the fragment already exists.
Although fragments can do a lot of things, it’s still quite common to need to talk to the activity they are attached to. For instance, you might have a custom
DialogFragment and you need to tell the activity which button the user pressed. In other situations, you would do this with an interface and a setter method, but the fragment lifecycle makes that problematic.
When the user rotates the device, the activity and fragment go away and the new versions are created. Because the fragment is created with an empty constructor, it no longer has reference to the interface you might have passed in. Instead, you do this by casting the activity. Because blindly casting can easily create bugs, it is a good idea to verify that the activity implements the correct interface in the
onAttach(Activity) method and throw an exception if it does not.
In many cases, you will not need to worry about fragment transactions directly. You are able to embed fragments in XML, just like views, and
DialogFragments have a
show() method that just takes the
FragmentSupportManager when using the support library) and a string tag to later find the fragment again.
When you do need to add a fragment to the UI programmatically, you use a fragment transaction obtained from the
FragmentManager’s (or the support version’s)
beginTransaction() method. A fragment transaction can add, detach, hide, remove, replace, and show fragments and a single transaction can include multiple commands.
When the transaction is ready, you call either
commitAllowingStateLoss(). The former is more common but throws an exception if triggered after the activity has saved its state; the latter will not throw an exception, meaning that changes committed after an activity’s state has been saved (such as just before an orientation change) could be lost.
Finally, a fragment transaction can add itself to the back stack, meaning that pressing the back button will reverse the transaction, by calling
Despite the capabilities that fragments bring to Android, they are not without their problems. The fragment lifecycle is complex, debugging is challenging both due to the underlying source code (particularly due to all the different states) and the asynchronous nature of fragment transactions, and the recreation of fragments using reflection (which means you can’t use anonymous classes or any other fragment that doesn’t have a default constructor).
When making a mistake, it is not uncommon for it to show up later on (either due to a configuration change or an asynchronous transaction), which means the code that actually caused that situation can be very hard to track down.
Although most Android developers use fragments regardless of these issues, there are many other approaches to breaking UI into reusable pieces. Some developers create their own solutions on a case-by-case basis, some create custom view groups as fragment replacements, and some use a variety of third party libraries (such as Flow and Mortar, both developed by Square and available at http://square.github.io/).
One of the challenges of Android is that it is an open source operating system used in countless devices. Many of the manufacturers aren’t particularly incentivized to provide OS updates after a year or two when you may be looking to upgrade to a new device. One of the ways Google has combated the challenge of developing software for an operating system that evolves extremely rapidly and yet is frequently not up to date on most devices is the support library.
There are eight more libraries that you should know about:
RecyclerView, and Support Annotations. To use any of them, be sure that you’ve installed the Android Support Repository via the SDK manager. Below listing shows the Gradle dependencies for these libraries:
One of the fundamental parts of Material Design is shadows. Unfortunately, this isn’t a feature that is easy to support on older versions of Android because of fundamental rendering changes. The CardView library is meant to help with that by providing the
CardView class, a concrete implementation of a card (a piece of paper) with support for shadows on older versions of Android by using an image. Each card view can hold one child view and give it shadows with support for dynamically changing elevations.
The design library provides concrete implementations of a variety of Material Design elements such as the FAB (with the
FloatingActionButton class), snackbars (with the
Snackbar class), scrollable and fixed tabs (with the
TabLayout class), the navigation drawer (with the
NavigationView class), and even floating labels for text entry (with the
Two other major classes to know in this library are the
CoordinatorLayout and the
AppBarLayout. These classes allow you to do things like moving your FAB out of the way when you display a snackbar or scrolling the app bar off the screen while scrolling down a list and back on when scrolling up.
Many of these classes are used in future chapters in this book, but it’s a good idea to read the initial announcement of this library so that you can get a feel for what’s in it at http://android-developers.blogspot.com/2015/05/android-design-support-library.html.
Occasionally you need to align views in a dynamic or complex grid and using relative layouts or nested linear layouts is problematic. In these cases, using the
GridLayout class can be a good solution. This class was made available in API level 14 (Android 4.0), but this library allows you to use it with older versions of Android.
Apps designed for the TV have fundamentally different design requirements. The so-called 10-foot view necessitates larger fonts and pictures, simple directional navigation, and search. This library provides fragments to simplify implementing browsing rows of content, viewing details, video playback, and search for Android TV apps. For more information about designing for the TV experience, see http://developer.android.com/design/tv/.
One common challenge in designing apps is dynamic images. If your app displays a lot of dynamic images, you have to be very careful about what colors you include in the UI around them. It’s easy to end up with something that clashes or detracts from the experience.
The two main ways designers have gotten around this issue is to either design a UI with limited colors (that’s why so many photo apps and websites are white, black, or gray) or to use colors from the images themselves. The second solution is what the
Palette class provides. It can analyze an image and give you the vibrant and muted colors (plus dark and light versions of each) from the image, allowing you to easily color buttons, chrome, or other UI elements dynamically.
For most lists of content, a
ListView class works fine. Unfortunately, there are some issues. For instance, you might try animating a view within a list, but scrolling causes that view to be reused while the animation is still going on, leading to a very confusing experience for users.
In Android 4.1 (API level 16),
ViewPropertyAnimator-based animations no longer had this problem issue and the
View class had another method added called
setHasTransientState(boolean), specifically designed to tell the adapters that a view was in a transient or temporary state and shouldn’t immediately be reused.
You also can’t create content that is laid on horizontally or in a grid. The
RecyclerView class is provided by this library to solve these problems and more. It is supported all the way back to API level 7 (Android 2.1) and it can handle custom animations and layouts.
One of the challenges in writing code is knowing what is allowed or expected. For instance, if you call a method that returns a collection of items, what happens if it has no results? Depending on the developer, it could return null or it could return an empty collection. What if you have a method that takes a color resource ID and you want to prevent someone from accidentally passing in a raw color
int? The support annotations solve these problems.
You can specify a parameter or return value as
@Nullable to indicate that it can be
@NonNull to indicate that it can’t. You can also declare that a given int is a color resource ID with
@ColorRes (and there are annotations for each of the types of resources such as
In addition, there are times in Android when you want to use an enum, but you don’t want the performance penalty of full Java classes which Java creates for each enum. Typically these are
ints, but you have to rely on code comments to get someone to pass in correct values. The annotation library includes
@StringDef for these cases.
Give yourself a pat on the back; you’ve almost made it to the good stuff. You should now have a solid understanding of how the
ViewGroup class and its subclasses work as well as how to use fragments to create reusable layouts with display and handling logic contained within.
Plus, you’re aware of the large number of support libraries that are available to make your life easier. Combine that with the knowledge from Chapter 2 and you know the most important aspects of getting your layouts on the screen exactly where you want them.
(To Be Continued)