Android Programming for Beginners
上QQ阅读APP看书,第一时间看更新

Structure of a UI design

When we create a new project, Android Studio creates a new layout for us. As we have seen, this autogenerated layout is very simple and contains a single TextView widget. The TextView widget is unsurprisingly the standard way of displaying text. On the palette it is labeled as Plain TextView but in XML code it just says TextView. I will refer to it in the way that seems most appropriate for the current context.

Tip

It will become apparent as we progress, but it is worth making clear at this point that all our layouts will be designed in XML, not Java. In later chapters, you will learn more Java and then we will see how we write Java code to manipulate these layouts.

What we haven't looked at quite as closely is that the generated activity_main.xml file also contains a layout. Layouts come in a few different types and the one that is provided with our auto-generated layout is called RelativeLayout. Here is the XML that makes this layout:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
  android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
  android:paddingRight="@dimen/activity_horizontal_margin"
  android:paddingTop="@dimen/activity_vertical_margin"
  android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".LayoutExperiments">
</RelativeLayout>

Anything in between the closing > that defines the properties of the layout itself and the </RelativeLayout> is a child (like our solitary TextView) of the layout. That child will be influenced by the properties of its parent, and also must use properties appropriate to its parent. For example, when we placed a button as a child of the RelativeLayout in Chapter 2, Java – First Contact, it used the following syntax to position itself immediately below the TextView that has an ID of textView:

android:layout_below="@+id/textView"

Depending upon the type of layout a child is contained within, it will need to use appropriate syntax to influence its appearance/position. The only way to learn all the different intricacies of the different layout types, and how they affect their child widgets, is to start using them.

Tip

As we add, edit, and delete widgets and views in this section, it is not important to make your project the same as mine. The purpose of this section is not to achieve a meaningful end result, only to explore as many of the widgets, layouts, and their properties as we can in as few pages as possible. Do try everything. Don't worry about making it the same as the pictures or following the instructions to the letter. If I add a small margin or other property to a widget, feel free to add an enormous one if you want to give it a go. If you want to see exactly what I created then the project files are in the Chapter 4 folder in the download bundle.

First we will explore the straightforward widgets and their properties, then the layouts, and finally we will use them meaningfully together in a series of mini layout projects.

Configuring and using widgets

Widgets are all the UI elements on the palette under the heading Widgets. First let's have a look at some of the properties of a widget. Note that some widgets have properties unique to themselves, but there are a lot of properties that all the widgets share, and they are useful to take a look at. Let's learn about some of the ways we can configure and use widgets before we use them for real.

Widget properties

As we have already seen, widgets have properties that we can either set in XML or through the Properties window.

Setting the size

A widget's size can depend on a number of properties and the context in which they are used. Probably the most straightforward is by using actual units of size. We briefly saw this in the last chapter but we didn't look into it in any depth.

Sizing using dp

As we know, there are thousands of different Android devices. In order to try and have a system of measurement that works across different devices, Android uses density independent pixels or dp as a unit of measurement. The way this works is by first calculating the density of the pixels on the device an app is running on.

Tip

We can calculate density by dividing the horizontal resolution by the horizontal size, in inches, of the screen. This is all done on-the-fly, on the device on which our app is running.

All we have to do is use dp in conjunction with a number when setting the size of the various properties of our widgets. Using density independent measurements we can design layouts that scale to create a uniform appearance on as many different screens as possible.

So, problem solved then? We just use dp everywhere and our layouts will work everywhere? Unfortunately, density independence is only part of the solution. We will see more of how we can make our apps look great on a range of different screens in this chapter and throughout the rest of the book.

As an example we can affect the height and width of a widget directly, by adding the following code to its properties:

...
android:height="50dp"
android:width="150dp"
...

Alternatively we can use the properties window and add them through the comfort of the appropriate edit boxes as shown next. Which option you use will depend on your personal preference but sometimes one way will feel more appropriate than another in a given situation. Either way is correct and as we go through the book making mini-apps, I will usually point out if one way is better than another.

Or we can use the same dp units to set other properties such as margin and padding. We will look at margin and padding in a minute.

Sizing fonts using sp

Another device dependent unit of measurement, used for sizing Android fonts is scalable pixels or sp. The sp unit of measurement is used for fonts and is pixel density dependent in the exact same way that dp is. The extra calculation that an Android device will take into account when deciding how big your font will be, based on the value of sp you use, is the user's own font size settings. So, if you test your app on devices and emulators with normal size fonts, then a user who has a sight impairment (or just likes big fonts) and has the font setting on large, will see something different to what you saw during testing.

If you want to try playing with your Android device's font size settings, you can do so by selecting Settings | Display | Font size, as shown:

As we can see in the previous image there are quite a number of settings and if you try it on Huge the difference is, well, huge!

We can set the size of fonts using sp in any widget that contains text. This includes Button, TextView, and all the UI elements under the Text Fields category in the palette, as well as some others. We do so by setting the textSize property like so:

android:textSize="50sp"

As usual we can also use the properties window to achieve the same thing.

Determining size with wrap or match

We can also determine how the size of widgets and many other UI elements behave in relation to the containing/parent element. We can do so by setting the layoutWidth and layoutHeight properties to either wrap_content or match_parent.

For example, if we set the properties of a lone button on a layout to the following:

...
android:layout_width="match_parent"
android:layout_height="match_parent"
....

Then the button will expand in both height and width to match the parent. We can see that the button in the next screenshot fills the entire screen:

More common for a button is wrap_content, as shown next:

....
android:layout_width="wrap_content"
android:layout_height="wrap_content"
....

This causes the button to be as big as it needs to be, to wrap its content (width and height in dp and text in sp).

Using padding and margin

If you have ever done any web design, then you will be very familiar with the next two properties. Padding is the space from the edge of the widget to the start of the content in the widget. Margin is the space outside of the widget that is left between other widgets—including the margin of other widgets, should they have any. Here is a visual representation:

We can set padding and margin in a straightforward way, equally for all sides, like this:

...
android:layout_margin="43dp"
android:padding="10dp"
...

Look at the slight difference in the naming convention for the margin and the padding. The padding is just called padding but the margin is referred to as layout_margin. This reflects the fact that padding only affects the widget itself but margin can affect other widgets in the layout.

Or we can specify different top, bottom, left, and right margins and padding, like this:

android:layout_marginTop="43dp"
android:layout_marginBottom="43dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"

Specifying margin and padding values for a widget is optional and a value of zero will be assumed if nothing is specified. We can also choose to specify some of the different side's margin and padding but not others, as in the previous example.

It is probably becoming obvious that the way we design our layouts is extremely flexible, but also that it is going to take some practice to achieve precise results with this many options. We can even specify negative margin values to create overlapping widgets.

Let's look at a few more properties and then we will go ahead and play around with a few widgets for real.

Using the layout_weight property

Weight refers to the relative amount compared to other UI elements. So, for layout_weight to be useful, we need to assign a value to the layout_weight property on two or more elements. We can then assign portions that add up to 100% in total. This is especially useful for dividing up screen space between parts of the UI where we want the relative space they occupy to remain the same regardless of screen size. Using layout_weight in conjunction with sp and dp units can make for a really simple and flexible layout. For example, take a look at this code:

<Button
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight=".1"
    android:text="one tenth" />

<Button
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight=".2"
    android:text="two tenths" />

<Button
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight=".3"
    android:text="three tenths" />

<Button
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight=".4"
    android:text="four tenths" />

Here is what this code will do:

Notice that all the layout_height properties are set to 0dp. Effectively the layout_weight is replacing the layout_height property. The context in which we use layout_weight is important (or it won't work) and we will see this in a real project soon. Also note that we don't have to use fractions of 1, we can use whole numbers, percentages, and any other number, and as long as they are relative to each other they will probably achieve the effect you are after. Note that layout_weight only works in certain contexts and we will get to see where when we build some layouts.

Using gravity

Gravity can be our friend and can be used in so many ways in our layouts. It helps us affect the position of a widget by moving it in a given direction; like it was being acted upon by gravity. The best way to see what gravity can do is to take a look at some example code and pictures.

Setting the gravity property on a button (or other widget) to left|center_vertical like this:

android:gravity="left|center_vertical"

Will have an effect that looks like this:

Notice that the contents of the widget (in this case the button's text) are indeed aligned left and centrally vertical.

In addition, a widget can influence its own position within a layout element with the layout_gravity element, like this:

android:layout_gravity="left"

This would set the widget within its layout, probably as expected, like this:

The previous code allows different widgets within the same layout to be effected as if the layout has multiple, different gravities.

The contents of all the widgets in a layout can be affected by the gravity property of their parent layout by using the same code as a widget:

android:gravity="left"

Let's mention a few more properties and then we can go about using some of them.

More properties

There are in fact many more properties than those we have discussed. Some we won't need in this book and some are quite obscure and you might never need them in your entire Android career. But others are quite commonly used such as: background, textColor, alignment, typeface, visibility, and shadowColor; but these are best explored with a practical experiment. So let's do that now.

Experimenting with widgets

If you are unsure on any of the following steps then refer back to Chapter 1, The First App for further discussion and images for most of the steps. You can find the completed code files for this tutorial in Chapter 4/Widget Experiments:

  1. Start the new project wizard by left-clicking on File | New Project. If you are on the Welcome to Android Studio start menu then left-click Start a new Android Studio project.
  2. Name the application Widget Experiments, enter your preferred domain name and project location, then click on Next.
  3. On the Target Android Devices window accept the default settings by left-clicking on Next.
  4. On the Add an Activity to Mobile window left-click Blank Activity then left-click on Next to proceed.
  5. On the Customize the Activity window, name the activity WidgetExperimentsActivity, make the title Widget Experiments, and leave everything else at default. Now press Finish.
  6. Wait for Android Studio to create the new project.
  7. If you already had a project open at the start of this tutorial you will now have two completely separate instances of Android Studio running. You can close the previous one.
  8. Arrange your design view as we did earlier to give extra space to the Palette, Component Tree, and Properties windows.
  9. Now we have a new project we will talk about and play with some widgets. Left-click and drag a Button from the Widgets category of the palette onto the top-left corner of the design preview.
  10. In the Properties window scroll to find the text property and change it to Left Button.
  11. In the properties window scroll to find the width property and change it to 150dp. Note there is no space between 150 and dp.
  12. Scroll to the layout:margin property. Note there is a little grey triangle to the left that when clicked will reveal more options to this property. Set the left property to 10dp, the top property to 100dp, the right property to 50dp, and the bottom property to 50dp. The next screenshot should help with this step:
  13. Of course if we wanted a consistent margin on all sides of our button we could have entered a value in the all property. Observe the design preview after these steps. We can see the effect of the larger 100dp top margin and the smaller 10dp left margin. We can also observe the button text is just as we set it and the button is more elongated because we set the width property to 150dp. The right and bottom margins are not visually apparent at the moment.
  14. Now, drag another Button onto the layout and place it vertically in line with the previous button and to the right.
  15. Set the text property to Right and the width property to 80dp. Notice that the button does indeed shrink a little. Let's experiment some more.
  16. Drag Plain TextView onto the layout and center it horizontally and below the two buttons.
  17. Change the textSize property to 100sp. We can see in the next screenshot that the text is too wide to fit onto one line and it wraps onto two. We can also see by the blue rectangle surrounding TextView that it is even overlapping the two buttons above:
  18. Now, change both the width and height properties to 150dp. We have now constrained TextView, however the textSize property we set remains, causing the text to be partially obscured.
  19. Let's fix this and add a few enhancements through a few more properties. Change the textSize property to 65sp. Change the layoutWidth property to match_parent. This will make it as wide as its parent (RelativeLayout). Change the gravity property to center. Set the alpha property, which changes the transparency, to .5. Notice that the text now fits quite nicely as it is smaller and has the entire width of the device, and is also centered.
  20. Now find and click on the background property, left-click on the three periods ..., and now left-click on the Color tab. You can now click the color chooser on any color you like. Now our TextView has a background color.
  21. Find and left-click on the textColor property. Left-click the Color tab and choose a color for your text that compliments the background color you chose in the previous step.
  22. Now change the typeface property to serif and notice that the font has changed.
  23. Add ImageView from the palette, below TextView. Notice that it is almost unnoticeable. This is because it needs an image to display. Scroll to the src property, left-click the three periods ..., and scroll down the list of possible sources for our image. Right near the end under the Mip Map heading, double left-click on ic_launcher. We now have the cute Android logo embedded in ImageView. The next screenshot shows how my experiment ended. Obviously if you are reading this in print you will be unable to see the precise colors of the text and the background:
  24. Finally, left-click on TextView to select it. Find the visibility property. By default this is set to visible. Try changing it to invisible. It disappears. This is probably what you expected and not the highlight of the chapter. But now try changing the visibility property to gone. Note how ImageView jumps up the layout to below the buttons. It does indeed behave as if the text is "gone". You probably remember that we said we can change properties on-the-fly while our app is running, using our Java code. Switching between visible, invisible, and gone can be really useful.
  25. You can run this app on an emulator or a real device.

In this widget experiment we could see how many of the properties we have discussed interact with each other. We also saw a few new properties. You probably also noticed that a few of the properties we mentioned in some detail before the experiment haven't been demonstrated yet. Most notably padding. These are best showcased using Layouts from the palette and will be seen soon.

You only need to glance at the Palette window and the Properties window to realize we have only scratched the surface of the layout options in Android. But what we have learned will actually allow us to design a surprisingly large variety of layouts.

Let's look at some layouts.

Containing widgets in layouts

We know that layouts are one of the main building blocks of our UI. And in Android we have several types of layout that we can choose from to suit our specific design goals.

We will now do some experimentation with some of the main layout types.

RelativeLayout

This is the type of layout that was automatically included within our Hello Android project for us. Let's just play around with it some more.

RelativeLayout in action

If you are unsure on any of the following steps then refer back to this mini app can be found in the Chapter 4/Layout Experiments folder:

  1. Start the new project wizard by left-clicking on File | New Project if you already have an existing project open or, if you are on the Welcome to Android Studio start menu, then left-click on Start a new Android Studio project.
  2. Name the application Layout Experiments, enter your preferred domain name and project location, then click on Next.
  3. On the Target Android Devices window accept the default settings by left-clicking on Next.
  4. On the Add an Activity to Mobile window left-click Blank Activity then left-click Next to proceed.
  5. On the Customize the Activity window name the activity LayoutExperimentsActivity, make the app title Layout Experiments, and leave everything else at the default. You don't need to change the Activity name for this to work, it just makes this project more distinct from the others we will build. Now press Finish.
  6. Wait for Android Studio to create the new project.
  7. Arrange your design view as we did earlier to give extra space to the Palette, Component tree, and Properties windows.

Now we have a new project we will talk about and play with some layouts.

Notice the top element of the Component tree window, after the Device Screen element. We already have RelativeLayout by default. This type of layout allows its children to use descriptions of positions relative to itself and its other children. Let's explore this:

  1. Left-click and drag a Plain TextView widget from the palette and drop it just underneath the existing Plain TextView.
  2. Now do the same with Button and drop it below the TextView we added in the previous step.
  3. Next place a Switch widget below the Button widget from the last step.

Your layout will probably look something like this next screenshot, which is a close-up view of the top left of the UI designer:

What is more interesting than the appearance, however, is the XML code that has been generated. Let's explore it now. Left-click on the Text tab below the editor window to reveal the generated XML.

Here are the entire contents of activity_layout_experiments.xml with the code for the parent RelativeLayout removed. Take a look at the code and take a close look at the three highlighted lines:

<TextView 
  android:text="@string/hello_world" 
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:id="@+id/textView" />

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="New Text"
  android:id="@+id/textView2"
  android:layout_below="@+id/textView"
  android:layout_alignParentLeft="true"
  android:layout_alignParentStart="true" />

<Button
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="New Button"
  android:id="@+id/button"
  android:layout_below="@+id/textView2"
  android:layout_alignParentLeft="true"
  android:layout_alignParentStart="true" />

<Switch
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="New Switch"
  android:id="@+id/switch1"
  android:layout_below="@+id/button"
  android:layout_alignParentLeft="true"
  android:layout_alignParentStart="true" />

In the code we can identify each of the widgets we added to our layout by the opening word of each code block. So the first <TextView block is the start of the text that reads Hello world! in the previous screenshot. The second block that begins with <TextView is therefore our very own TextView that we dragged onto the UI design ourselves. This TextView is the one with the text New Text in the previous screenshot.

Furthermore, the block of code that starts with <Button is of course our button labeled NEW BUTTON in the previous screenshot. And at this point you can probably guess that the code block that begins with <Switch is the Switch widget.

Also note that the last two lines of code for each widget are the same. Here they are again:

android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />

What this is effectively saying is to place this widget on the top left of its parent. So you might expect the widgets to be on top of one another?

Previously I suggested taking a close look at the three highlighted lines in the code. Let's examine the first highlighted line, which is from the first TextView that we added below TextView that was already there (the one with the text Hello world!). Here is the highlighted line of code again:

android:layout_below="@+id/textView"

What this is saying is place me below the widget with the id of textView. If you look at the main code listing again you will indeed see that the id property of TextView containing the text Hello world! is set to textView with the following line of XML:

android:id="@+id/textView" />

This method of describing where the contents of a layout go is for RelativeLayout only. It is perfect for some types of practical app designs (perhaps forms) and extremely awkward for others.

Let's explore some other layout types and then we will build some layouts that take advantage of each layout type we have experimented with.

Using LinearLayout

With this layout the clue is in the name. All the widgets contained in LinearLayout will be displayed in the order in which they are added. We can certainly still add margins, padding, and so on, but the order is fixed. It is linear (or sequential). LinearLayouts applies either vertical or horizontal ordering. Let's quickly make a project and combine a few LinearLayouts with some widgets.

The code for this mini app can be found in the Chapter 4/Linear Layout Experiment folder:

  1. Create a new project in Android Studio. Call it Linear Layout Experiment, choose a Blank Activity, and leave all the other settings at their defaults.
  2. Let's start with a completely clean sheet. Right-click on the layout folder in the project explorer. From the menu, choose New | Layout resource file as shown in the next screenshot:
  3. Then in the File name field enter linear_experiment and then left-click on OK.
  4. The new file is created and opened in the editor. If the file is not automatically opened in design view then left-click on the Design tab at the bottom left of the editor window.
  5. Look in the Component Tree window and you will see that we have been provided, by default, with LinearLayout as the root of our design. Also notice the word vertical in brackets, which indicates this is a vertical LinearLayout.
  6. From the Layouts category of the palette drag a LinearLayout (Horizontal) onto the design. Drop it at the top of the layout.
  7. Now drag a LinearLayout (Vertical) onto the design and confirm in Component Tree that it has indeed been added after the previous LinearLayout, as shown next:
  8. Now find the layout:weight property in the Properties window of the currently selected LinearLayout and set it to .5. Do this for the other LinearLayout that we added by left-clicking it in the Component Tree, finding the layout:weight property, and setting it to .5. We can see that the Component Tree is especially useful when the design itself is not clear about where exactly the elements that make our layout are situated. We now have two vertically nested LinearLayouts, the top one is a horizontal LinearLayout and the bottom one is a vertical LinearLayout. And they both take up exactly .5 (half) of the screen.
  9. Add two Buttons from the Widgets category of the palette to the top (horizontal) LinearLayout. Notice how they arrange themselves horizontally. But they are rather untidily stuck to the left-hand side of the layout.
  10. Select the horizontal (top) LinearLayout by left-clicking it in either the design or Component Tree. Find the gravity property and left-click on the small triangle to the left of the word gravity to reveal the options for this property. Now left-click on the center_horizontal check box. The next image should make this step clear:
  11. Now drag three Plain TextView widgets from the palette to the vertical (bottom) LinearLayout. Notice how they are nicely ordered from top to bottom but squashed to the top.
  12. Select the vertical (bottom) LinearLayout either by clicking on it in the layout or the component tree. Find the gravity property and click the triangle to reveal the options, just as we did for the other LinearLayout in step 8. Left-click the check box for center_vertical. Now the TextViews are neatly centered but are squashed together.
  13. Let's add a margin to each of the TextView widgets to solve this problem. Left-click to select the topmost TextView. Scroll to find the layout_margin property. Left-click the layout_margin property then add the value 20dp next to the top option.
  14. Repeat the previous step for each TextView.

Now we have a nice neat layout of buttons and text, as shown in the following screenshot:

This works because the first vertical LinearLayout wraps the other two on top of each other. We use layout_weight in the wrapped layouts to make them take up half the screen space each. The first wrapped layout lays its buttons out from left to right because it is horizontal, and the second wrapped layout lays its text from top to bottom because it is vertical. In addition both wrapped layouts use the gravity property to center the widgets as well.

The one issue that you will notice if you try and run the app to see it on an emulator or a real device is that the default "Hello world!" layout from the activity_main.xml file is shown to the user, not our linear_experiment.xml layout.

We can fix this with these simple steps:

  1. Open up MainActivity.java in the editor.
  2. Look at the onCreate method and find the following line of code:
    setContentView(R.layout.activity_main);
  3. Change the highlighted part, activity_main to the name of the layout we want to display, linear_experiment.
  4. Run the app again and it will display our neat and tidy experiment.

Here we can see that the setContentView method call, within the onCreate method, is what displays a layout to the user. We just need to pass in the name of the XML layout as an argument. We append R. (for the res folder) and layout. (for the layout folder) and it just works. Don't worry too much about the new terminology, we will look at passing in arguments in Chapter 8, Coding in Java Part 2 – Methods.

The new knowledge we have just gained about how to use Java to display a given UI layout in XML will be useful in the next project.

We have probably done enough experimenting. Now let's build some slightly more real-world UI's.