Chapter 1
In This Chapter
Organizing the widgets on the device’s screen
Dealing with colors, sizes, and positions
Working with various layouts
Which description do you prefer?
The first description is more precise, but the first is also more brittle. As I type this introduction, I anticipate the email messages from readers: “You got one of the numbers wrong in the first description.”
The second description is also more versatile. The second description makes sense whether you describe an image on a laptop screen or on a highway billboard. In a world with all kinds of mobile devices, all kinds of screen sizes, screen resolutions, display qualities, refresh rates, and who-knows-what-other variations, the big picture is often more useful than the picky details. When you describe your app’s screen, you should avoid measurements in favor of concepts.
Android supports several layout concepts, including linear layout, relative layout, table layout, and frame layout. In many cases, choosing one kind of layout over another is a matter of taste. A table layout with only one row looks like a horizontal linear layout. A set of nested linear layouts may look exactly like a complicated relative layout. The possibilities are endless.
The game with Android’s layouts is to place visible things into a container in an orderly way. In a typical scenario, one of these “visible things” is a view, and one of these “containers” is a view group.
The formal terminology is a bit hazy. But fortunately, the fine distinctions between the terms aren’t terribly important. Here’s some formal terminology:
android.view.View
class. A broader use of the word view includes any class or interface in the android.view
package.A widget appears on the user’s screen and (either directly or indirectly) involves user interaction. (Sounds a lot like a view, doesn’t it?) The word widget commonly refers to a class or interface in the android.widget
package.
Views can be widgets, and widgets can be views. As long as your import
declarations work and your method parameter types match, the distinction between widgets and views is unimportant.
Commonly used widgets and views include buttons, check boxes, text views, toasts, and more exotic things such as digital clocks, sliding drawers, progress bars, and other good junk.
(Android or no Android, I think widget is a wonderful word. The playwrights George S. Kaufman and Marc Connelly made up the word “widget” for dialogue in their 1924 comedy Beggar on Horseback. I learned about widgets from the fictitious Universal Widgets company — an enterprise featured in The Wheeler Dealers from 1963.)
A view group (an instance of android.view.ViewGroup
) is a view that contains other views (including other view groups). The views contained in a view group are the view group’s children.
Examples of view groups are the linear layouts, relative layouts, table layouts, and frame layouts mentioned at the beginning of this chapter, as well as some more special-purpose things such as the list view and the scroll view.
One way or another, I usually write about putting a “view” on a “layout.”
Linear layouts have either vertical or horizontal orientation. Views in a vertical linear layout line up one beneath the other. Views in a horizontal linear layout line up one beside the other. (See Figure 1-1.)
You can create the layout in Figure 1-1 with the XML code in Listing 1-1.
Listing 1-1: A Horizontal Linear Layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<Button android:text="Button1"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/button1">
</Button>
<Button android:text="Button2"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/button2">
</Button>
<Button android:text="Button3"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/button3">
</Button>
<Button android:text="Button4"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/button4">
</Button>
</LinearLayout>
You can also create the layout in Figure 1-1 by dragging and dropping views onto the Preview screen in Android Studio’s Designer tool.
Linear layouts don’t wrap, and they don’t scroll. So, if you add six buttons to a horizontal layout and the user’s screen is wide enough for only five of the buttons, the user sees only five buttons. (See Figure 1-2.)
Using XML attributes, you can change a layout’s default behavior. This section has several examples.
You can tweak the size of a view using the android:layout_width
and android:layout_height
attributes. Listing 1-2 has some code, and Figure 1-3 shows the resulting layout.
Listing 1-2: Setting a View’s Width and Height
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=
"1. android:layout_height is wrap_content
android:layout_width is match_parent ">
android:id="@+id/button1"
</Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2. wrap/wrap">
android:id="@+id/button2"
</Button>
<Button
android:layout_width="60dp"
android:layout_height="wrap_content"
android:text="3. width 60dp">
android:id="@+id/button3"
</Button>
<Button
android:layout_width="160dp"
android:layout_height="wrap_content"
android:text="4. android:layout_width is 160dp">
android:id="@+id/button4"
</Button>
<Button
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="5. width wrap_content
height match_parent"
android:id="@+id/button5">
</Button>
</LinearLayout>
You can describe a view’s size using general guidelines or numbers of units, as spelled out in the next two sections.
To create general guidelines, use the "wrap_content"
, "match_parent"
, or "fill_parent"
value. (Refer to Listing 1-2.) With "wrap_content"
, Android shrinks a view’s width or length so that the view tightly encloses whatever it contains. With "match_parent"
and "fill_parent"
, Android expands a view’s width or length so that the view fits tightly inside its container.
In Listing 1-2, the first button has attributes android:layout_width="match_parent"
and android:layout_height="wrap_content"
. So in Figure 1-3, the top button is as wide as it can be and only tall enough to contain the words displayed on the button’s face.
In Listing 1-2, I describe the third button’s width in units. The value 60dp stands for 60 density-independent pixels. A density-independent pixel is a measurement based on a 160 pixels-per-inch benchmark.
“And what,” you ask, “is a 160 pixels-per-inch benchmark?” A pixel is a single dot on a device’s screen. A pixel can be invisible, glow brightly, or anything in between.
Different devices have different pixel densities. For example, a low-density screen might have 120 pixels per inch, and an extra-extra-high-density screen might have 480 pixels per inch. To adjust for these differences, each Android screen has several metrics. Each metric is a numeric value describing some characteristic of the display:
widthPixels
(an int
value): The number of pixels from the left edge to the right edge of the screen.heightPixels
(an int
value): The number of pixels from the top to the bottom of the screen.xdpi
(an int
value): The number of pixels from left to right along one inch of the screen.ydpi
(an int
value): The number of pixels from top to bottom along one inch of the screen. (In a square inch, some screens stuff more pixels across than up and down, so xdpi
isn’t necessarily the same as ydpi
.)densityDpi
(an int
value): A general measure of the number of pixels per inch. For screens with equal xdpi
and ydpi
values, densityDpi
is the same as xdpi
and ydpi
. For screens with unequal xdpi
and ydpi
values, somebody figures out what the screen’s densityDpi
is (but they don’t tell me how they figure it out).density
(a float
value): The number of pixels per inch, divided by 160.scaledDensity
(a float
value): Another take on the density
measure, but this time with some extra stretching or squeezing to account for any default font size chosen by the user. (Some versions of Android don’t let the user adjust the default font size, but with some third-party apps, a user can get around the limitation.)When you specify 160dp (as in Listing 1-2), you’re telling Android to display density
× 160 pixels. So, on my tiny screen, a width of 160dp is one inch, and on the Android home theater that you transport through time from the year 2055, a width of 160dp is one inch. Everybody gets the inch that they want.
Another handy unit of measurement is sp — scale-independent pixels. Like the dp unit, the size of an sp unit adjusts nicely for different screens. But the size of an sp unit changes in two ways. In addition to changing based on the screen’s pixel density, the sp unit changes based on the user’s font size preference settings.
If you want to be ornery, you can use physical units. For example, value 2in
stands for two inches. Other unsavory physical units include mm (for millimeters), pt (for points, with 1 point being 1/1000 of an inch), and px (for pixels — actual dots on the device’s screen). In almost all situations, you should avoid physical units. Use dp to specify a view’s size, and use sp to specify a text font size.
Figures 1-4 and 1-5 illustrate the relationship between pixels and density-independent pixels. The screen in Figure 1-4 has density 0.75. So an inch-wide button consumes 0.75 × 160 = 120 pixels. You can confirm this by comparing the sizes of the 160dp and 160px buttons. The 160dp button is roughly three quarters the width of the 160px button.
The screen in Figure 1-5 has a density 1.5, so in Figure 1-5, a 160dp button is 1.5 times as wide as a 160px button.
The XML document describing the layout in Figures 1-4 and 1-5 is shown in Listing 1-3.
Listing 1-3: Using Size Units
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:textSize="30sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView1"></TextView>
<Button android:layout_width="160dp"
android:text="160dp"
android:layout_height="wrap_content"
android:id="@+id/button1"></Button>
<Button android:layout_width="160px"
android:text="160px"
android:layout_height="wrap_content"
android:id="@+id/button2"></Button>
</LinearLayout>
Notice the metric information in the text view in Figures 1-4 and 1-5. To display this information, I use the android.util.DisplayMetrics
class in my app’s main activity:
package com.allmycode.screen;
import android.app.Activity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.widget.TextView;
public class ScreenActivity extends Activity {
TextView textView;
DisplayMetrics metrics;
String densityDpiConstant;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
textView = (TextView) findViewById(R.id.textView1);
metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().
getMetrics(metrics);
switch (metrics.densityDpi) {
case DisplayMetrics.DENSITY_LOW:
densityDpiConstant = "DENSITY_LOW";
break;
case DisplayMetrics.DENSITY_MEDIUM:
densityDpiConstant = "DENSITY_MEDIUM";
break;
case DisplayMetrics.DENSITY_HIGH:
densityDpiConstant = "DENSITY_HIGH";
break;
case DisplayMetrics.DENSITY_XHIGH:
densityDpiConstant = "DENSITY_XHIGH";
break;
case DisplayMetrics.DENSITY_XXHIGH:
densityDpiConstant = "DENSITY_XXHIGH";
break;
default:
densityDpiConstant = "densityDpi is " +
metrics.densityDpi;
break;
}
textView.setText(metrics.toString()
+ ", " + densityDpiConstant);
}
}
Objects on a screen need room to breathe. You can’t butt one text field right up against another. If you do, the screen looks horribly cluttered. So Android has things called padding and margin:
In Figure 1-6, I superimpose labels onto a screen shot from an emulator. The rectangle with eight little squares along its perimeter is the text view’s border. Think of the border as the text view’s clothing. The padding keeps the clothing from being too tight, and the margins determine how much “personal space” the text view wants.
Listing 1-4 contains the code that generates the layout in Figure 1-6.
Listing 1-4: Using Margin and Padding
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<LinearLayout android:id="@+id/LinearLayout1"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:background="@color/opaque_white">
<TextView android:text="Dummies"
android:layout_margin="30dip"
android:padding="30dip"
android:textSize="30sp"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/textView1"
android:textColor="@color/opaque_black">
</TextView>
</LinearLayout>
</LinearLayout>
I once asked a button what it wanted to be when it grows up. The button replied, “I want to be an astronaut.” So I placed the button inside a layout with attribute android:layout_gravity="center"
. A layout with this attribute is like the International Space Station. Things float in the middle of it. (Well, they don’t actually bob to and fro the way things do in the space-station videos, but that’s beside the point.)
Android has two similarly named attributes, and it’s very easy to confuse them with one another. The android:gravity
attribute tells a layout how to position the views within it. The android:layout_gravity
attribute tells a view how to position itself within its layout. Figure 1-7 illustrates the idea.
The screen in Figure 1-7 contains a shortened linear layout and a button. The linear layout is only 220dip tall, and its android:layout_gravity
is center_vertical
. (See Listing 1-5.) So the gray linear layout floats downward to the center of the screen. But the layout’s android:gravity
attribute is center_horizontal
. So the button within the layout shimmies horizontally to the layout’s center. The button hangs along the top edge of the layout because, by default, things rise to the top and hug the left.
Listing 1-5: Using layout_gravity and gravity
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_gravity="center_vertical"
android:gravity="center_horizontal"
android:background="#F999"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="220dip">
<Button android:text="Button"
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</Button>
</LinearLayout>
Figures 1-8 through 1-11 illustrate some other android:gravity
attribute values.
Figure 1-10 shows what you can do by combining gravity values using Java’s bitwise or
operator (|
).
You can apply colors to all kinds of things — things such as text, backgrounds, shadows, links, and other stuff. In Listing 1-5, I use the attribute android:background="#F999"
, making a layout’s background one quiet, dignified shade of gray.
As an Android developer, the most grown-up way to create a color is to declare it in a res/values/colors.xml
file. The file looks something like the stuff in Listing 1-6.
Listing 1-6: A colors.xml File
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android=
"http://schemas.android.com/apk/res/android">
<color name="bright_red">#F00</color>
<color name="bright_red2">#FF00</color>
<color name="bright_red3">#FF0000</color>
<color name="translucent_red">#7F00</color>
<color name="invisible_good_for_nothing_red">
#00FF0000
</color>
<color name="white">#FFF</color>
<color name="black">#000</color>
<color name="puce">#CC8898</color>
</resources>
A color value begins with a pound sign (#
) and then has three, four, six, or eight hexadecimal digits. With three digits, the leftmost digit is an amount of redness, the middle digit is an amount of greenness, and the rightmost digit is an amount of blueness. (The colors always come in that order — red, then green, and then blue. It’s called RGB color.) So, for example, the color value #F92
stands for a decent-looking orange color — 15 units of red, 9 units of green, and 2 units of blue, each out of a possible 16 units.
With only three hexadecimal digits, you can’t express fine color differences. So Android permits you to express a color as a sequence of six hex digits. For example, the value #FEF200
is a good approximation to the yellow on this book’s cover. It’s 254 units of red, 242 units of green, and no blue, each out of a possible 255 units.
With three RGB digits, you can add a fourth alpha digit immediately after the pound sign. And with six RGB digits, you can add two additional alpha digits immediately after the pound sign. The alpha value is the amount of opaqueness, with 15 being fully opaque and 0 being completely transparent. So the value #7F00
in Listing 1-6 is partially transparent red (“translucent red,” if you will). Against a black background, the value #7F00
is a dull, depressing reddishness.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:background="@color/black"
android:id="@+id/linearLayout1"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="vertical">
<Button android:background="@color/translucent_red"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button"
android:id="@+id/button8"></Button>
</LinearLayout>
Against a white background, the value #7F00
looks like elementary-school pink.
An Android view has one of three visibility values — android.view.View.VISIBLE
, android.view.View.INVISIBLE
, or android.view.View.GONE
. The first value — VISIBLE
— is self-explanatory. The difference between INVISIBLE
and GONE
is as follows: An INVISIBLE
view takes up space; a view that’s GONE
takes up no space. For example, in Figure 1-12, an INVISIBLE
view (Button2) separates Button1 from Button3. If I change Button2's visibility to GONE
, Button1 butts up against Button3. (See Figure 1-13.)
In Figure 1-13, Button2 is gone but not forgotten. See this chapter’s “Frame Layout” section for more details.
You may have noticed folders with names like drawable-xhdpi
and layout-sw300dp
. In Android Studio’s Project tool window, they appear as items inside the drawable
and layout
branches. But in your system’s File Explorer or Finder, these are separate folders containing image files, XML files, and other kinds of files. You can also find branches in the Project tool window with names like values-it
(values for the Italian language locale). In your File Explorer or Finder, the corresponding folders contain strings.xml
files, and other locale-specific files.
In a res
subfolder name, characters that start with a dash (characters such as -xhdpi
, -sw300dp
, and -it
) are configuration qualifiers. The general idea is to specify certain folders for particular device configurations. If the device is configured to display text in Italian, Android uses files from the res/values-it
folder instead of from the default res/values
folder. If the device has an extra-high-density screen (no big deal by today’s standards), Android uses files from the res/layout-xhdpi
folder. (That is, Android uses files from the res/layout-xhdpi
folder if the project has such a folder.)
When you run an app, Android checks the device screen’s characteristics. A screen with dpi value 240 is considered to be high density. This can be called hdpi
or DisplayMetrics.DENSITY_HIGH
, depending on where you refer to it in your project. So, if the screen’s dpi value is close to 240 (whatever "close to" means), Android looks for folders named res/layout-hdpi
, res/drawable-hdpi
, and so on. The res/layout-hdpi
folder (if your app has one) contains files named activity_main.xml
and other files describing the layout of your app’s screens. The res/drawable-hdpi
folder (if your app has such a folder) contains images. For a high-density device or emulator, Android uses files that it finds in these -hdpi
folders. If your app doesn’t have such folders, Android uses files in the default folders (the plain old res/layout
and res/drawable
folders).
Table 1-1 contains some of Android’s dpi names and their corresponding dpi values.
Table 1-1 Android Screen Densities
Name |
Acronym |
Approximate* Number of Dots per Inch (dpi) |
Fraction of the Default Density |
|
ldpi |
120 |
¾ |
|
mdpi |
160 |
1 |
|
hdpi |
240 |
1½ |
|
xhdpi |
320 |
2 |
|
xxhdpi |
480 |
3 |
DENSITY_XXXHIGH |
xxxhdpi |
640 |
4 |
* When the screen density of a device doesn’t match a number in Column 3 of Table 1-1 , Android does its best with the existing categories. For example, Android classifies density 265 dpi in the hdpi group.
Fun facts: DENSITY_XHIGH
is used for 1080p high-definition televisions in the United States. A seldom-used Android density, DENSITY_TV
with 213 dpi, represents 720p television. The ultra-large DENSITY_XXXHIGH
might be useful for displaying graphics on the newest 4K television screens.
When I was young, products were available in sizes small, medium, and large. But the U.S. economy was doing well, and people were buying more and more stuff. So companies started selling extra-large items. After a while, people were no longer buying small sizes. Eventually, the three choices in supermarket shelves were large, extra-large, and jumbo. Nothing was available in small or medium sizes.
The same kind of thing has happened with Android screen sizes. People don’t write apps for DENSITY_LOW
screens anymore. And the trend toward higher and higher densities isn’t stopping. By the time Android 3.2 was released, the stewards of Android were tired of making up new names for higher and higher screen densities. So they came up with a more versatile scheme. They called for folders with names such as layout-sw300dp
.
In the Android documentation, such folders are named sw<
N
>dp
folders. The letters sw stand for smallest width (the width of your activity when the activity has its narrowest width — typically the activity’s width in portrait mode). If the screen width available to your activity is ever smaller than whatever number you substitute in place of N, Android doesn’t look inside this sw<
N
>dp
folder. For example, if the screen width available to your activity is ever smaller than 600dp, Android doesn’t look inside the layout-sw600dp
folder, the drawable-sw600dp
, or any other -sw600dp
folders.
Here’s an experiment for you to try:
res
folder. Name the new folder layout-sw300dp
(for example).
Still using your system’s File Explorer or Finder, copy your app’s activity_main.xml
file from the res/layout
folder to the new res/layout-sw300dp
folder.
Now your main activity has two layout files — one for emulators and devices that have 300dp or more for an activity and another for all other screens.
Modify one of the activity_main.xml
files so that you can determine which is being used during a run of the app.
For me, the easiest thing to do is to take the text view that comes with every new app. In that text view, change the characters in one of the activity_main.xml
files.
Run your app on two emulators — one relatively new and another that’s fairly old.
I ran this app on a 768 × 1280 Nexus 4, and a 480 × 800 Nexus One. The Nexus 4 displayed the layout from the res/layout-sw300dp
folder. But the Nexus One defaulted to the layout from the res/layout
folder. That’s how these sw<
N
>dp
folders work.
In the previous section, you name different layout files for different screen sizes. The screen sizes depend on the device’s dpi (dots per inch) value. This is very useful, but when you’re trying to make an app look nice, dots per inch don’t tell the whole story.
Suppose you want different layouts for the device in portrait and landscape modes. You can make that happen by creating a res/layout-land
folder. Here are the details:
res
folder. Name the new folder layout-land
.
Still using your system’s File Explorer or Finder, copy your app’s activity_main.xml
file from the res/layout
folder to the new res/layout-land
folder.
Now your main activity has two layout files — one for landscape mode on your emulator or device (res/layout-land/activity_main.xml
) and another for portrait mode (res/layout /activity_main.xml
).
Modify one of the activity_main.xml
files so that you can determine which is being used during a run of the app.
For me, the easiest thing to do is to take the text view that comes with every new app. In that text view, change the characters in one of the activity_main.xml
files.
Change the emulator or device back and forth between portrait and landscape mode.
Notice how the layout changes automatically from one XML file to the other.
To switch an emulator between portrait and landscape modes, press Ctrl+F11.
This chapter emphasizes the dpi
, sw<
N
>dp
, and land
configuration qualifiers. But Android has many other configuration qualifiers. Table 1-2 contains a brief list.
Table 1-2 Configuration Qualifiers
Name |
Examples |
Meaning |
Language and region |
|
In what language (and for what country or region) is this device configured? |
Layout direction |
|
In which direction does writing go on this device? Right-to-left (as in Hebrew or Arabic) or left-to-right (as in many other languages)? |
Smallest width |
|
No matter how the device is oriented, what’s the smallest width (in density-independent pixels) that’s available to your activity? |
Available width |
|
For the device’s current orientation, what’s the width (in density-independent pixels) that’s available to your activity? |
Available height |
|
For the device’s current orientation, what’s the height (in density-independent pixels) that’s available to your activity? |
Pixel density |
|
How many density-independent pixels does the device’s screen have? |
Orientation |
|
How is the device currently oriented? Portrait or landscape? |
UI mode |
|
What kind of a device is running your app? |
Night mode |
|
Are you (or aren’t you) currently in the Earth’s shadow? |
Touchscreen type |
|
Does the screen respond to the user’s touch? |
API level |
|
Which API level does this device support? |
When you create a new Android project, Android Studio fills your main activity’s layout with a relative layout. A relative layout describes the placement of each view compared with other views. For example, in a relative layout, you might place Button2 beneath Button1 and place Button3 to the right of Button1. Listing 1-7 has some code, and Figure 1-14 shows the resulting screen.
Listing 1-7: Using a Relative Layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent">
<Button android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:text="Button1" android:id="@+id/button1"
android:layout_height="wrap_content"
android:layout_width="wrap_content"></Button>
<Button android:layout_alignParentLeft="true"
android:layout_below="@+id/button1"
android:text="Button2" android:id="@+id/button2"
android:layout_height="wrap_content"
android:layout_width="wrap_content"></Button>
<Button android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/button1"
android:text="Button3" android:id="@+id/button3"
android:layout_height="wrap_content"
android:layout_width="wrap_content"></Button>
<Button android:layout_below="@+id/button2"
android:layout_alignLeft="@+id/button3"
android:text="Button4" android:id="@+id/button4"
android:layout_height="wrap_content"
android:layout_width="wrap_content"></Button>
</RelativeLayout>
Coding Android’s relative layouts can be complicated. I have trouble remembering which android:id
goes with which view’s android:layout_ toRightOf
. I can easily goof by creating a circular reference. But don’t give up on relative layouts! To avoid relative layouts, people try creating vast nests of linear layouts within other linear layouts. Things go well until someone runs the code. Excessive nesting of linear layouts slows down a processor.
Android has tools to help wean you away from nested linear layouts. One is the hierarchy viewer. The hierarchy viewer’s tree displays the nesting of your layout’s objects. To use the hierarchy viewer, do the following:
Figure 1-15 contains a revealing (okay, okay, a personally embarrassing) hierarchy viewer analysis of one of my recent projects. Look at the Tree Overview in the upper-right panel. Each rounded rectangle represents a view (a layout, for example). The length of the tree (from left to right) shows how deep the nesting goes for this particular project. Some of the tree’s branches are nine levels deep, and the processor can’t draw a view without first calculating the views to its left along the tree’s branches. So the processor chugs slowly as it tries to render the whole scene.
A run of the hierarchy viewer tells you how deeply nested your layouts are. With the viewer’s tree in mind, you can look for ways to eliminate some of the nesting.
A table layout has rows, and each row contains some views. If you do nothing to override the defaults, the views line up to form columns.
For example, the table layout in Figure 1-16 has three table rows. Each table row contains buttons.
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TableRow>
<Button android:text="Button"/>
<Button android:text="Button"/>
<Button android:text="Button"/>
</TableRow>
<TableRow>
<Button android:text="Button"/>
<Button android:text="Wide button"/>
</TableRow>
<TableRow>
<Button android:text="Button"/>
<Button android:text="Btn."/>
<Button android:text="Button"/>
</TableRow>
</TableLayout>
In Figure 1-16, notice how each column widens to accommodate the largest item in the column. Notice also how each item expands to fill its entire column.
What? You say you want the leftmost column to be wider? Just add the android:stretchColumns
attribute to your <TableLayout>
start tag:
<TableLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:stretchColumns="0">
The leftmost column has number 0, so when you write stretchColumns="0"
, you get a layout like the one in Figure 1-17.
Do you want more than one column to be stretched? Then name several column numbers in your stretchColumns
attribute:
<TableLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:stretchColumns="0,1,2">
When you do, you get the layout shown in Figure 1-18.
You can move the Wide button back in Figure 1-16 to the rightmost column:
<TableRow>
<Button android:text="Button"/>
<Button android:text="Wide button"
android:layout_column="2"/>
</TableRow>
The resulting layout is shown in Figure 1-19.
You can make a cell in the table span several columns:
<TableRow>
<Button android:text="Button"/>
<Button android:text="Wide button"
android:layout_span="2"/>
</TableRow>
When you do, you get the layout shown in Figure 1-20.
Like a table layout, a grid layout arranges things in rows and columns. But some features make grid layouts different from table layouts. For one thing, you don’t use TableRow
elements (or anything like TableRow
elements) in a grid layout. Instead, you specify the number of rows and columns in the GridLayout
start tag:
<GridLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:columnCount="3"
android:rowCount="3">
<Button android:text="Button"/>
<Button android:text="Button"/>
<Button android:text="Button"/>
<Button android:text="Button"/>
<Button android:text="Wide button"/>
<Button android:text="Button"
android:layout_row="2"
android:layout_column="0"/>
<Button android:text="Btn."/>
<Button android:text="Button"/>
</GridLayout>
This code gives you the layout shown in Figure 1-21. Notice how the layout_row
and layout_column
attributes force all the buttons after the Wide button into the bottommost row.
Notice also how the buttons in Figure 1-21 don’t expand to fill their respective columns. Android’s layout_width
attribute doesn’t play nicely with the grid layout. So, if you want an item in a grid layout to expand to fill its column, use the android:layout_gravity
attribute:
<Button android:text="Button"
android:layout_row="2"
android:layout_column="0"/>
<Button android:layout_gravity="fill_horizontal"
android:text="Btn."/>
<Button android:text="Button"/>
The resulting layout is shown in Figure 1-22.
A frame layout displays one view. What good is that?
Well, to be more precise (and less sensational), a frame layout displays views one in front of another. (Put on your 3D glasses and think of a frame layout as an outward-pointing linear layout.) Because views tend to cover the stuff behind them, a frame layout normally displays only one view — namely, whatever’s in front.
Frame layouts usually serve one of two purposes:
This section’s example illustrates both ideas. You start with a word superimposed on an image, which is, in turn, superimposed on top of another image. (See Figure 1-23.)
When the user touches the screen, two of the three items disappear. (See Figure 1-24.) The screen cycles through the three images, changing the image whenever the user touches the screen. (See Figures 1-25 and 1-26.)
(I know what you’re thinking: “The author looks for excuses to show pictures of his cats.” That’s only partly true. I include lots of illustrations to help you visualize the code’s behavior. Anyway, pictures of cats make perfect clip art. Cats don’t complain when you use their least favorite profiles. Pictures of cats are better than pictures of your family members because readers seldom stalk cats. Best of all, no one’s figured out how to patent the domestic cat. I can’t be sued for putting cat pictures in my book. Not yet, anyway.)
Listing 1-8 shows you the XML code for the app in Figures 1-23 to 1-26.
Listing 1-8: Creating a Frame Layout
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:id="@+id/mainlayout"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:orientation="vertical"
android:onClick="rotate">
<ImageView android:src="@drawable/calico"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:padding="5px"
android:layout_gravity="center"
android:id="@+id/imageViewCalico"/>
<ImageView android:src="@drawable/burmese"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:padding="5px"
android:layout_gravity="center"
android:id="@+id/imageViewBurmese"/>
<TextView android:text="@string/meow"
android:textColor="#FFF"
android:textSize="15sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:id="@+id/textView"/>
</FrameLayout>
The only important business in Listing 1-8 is the order in which I declare the views. The largest image (the Calico) is the FrameLayout
element’s first child. So, in Figure 1-23, the Calico appears behind the other images. If the Burmese’s image was as large as the Calico’s image, you wouldn’t see the edges of the Calico’s image in Figure 1-23. The last element in Listing 1-8 is the text view, so in Figure 1-23, the text is superimposed on top of the other elements.
Listing 1-9 has the code that rotates from image to image.
Listing 1-9: Coding Java with a Frame Layout
package com.allmycode.layouts;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
public class LayoutTesterActivity extends Activity {
ImageView imageCalico, imageBurmese;
TextView textView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.frame);
imageCalico =
(ImageView) findViewById(R.id.imageViewCalico);
imageBurmese =
(ImageView) findViewById(R.id.imageViewBurmese);
textView = (TextView) findViewById(R.id.textView);
}
int count = 0;
public void rotate(View view) {
switch (count++ % 3) {
case 0:
textView.setVisibility(View.VISIBLE);
imageCalico.setVisibility(View.INVISIBLE);
imageBurmese.setVisibility(View.INVISIBLE);
break;
case 1:
textView.setVisibility(View.INVISIBLE);
imageCalico.setVisibility(View.VISIBLE);
imageBurmese.setVisibility(View.INVISIBLE);
break;
case 2:
textView.setVisibility(View.INVISIBLE);
imageCalico.setVisibility(View.INVISIBLE);
imageBurmese.setVisibility(View.VISIBLE);
break;
}
}
}
When the app starts running, all three views (the two images and the text view) are visible. But then Listing 1-9 cycles from one view to another. Each call to the rotate
method makes one view visible and makes the other two views invisible.
Sometimes, an activity’s screen takes up more space than is available on a mobile phone. If that’s likely to happen, you can enclose the activity’s screen in a ScrollView
. As its name suggests, a ScrollView
lets the user slide things onto the screen as other things slide off. Listing 1-10 shows you some code, and Figure 1-27 shows you the results.
Listing 1-10: How to Scroll
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="0
1
2
3
4
5
6
7
8
9"
android:textSize="40sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="10
11
12
13
14"
android:textSize="40sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="15
16
17
18
19"
android:textSize="40sp"/>
</LinearLayout>
</ScrollView>
In truth, Figure 1-27 doesn’t show you the whole story. If the user slides a finger upward along the screen, the numbers in Figure 1-27 move upward. The smaller numbers scroll off the top of the screen, and larger numbers scroll up onto the bottom of the screen.
The big restriction on a ScrollView
is that a ScrollView
may contain only one direct child. So, in Listing 1-10, you can’t put three TextView
elements directly inside a ScrollView
element. Instead, you put the three TextView
elements inside some other element (in Listing 1-10, a LinearLayout
element) and then put that other element inside the ScrollView
.
XML files are good for describing layouts because XML is declarative. In an XML file, you declare, once and for all, a layout’s characteristics. You declare the presence of a text field and a button. You don’t think of these widgets as actions (“Put a text field here and, sometime later, put a button there”), so you don’t say “Do this, and later, do that” the way you say things in a Java program.
But sometimes, a layout’s look has to change. Imagine an activity in which you have one row consisting of two items — a text field and a button. (See Figure 1-28.)
When you click the button, you want an additional row to appear. (See Figure 1-29.)
To make this happen, you start with a regular, old XML layout. (See Listing 1-11.)
Listing 1-11: The Starting Layout
<LinearLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:id="@+id/linear_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/type_text_here"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_a_row"/>
</LinearLayout>
</LinearLayout>
The layout in Listing 1-11 describes the screen shown earlier in Figure 1-28.
In your main activity, you respond to button clicks. The code is in Listing 1-12.
Listing 1-12: Creating a New Row of Widgets.
package com.allyourcode.dynamiclayout;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
public class MainActivity extends Activity
implements View.OnClickListener {
LinearLayout rootlayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rootlayout =
(LinearLayout) findViewById(R.id.linear_layout);
Button firstButton = (Button) findViewById(R.id.button);
firstButton.setOnClickListener(this);
}
@Override
public void onClick(View view) {
LayoutParams editTextLayoutParams =
new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
LayoutParams buttonLayoutParams =
new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
LayoutParams rowLayoutParams =
new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT);
EditText editText = new EditText(this);
editText.setLayoutParams(editTextLayoutParams);
editText.setHint(R.string.type_text_here);
Button button = new Button(this);
button.setLayoutParams(buttonLayoutParams);
button.setText(R.string.add_a_row);
button.setOnClickListener(this);
LinearLayout newRow = new LinearLayout(this);
newRow.setLayoutParams(rowLayoutParams);
newRow.setOrientation(LinearLayout.HORIZONTAL);
newRow.addView(editText);
newRow.addView(button);
LinearLayout currentRow =
((LinearLayout) view.getParent());
int currentIndex =
((LinearLayout) currentRow.getParent())
.indexOfChild(currentRow);
rootlayout.addView(newRow, currentIndex + 1);
}
}
In Listing 1-12, in the onCreate
method, you identify the outmost linear layout and the button that’s currently displayed in that layout. You assign these items to the variables rootLayout
and firstButton
. You make this very activity be the listener for button clicks. Accordingly, this activity implements the OnClickListener
interface and has an onClick
method. The onClick
method responds when the user clicks the button.
In the body of the onClick
method, you do five things:
You create instances of the LayoutParams
class.
Each instance sets the width and height for one of the items that you’ll be adding to the user’s screen.
You create a new EditText
and a new Button
.
You set the properties of these two new items. In particular, you make this activity listen for clicks of your new button.
You create a new row.
The row has a horizontal linear layout.
EditText
and the Button
to the new row.
Finally, you add the new row to the rootLayout
.
If you call
rootlayout.addView(newRow);
then the new row gets added after all other rows in the rootLayout
. In some situations, this is okay. But presumably, in this example, a new row goes immediately below whichever button the user pressed.
Accordingly, before adding a new row, you want to find out which row contains the button that the user pressed. That’s what this bit of Listing 1-12 code does:
LinearLayout currentRow =
((LinearLayout) view.getParent());
int currentIndex =
((LinearLayout) currentRow.getParent())
.indexOfChild(currentRow);
With currentIndex
equal to the position number of the row containing the pressed button, you call
rootlayout.addView(newRow, currentIndex + 1);
You add the newRow
one position after the current row.
At the end of Chapter 6 in Book I, I present an activity that has a few check boxes and a button. When the user clicks the button, a web page takes over the entire screen. This behavior is surprising because you call
webView.loadUrl("http://blah-blah-blah);
and the webView
in question is only one element in the activity’s layout. Why don’t the other elements stay on the screen?
In early versions of Android, the web page didn’t consume the entire screen, and it would be nice if those good old days weren’t gone forever. To keep the web page from consuming all of the screen’s real estate, declare the webView
's position in the activity’s relative layout.
<!-- Disclaimer: This code conveys the general idea
but this code isn’t real XML. (Sorry about that!) -->
<RelativeLayout …>
<include
android:id="@+id/checkboxes_and_button"
… />
<WebView
android:layout_below="@id/checkboxes_and_button"
… />
</RelativeLayout …>
In addition, you have to sneak back to the main activity’s Java file and add code of the following kind:
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading
(WebView webView, String url) {
webView.loadUrl(url);
return true;
}
});
This extra Java code overrides the default behavior by letting your activity control the loading of the new URL. The webView
in your activity’s layout displays the web page, and the good old days are here again.
35.171.45.182