Android User Interface Design: Understanding Views - Creating Full Layouts With View Groups and Fragments

Understanding Views - Creating Full Layouts With View Groups and Fragments

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.

UNDERSTANDING VIEWGROUP AND THE COMMON IMPLEMENTATIONS

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 View class.

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 TextView, an 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 getChildAt(int).

1
2
3
4
5
final int childCount = myViewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
View v = myViewGroup.getChildAt(i);
// Do something with the View
}

FrameLayout

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

A 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 LinearLayout.

RelativeLayout

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.

AdapterView

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

ListView presents a vertically scrolling list of views that can be reused. Figure blow illustrates what happens when this view is scrolled.

Process of scrolling a list view

There is also a special version of ListView called 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 ExpandableListAdapter.

GridView

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.

Spinner

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.

Gallaery

The 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 ViewPager or RecyclerView.

Adapter

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 Adapter is 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 LayoutParams.

Interfaces for AdapterView

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 AdapterView.

ViewPager

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.

Fortunately, the 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.

A ViewPager takes a PageAdapter that supplies the views, and one of the most common uses is to actually provide fragments via the FragmentPagerAdapter.

Toolbar

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.

Other Notable ViewGroups

  • 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 AdapterViewAnimator but 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 FrameLayout for 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 TabHost and provides the tab event triggers
  • TableLayout — Allows you to organize content in a tabular fashion, although you should generally use a GridLayout because it is more efficient
  • TableRow — Represents a row in a TableLayout, although it is essentially just a LinearLayout
  • TextSwitcher — Animates between two TextViews. This is really just a ViewSwitcher with a few helper methods
  • ViewAnimator — Switches among views, using an animation
  • ViewFlipper — Similar to ViewAnimator but 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

ENCAPSULATING VIEW LOGIC WITH FRAGMENTS

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.

Like Activity, Context, and Intent, 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.

The Fragment Lifecycle

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 Activity that 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 onCreate() method
  • onViewStateRestored(Bundle) — Triggered to indicate that the state of views (such as the text in an EditText instance 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

Activity Fragment Lifecycle

Giving Fragments Data

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class TextViewFragment extends Fragment {
/**
* String to use as the key for the "text" argument
*/
private static final String KEY_TEXT = "text";
/**
* Constructs a new TextViewFragment with the specified String
*
* @param text String to associated with this TextViewFragment
* @return TextViewFragment with set arguments
*/
public static TextViewFragment newInstance(String text) {
TextViewFragment f = new TextViewFragment();
Bundle args = new Bundle();
args.putString(KEY_TEXT, text);
f.setArguments(args);
return f;
}
/**
* Returns the String set in {@link #newInstance(String)}
*/
public String getText() {
return getArguments().getString(KEY_TEXT);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
TextView tv = new TextView(getActivity());
tv.setText(getText());
return tv;
}
}

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.

Taliing to the Activity

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/**
* DialogFragment with a simple cancel/confirm dialog and message.
*
* Activities using this dialog must implement OnDialogChoiceListener.
*/
public class SampleDialogFragment extends DialogFragment {
/**
* Interface for receiving dialog events
*/
public interface OnDialogChoiceListener {
/**
* Triggered when the user presses the cancel button
*/
public void onDialogCanceled();
/**
* Triggered when the user presses the confirm button
*/
public void onDialogConfirmed();
}
private static final String ARG_CONTENT_RESOURCE_ID = "contentResourceId";
private static final String ARG_CONFIRM_RESOURCE_ID = "confirmResourceId";
private int mContentResourceId;
private int mConfirmResourceId;
private OnDialogChoiceListener mListener;
/**
* Creates a new instance of the fragment and sets the arguments
*
* @param contentResourceId int to use for the content such as R.string.dialog_text
* @param confirmResourceId int to use for the confirm button such as R.string.confirm
* @return new SampleDialogFragment instance
*/
public static SampleDialogFragment newInstance(int contentResourceId, int confirmResourceId) {
SampleDialogFragment fragment = new SampleDialogFragment();
Bundle args = new Bundle();
args.putInt(ARG_CONTENT_RESOURCE_ID, contentResourceId);
args.putInt(ARG_CONFIRM_RESOURCE_ID, confirmResourceId);
fragment.setArguments(args);
return fragment;
}
public SampleDialogFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Bundle args = getArguments();
if (args == null) {
throw new IllegalStateException("No arguments set, use the"
+ " newInstance method to construct this fragment");
}
mContentResourceId = args.getInt(ARG_CONTENT_RESOURCE_ID);
mConfirmResourceId = args.getInt(ARG_CONFIRM_RESOURCE_ID);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(mContentResourceId)
.setPositiveButton(mConfirmResourceId, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// Send the positive button event back to the host activity
mListener.onDialogConfirmed();
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// Send the negative button event back to the host activity
mListener.onDialogCanceled();
}
}
);
return builder.create();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnDialogChoiceListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
}

Fragment Transactions

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 FragmentManager (or 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 commit() or 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 addToBackStack(String).

1
2
3
4
getSupportFragmentManager().beginTransaction()
.add(R.id.container, ExampleFragment.newInstance())
.addToBackStack(null)
.commit();

Controversy

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/).

THE SUPPORT LIBRARY

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: CardView, Design, GridLayout, Leanback, MediaRouter, Palette, 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
dependencies {
// AppCompat - likely in every app you develop
compile 'com.android.support:appcompat-v7:22.2.1'
// CardView - for paper with shadows on older versions
compile 'com.android.support:cardview-v7:22.2.1'
// Design - for Material Design views and motion
compile 'com.android.support:design:22.2.1'
// GridLayout - for laying out views in a grid
compile 'com.android.support:gridlayout-v7:22.2.1'
// Leanback - for fragments that simplify TV apps
compile 'com.android.support:leanback-v17:22.2.1'
// MediaRouter - for outputting media to various devices
compile 'com.android.support:mediarouter-v7:22.2.1'
// Palette - for extracting colors from images
compile 'com.android.support:palette-v7:22.2.1'
// RecyclerView - for advanced AdapterView needs
compile 'com.android.support:recyclerview-v7:22.2.1'
// Support Annotations - for Java annotations to prevent bugs
compile 'com.android.support:support-annotations:22.2.1'
// Support V13 - probably not required in your app
compile 'com.android.support:support-v13:22.2.1'
// Support V4 - included by AppCompat, so not necessary to add
compile 'com.android.support:support-v4:22.2.1'
}

The CardView library

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.

CardView Example

Design Library

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 TextInputLayout class).

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.

GridLayout Library

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.

Leanback Library

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/.

Palette Library

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.

RecyclerView Library

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.

Support Annotations Library

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 null or @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 @StringRes).

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 Strings or ints, but you have to rely on code comments to get someone to pass in correct values. The annotation library includes @IntDef and @StringDef for these cases.

SUMMARY

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)