Chapter 3. Getting to Know the Android User Interface

WHAT YOU WILL LEARN IN THIS CHAPTER

  • The various ViewGroups you can use to lay out your views

  • How to adapt to changes in screen orientation

  • How to manage screen orientation changes

  • How to create the UI programmatically

  • How to listen for UI notifications

In Chapter 2, you learned about the Activity class and its life cycle. You learned that an activity is a means by which users interact with the application. However, an activity by itself does not have a presence on the screen. Instead, it has to draw the screen using Views and ViewGroups. In this chapter, you will learn the details about creating user interfaces in Android, how users interact with them. In addition, you will learn how to handle changes in screen orientation on your Android devices.

UNDERSTANDING THE COMPONENTS OF A SCREEN

In Chapter 2, you saw that the basic unit of an Android application is an activity. An activity displays the user interface of your application, which may contain widgets like buttons, labels, text boxes, and so on. Typically, you define your UI using an XML file (e.g., the main.xml file located in the res/layout folder), which may look like this:

<?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:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello"
    />
</LinearLayout>

During run time, you load the XML UI in the onCreate() event handler in your Activity class, using the setContentView() method of the Activity class:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main;
}

During compilation, each element in the XML file is compiled into its equivalent Android GUI class, with attributes represented by methods. The Android system then creates the UI of the activity when it is loaded.

Note

While it is always easier to build your UI using an XML file, sometimes you need to build your UI dynamically during run time (for example, when writing games). Hence, it is also possible to create your UI entirely using code. Later in this chapter you will see an example of how this can be done.

Views and ViewGroups

An activity contains Views and ViewGroups. A view is a widget that has an appearance on screen. Examples of views are buttons, labels, and text boxes. A view derives from the base class android.view.View.

Note

Chapters 4 and 5 discuss the various common views in Android.

One or more views can be grouped together into a ViewGroup. A ViewGroup (which is itself a special type of view) provides the layout in which you can order the appearance and sequence of views. Examples of ViewGroups include LinearLayout and FrameLayout. A ViewGroup derives from the base class android.view.ViewGroup.

Android supports the following ViewGroups:

  • LinearLayout

  • AbsoluteLayout

  • TableLayout

  • RelativeLayout

  • FrameLayout

  • ScrollView

The following sections describe each of these ViewGroups in more detail. Note that in practice it is common to combine different types of layouts to create the UI you want.

LinearLayout

The LinearLayout arranges views in a single column or a single row. Child views can be arranged either vertically or horizontally. To see how LinearLayout works, consider the following elements typically contained in the main.xml file:

<?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:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text="@string/hello"
  />
</LinearLayout>

In the main.xml file, observe that the root element is <LinearLayout> and it has a <TextView> element contained within it. The <LinearLayout> element controls the order in which the views contained within it appear.

Each View and ViewGroup has a set of common attributes, some of which are described in Table 3-1.

Table 3-1. Common Attributes Used in Views and ViewGroups

ATTRIBUTE

DESCRIPTION

layout_width

Specifies the width of the View or ViewGroup

layout_height

Specifies the height of the View or ViewGroup

layout_marginTop

Specifies extra space on the top side of the View or ViewGroup

layout_marginBottom

Specifies extra space on the bottom side of the View or ViewGroup

layout_marginLeft

Specifies extra space on the left side of the View or ViewGroup

layout_marginRight

Specifies extra space on the right side of the View or ViewGroup

layout_gravity

Specifies how child Views are positioned

layout_weight

Specifies how much of the extra space in the layout should be allocated to the View

layout_x

Specifies the x-coordinate of the View or ViewGroup

layout_y

Specifies the y-coordinate of the View or ViewGroup

Note

Some of these attributes are applicable only when a View is in a specific ViewGroup. For example, the layout_weight and layout_gravity attributes are applicable only when a View is in either a LinearLayout or a TableLayout.

For example, the width of the <TextView> element fills the entire width of its parent (which is the screen in this case) using the fill_parent constant. Its height is indicated by the wrap_content constant, which means that its height is the height of its content (in this case, the text contained within it). If you don't want to have the <TextView> view occupy the entire row, you can set its layout_width attribute to wrap_content, like this:

< TextView
    android:layout_width="wrap_content
    android:layout_height="wrap_content"
    android:text="@string/hello"
/>

This will set the width of the view to be equal to the width of the text contained within it.

Consider the following layout:

<?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:layout_width="105dp"
    android:layout_height="wrap_content"
    android:text="@string/hello"
    />
<Button
    android:layout_width="160dp"
    android:layout_height="wrap_content"
    android:text="Button"
    />
</LinearLayout>

Here, you set the width of both the TextView and Button views to an absolute value. In this case, the width for the TextView is set to 105 density-independent pixels wide, and the Button to 160 density-independent pixels wide. Figure 3-1 shows how the views look when viewed on an emulator with a resolution of 320×480.

Figure 3-2 shows how the views look when viewed on a high-resolution (480×800) emulator.

FIGURE 3-1

Figure 3-1. FIGURE 3-1

FIGURE 3-2

Figure 3-2. FIGURE 3-2

As you can see, in both emulators the widths of both views are the same with respect to the width of the emulator. This demonstrates the usefulness of using the dp unit, which ensures that even if the resolution of the target device is different, the size of the view relative to the device remains unchanged.

The preceding example also specifies that the orientation of the layout is vertical:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >

The default orientation layout is horizontal, so if you omit the android:orientation attribute, the views will appear as shown in Figure 3-3.

FIGURE 3-3

Figure 3-3. FIGURE 3-3

In LinearLayout, you can apply the layout_weight and layout_gravity attributes to views contained within it, as the following modifications to main.xml show:

<?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:layout_width="105dp"
    android:layout_height="wrap_content"
    android:text="@string/hello"
    />
<Button
    android:layout_width="160dp"
    android:layout_height="wrap_content"
    android:text="Button"
android:layout_gravity="right"
    android:layout_weight="0.2"
    />
<EditText
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textSize="18sp"
    android:layout_weight="0.8"
    />
</LinearLayout>

Figure 3-4 shows that the button is aligned to the right of its parent (which is the LinearLayout) using the layout_gravity attribute. At the same time, you use the layout_weight attribute to specify the ratio in which the Button and EditText views occupy the remaining space on the screen. The total value for the layout_weight attribute must be equal to 1.

FIGURE 3-4

Figure 3-4. FIGURE 3-4

AbsoluteLayout

The AbsoluteLayout enables you to specify the exact location of its children. Consider the following UI defined in main.xml:

<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    xmlns:android="http://schemas.android.com/apk/res/android"
>
<Button
    android:layout_width="188dp"
    android:layout_height="wrap_content"
    android:text="Button"
    android:layout_x="126px"
    android:layout_y="361px"
    />
<Button
    android:layout_width="113dp"
    android:layout_height="wrap_content"
    android:text="Button"
    android:layout_x="12px"
    android:layout_y="361px"
    />
</AbsoluteLayout>

Figure 3-5 shows the two Button views located at their specified positions using the android_layout_x and android_layout_y attributes.

FIGURE 3-5

Figure 3-5. FIGURE 3-5

However, there is a problem with the AbsoluteLayout when the activity is viewed on a high-resolution screen (see Figure 3-6). For this reason, the AbsoluteLayout has been deprecated since Android 1.5 (although it is still supported in the current version). You should avoid using the AbsoluteLayout in your UI, as it is not guaranteed to be supported in future versions of Android. You should instead use the other layouts described in this chapter.

FIGURE 3-6

Figure 3-6. FIGURE 3-6

TableLayout

The TableLayout groups views into rows and columns. You use the <TableRow> element to designate a row in the table. Each row can contain one or more views. Each view you place within a row forms a cell. The width of each column is determined by the largest width of each cell in that column.

Consider the content of main.xml shown here:

<?xml version="1.0" encoding="utf-8"?>
<TableLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="fill_parent"
    android:layout_width="fill_parent"
    >
    <TableRow>
        <TextView
            android:text="User Name:"
            android:width ="120px"
            />
      <EditText
          android:id="@+id/txtUserName"
          android:width="200px" />
    </TableRow>
<TableRow>
       <TextView
           android:text="Password:"
           />
       <EditText
           android:id="@+id/txtPassword"
           android:password="true"
           />
   </TableRow>
   <TableRow>
       <TextView />
       <CheckBox android:id="@+id/chkRememberPassword"
           android:layout_width="fill_parent"
           android:layout_height="wrap_content"
           android:text="Remember Password"
           />
    </TableRow>
    <TableRow>
        <Button
            android:id="@+id/buttonSignIn"
            android:text="Log In" />
    </TableRow>
</TableLayout>

Figure 3-7 shows what the preceding looks like when rendered on the Android Emulator.

FIGURE 3-7

Figure 3-7. FIGURE 3-7

Note that in the preceding example, there are two columns and four rows in the TableLayout. The cell directly under the Password TextView is populated with an <TextView/> empty element. If you don't do this, the Remember Password checkbox will appear under the Password TextView, as shown in Figure 3-8.

FIGURE 3-8

Figure 3-8. FIGURE 3-8

RelativeLayout

The RelativeLayout enables you to specify how child views are positioned relative to each other. Consider the following main.xml file:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:id="@+id/RLayout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    xmlns:android="http://schemas.android.com/apk/res/android"
    >
    <TextView
        android:id="@+id/lblComments"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Comments"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        />
    <EditText
        android:id="@+id/txtComments"
        android:layout_width="fill_parent"
        android:layout_height="170px"
        android:textSize="18sp"
        android:layout_alignLeft="@+id/lblComments"
        android:layout_below="@+id/lblComments"
        android:layout_centerHorizontal="true"
        />
    <Button
        android:id="@+id/btnSave"
android:layout_width="125px"
      android:layout_height="wrap_content"
      android:text="Save"
      android:layout_below="@+id/txtComments"
      android:layout_alignRight="@+id/txtComments"
      />
  <Button
      android:id="@+id/btnCancel"
      android:layout_width="124px"
      android:layout_height="wrap_content"
      android:text="Cancel"
      android:layout_below="@+id/txtComments"
      android:layout_alignLeft="@+id/txtComments"
      />
</RelativeLayout>

Notice that each view embedded within the RelativeLayout has attributes that enable it to align with another view. These attributes are as follows:

  • layout_alignParentTop

  • layout_alignParentLeft

  • layout_alignLeft

  • layout_alignRight

  • layout_below

  • layout_centerHorizontal

The value for each of these attributes is the ID for the view that you are referencing. The preceding XML UI creates the screen shown in Figure 3-9.

FIGURE 3-9

Figure 3-9. FIGURE 3-9

FrameLayout

The FrameLayout is a placeholder on screen that you can use to display a single view. Views that you add to a FrameLayout are always anchored to the top left of the layout. Consider the following content in main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:id="@+id/RLayout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    xmlns:android="http://schemas.android.com/apk/res/android"
    >
    <TextView
        android:id="@+id/lblComments"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This is my lovely dog, Ookii"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        />
    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/lblComments"
        android:layout_below="@+id/lblComments"
        android:layout_centerHorizontal="true"
        >
      <ImageView
          android:src = "@drawable/ookii"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          />
    </FrameLayout>
</RelativeLayout>

Here, you have a FrameLayout within a RelativeLayout. Within the FrameLayout, you embed an ImageView. The UI is shown in Figure 3-10.

Note

This example assumes that the res/drawable-mdpi folder has an image named ookii.png.

If you add another view (such as a Button view) within the FrameLayout, the view will overlap the previous view (see Figure 3-11):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:id="@+id/RLayout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
   >
   <TextView
       android:id="@+id/lblComments"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="This is my lovely dog, Ookii"
       android:layout_alignParentTop="true"
       android:layout_alignParentLeft="true"
       />
   <FrameLayout
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_alignLeft="@+id/lblComments"
       android:layout_below="@+id/lblComments"
       android:layout_centerHorizontal="true"
       >
       <ImageView
            android:src = "@drawable/ookii"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            />
       <Button
            android:layout_width="124dp"
            android:layout_height="wrap_content"
            android:text="Print Picture"
            />
    </FrameLayout>
</RelativeLayout>
FIGURE 3-10

Figure 3-10. FIGURE 3-10

FIGURE 3-11

Figure 3-11. FIGURE 3-11

Note

You can add multiple views to a FrameLayout, but each will be stacked on top of the previous one. This is useful in cases where you want to animate series of images, with only one visible at a time. "

ScrollView

A ScrollView is a special type of FrameLayout in that it enables users to scroll through a list of views that occupy more space than the physical display. The ScrollView can contain only one child view or ViewGroup, which normally is a LinearLayout.

Note

Do not use a ListView (discussed in Chapter 4) together with the ScrollView. The ListView is designed for showing a list of related information and is optimized for dealing with large lists.

The following main.xml content shows a ScrollView containing a LinearLayout, which in turn contains some Button and EditText views:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    android:layout_width="fill_parent"
android:layout_height="fill_parent"
  xmlns:android="http://schemas.android.com/apk/res/android"
  >
  <LinearLayout
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:orientation="vertical"
      >
      <Button
          android:id="@+id/button1"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:text="Button 1"
          />
      <Button
          android:id="@+id/button2"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:text="Button 2"
          />
      <Button
          android:id="@+id/button3"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:text="Button 3"
          />
      <EditText
          android:id="@+id/txt"
          android:layout_width="fill_parent"
          android:layout_height="300px"
          />
      <Button
          android:id="@+id/button4"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:text="Button 4"
          />
      <Button
            android:id="@+id/button5"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="Button 5"
            />
    </LinearLayout>
</ScrollView>

Figure 3-12 shows the ScrollView enabling the users to drag the screen upward to reveal the views located at the bottom of the screen.

FIGURE 3-12

Figure 3-12. FIGURE 3-12

ADAPTING TO DISPLAY ORIENTATION

One of the key features of modern smartphones is their ability to switch screen orientation, and Android is no exception. Android supports two screen orientations: portrait and landscape. By default, when you change the display orientation of your Android device, the current activity that is displayed will automatically redraw its content in the new orientation. This is because the onCreate() event of the activity is fired whenever there is a change in display orientation.

Note

When you change the orientation of your Android device, your current activity is actually destroyed and then re-created.

However, when the views are redrawn, they may be drawn in their original locations (depending on the layout selected). Figure 3-13 shows one of the examples illustrated earlier displayed in both portrait and landscape mode.

As you can observe in landscape mode, a lot of empty space on the right of the screen could be used. Furthermore, any additional views at the bottom of the screen would be hidden when the screen orientation is set to landscape.

FIGURE 3-13

Figure 3-13. FIGURE 3-13

In general, you can employ two techniques to handle changes in screen orientation:

  • Anchoring — The easiest way is to "anchor" your views to the four edges of the screen. When the screen orientation changes, the views can anchor neatly to the edges.

  • Resizing and repositioning — Whereas anchoring and centralizing are simple techniques to ensure that views can handle changes in screen orientation, the ultimate technique is resizing each and every view according to the current screen orientation.

Anchoring Views

Anchoring could be easily achieved by using RelativeLayout. Consider the following main.xml containing five Button views embedded within the <RelativeLayout> element:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    xmlns:android="http://schemas.android.com/apk/res/android"
    >
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Top Left Button"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        />
<Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Top Right Button"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        />
    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Bottom Left Button"
        android:layout_alignParentLeft="true"
        android:layout_alignParentBottom="true"
        />
    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Bottom Right Button"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        />
    <Button
        android:id="@+id/button5"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Middle Button"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        />
</RelativeLayout>

Observe the following attributes found in the various Button views:

  • layout_alignParentLeft — Aligns the view to the left of the parent view

  • layout_alignParentRight — Aligns the view to the right of the parent view

  • layout_alignParentTop — Aligns the view to the top of the parent view

  • layout_alignParentBottom — Aligns the view to the bottom of the parent view

  • layout_centerVertical — Centers the view vertically within its parent view

  • layout_centerHorizontal — Centers the view horizontally within its parent view

Figure 3-14 shows the activity when viewed in portrait mode.

When the screen orientation changes to landscape mode, the four buttons are aligned to the four edges of the screen, and the center button is centered in the middle of the screen with its width fully stretched (see Figure 3-15).

FIGURE 3-14

Figure 3-14. FIGURE 3-14

FIGURE 3-15

Figure 3-15. FIGURE 3-15

Resizing and Repositioning

Apart from anchoring your views to the four edges of the screen, an easier way to customize the UI based on screen orientation is to create a separate res/layout folder containing the XML files for the UI of each orientation. To support landscape mode, you can create a new folder in the res folder and name it as layout-land (representing landscape). Figure 3-16 shows the new folder containing the file main.xml.

FIGURE 3-16

Figure 3-16. FIGURE 3-16

Basically, the main.xml file contained within the layout folder defines the UI for the activity in portrait mode, whereas the main.xml file in the layout-land folder defines the UI in landscape mode.

The following shows the content of main.xml under the layout folder:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    xmlns:android="http://schemas.android.com/apk/res/android"
    >
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Top Left Button"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        />
    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Top Right Button"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        />
    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Bottom Left Button"
        android:layout_alignParentLeft="true"
        android:layout_alignParentBottom="true"
        />
    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Bottom Right Button"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        />
<Button
        android:id="@+id/button5"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Middle Button"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        />
</RelativeLayout>

The following shows the content of main.xml under the layout-land folder (the statements in bold are the additional views to display in landscape mode):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    xmlns:android="http://schemas.android.com/apk/res/android"
    >
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Top Left Button"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        />
    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Top Right Button"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        />
    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Bottom Left Button"
        android:layout_alignParentLeft="true"
        android:layout_alignParentBottom="true"
        />
    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Bottom Right Button"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        />
    <Button
        android:id="@+id/button5"
        android:layout_width="fill_parent"
android:layout_height="wrap_content"
        android:text="Middle Button"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        />
    <Button
        android:id="@+id/button6"
        android:layout_width="180px"
        android:layout_height="wrap_content"
        android:text="Top Middle Button"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:layout_alignParentTop="true"
        />
    <Button
        android:id="@+id/button7"
        android:layout_width="180px"
        android:layout_height="wrap_content"
        android:text="Bottom Middle Button"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:layout_alignParentBottom="true"
        />
</RelativeLayout>

When the activity is loaded in portrait mode, it will show five buttons, as shown in Figure 3-17.

FIGURE 3-17

Figure 3-17. FIGURE 3-17

When the activity is loaded in landscape mode, there are now seven buttons (see Figure 3-18), proving that different XML files are loaded when the device is in a different orientation.

FIGURE 3-18

Figure 3-18. FIGURE 3-18

Using this method, when the orientation of the device changes, Android will automatically load the appropriate XML file for your activity depending on the current screen orientation.

MANAGING CHANGES TO SCREEN ORIENTATION

Now that you have looked at how to implement the two techniques for adapting to screen orientation changes, let's explore what happens to an activity's state when the device changes orientation.

The following Try It Out demonstrates the behavior of an activity when the device changes orientation.

Persisting State Information during Changes in Configuration

So far, you have learned that changing screen orientation destroys an activity and re-creates it. Keep in mind that when an activity is re-created, the current state of the activity may be lost. When an activity is killed, it will fire one or more of the following two events:

  • onPause() — This event is always fired whenever an activity is killed or pushed into the background.

  • onSaveInstanceState() — This event is also fired whenever an activity is about to be killed or put into the background (just like the onPause() event). However, unlike the onPause() event, the onSaveInstanceState event is not fired when an activity is being unloaded from the stack (for example, when the user pressed the Back button), because there is no need to restore its state later.

In short, to preserve the state of an activity, you could always implement the onPause() event, and then use your own ways to preserve the state of your activity, such as using a database, internal or external file storage, etc.

If you simply want to preserve the state of an activity so that it can be restored later when the activity is re-created (such as when the device changes orientation), a much simpler way would be to implement the onSaveInstanceState() method, as it provides a Bundle object as an argument so that you can use it to save your activity's state. The following code shows that you can save the string ID into the Bundle object during the onSaveInstanceState event:

@Override
public void onSaveInstanceState(Bundle outState) {
    //---save whatever you need to persist---
    outState.putString("ID", "1234567890");
    super.onSaveInstanceState(outState);
}

When an activity is re-created, the onCreate() event is first fired, followed by the onRestoreInstanceState() event, which enables you to retrieve the state that you saved previously in the onSaveInstanceState event through the Bundle object in its argument:

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    //---retrieve the information persisted earlier---
    String ID = savedInstanceState.getString("ID");
}

Although you can use the onSaveInstanceState() event to save state information, note the limitation that you can only save your state information into a Bundle object. If you need to save more complex data structures, then this is not an adequate solution.

Another event handler that you can use is the onRetainNonConfigurationInstance() event. This event is fired when an activity is about to be destroyed due to a configuration change. You can save your current data by returning it in this event, like this:

@Override
public Object onRetainNonConfigurationInstance() {
    //---save whatever you want here; it takes in an Object type---
    return("Some text to preserve");
}

Note

When screen orientation changes, this change is part of what is known as a configuration change. A configuration change will cause your current activity to be destroyed.

Note that this event returns an Object type, which pretty much allows you to return any data type. To extract the saved data, you can extract it in the onCreate() event, using the getLastNonConfigurationInstance() method, like this:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    Log.d(StateInfo", "onCreate");
    String str = (String) getLastNonConfigurationInstance();
}

Detecting Orientation Changes

Sometimes you need to know the device's current orientation during run time. To determine that, you can use the WindowManager class. The following code snippet demonstrates how you can programmatically detect the current orientation of your activity:

import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
//...
  public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.main);

      //---get the current display info---
      WindowManager wm = getWindowManager();
      Display d = wm.getDefaultDisplay();

      if (d.getWidth() > d.getHeight())
{
      //---landscape mode---
      Log.d("Orientation", "Landscape mode");
  }
  else
  {
      //---portrait mode---
      Log.d("Orientation", "Portrait mode");
  }
}

The getDefaultDisplay() method returns a Display object representing the screen of the device. You can then get its width and height and deduce the current orientation.

Controlling the Orientation of the Activity

Occasionally you might want to ensure that your application is only displayed in a certain orientation. For example, you may be writing a game that should only be viewed in landscape mode. In this case, you can programmatically force a change in orientation using the setRequestOrientation() method of the Activity class:

import android.content.pm.ActivityInfo;

public class MainActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        //---change to landscape mode---
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}

To change to portrait mode, use the ActivityInfo.SCREEN_ORIENTATION_PORTRAIT constant:

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

Besides using the setRequestOrientation() method, you can also use the android:screenOrientation attribute on the <activity> element in AndroidManifest.xml as follows to constrain the activity to a certain orientation:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="net.learn2develop.Orientations"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".MainActivity"
                  android:label="@string/app_name"
                  android:screenOrientation="landscape" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
</activity>
    </application>
    <uses-sdk android:minSdkVersion="9" />
</manifest>

The preceding example constrains the activity to a certain orientation (landscape in this case) and prevents the activity from being destroyed; that is, the activity will not be destroyed and the onCreate() event will not be fired again when the orientation of the device changes.

Following are two other values that you can specify in the android:screenOrientation attribute:

  • portrait — Portrait mode

  • sensor — Based on the accelerometer

CREATING THE USER INTERFACE PROGRAMMATICALLY

So far, all the UIs you have seen in this chapter are created using XML. As mentioned earlier, besides using XML you can also create the UI using code. This approach is useful if your UI needs to be dynamically generated during run time. For example, suppose you are building a cinema ticket reservation system and your application will display the seats of each cinema using buttons. In this case, you would need to dynamically generate the UI based on the cinema selected by the user.

The following Try It Out demonstrates the code needed to dynamically build the UI in your activity.

LISTENING FOR UI NOTIFICATIONS

Users interact with your UI at two levels: the activity level and the views level. At the activity level, the Activity class exposes methods that you can override. Some common methods that you can override in your activities include the following:

  • onKeyDown — Called when a key was pressed and not handled by any of the views contained within the activity

  • onKeyUp — Called when a key was released and not handled by any of the views contained within the activity

  • onMenuItemSelected — Called when a panel's menu item has been selected by the user (covered in Chapter 5)

  • onMenuOpened — Called when a panel's menu is opened by the user (covered in Chapter 5)

Overriding Methods Defined in an Activity

To understand how activities interact with the user, let's start off by overriding some of the methods defined in the activity's base class and learn how they are handled when the user interacts with the activity.

Registering Events for Views

Views can fire events when users interact with them. For example, when a user touches a Button view, you need to service the event so that the appropriate action can be performed. To do so, you need to explicitly register events for views.

Using the same example discussed in the previous section, recall that the activity has two Button views; therefore, you can register the button click events using an anonymous class as shown here:

package net.learn2develop.UIActivity;

import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Toast;

import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {
     /** Called when the activity is first created. */
     @Override
     public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        //---the two buttons are wired to the same event handler---
         Button btn1 = (Button)findViewById(R.id.btn1);
         btn1.setOnClickListener( btnListener);

        Button btn2 = (Button)findViewById(R.id. btn2);
        btn2.setOnClickListener( btnListener);
     }

     //---create an anonymous class to act as a button click listener---
     private OnClickListener btnListener = new OnClickListener()
     {
        public void onClick(View v)
{
            Toast.makeText(getBaseContext(),
                    ((Button) v).getText() + " was clicked",
                    Toast.LENGTH_LONG).show();
         }
    };

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event)
     {
        switch (keyCode)
         {
                 //...
                 //...
        }
         return false;
     }
}

If you now press either the OK button or the Cancel button, the appropriate message will be displayed (see Figure 3-25), proving that the event is wired up properly.

FIGURE 3-25

Figure 3-25. FIGURE 3-25

Besides defining an anonymous class for the event handler, you can also define an anonymous inner class to handle an event. The following example shows how you can handle the onFocusChange() event for the EditText view:

import android.widget.EditText;

public class MainActivity extends Activity {
/** Called when the activity is first created. */
     @Override
     public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        //---the two buttons are wired to the same event handler---
         Button btn1 = (Button)findViewById(R.id.btn1);
        btn1.setOnClickListener( btnListener);

        Button btn2 = (Button)findViewById(R.id. btn2);
        btn2.setOnClickListener( btnListener);

        EditText txt1 = (EditText)findViewById(R.id.txt1);

        //---create an anonymous inner class to act as an onfocus listener---
        txt1.setOnFocusChangeListener(new View.OnFocusChangeListener()
        {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                Toast.makeText(getBaseContext(),
                    ((EditText) v).getId() + " has focus - " + hasFocus,
                    Toast.LENGTH_LONG).show();
             }
        });
}

As shown in Figure 3-26, when the EditText view receives the focus, a message is printed on the screen.

FIGURE 3-26

Figure 3-26. FIGURE 3-26

SUMMARY

In this chapter, you have learned how user interfaces are created in Android. You have also learned about the different layouts that you can use to position the views in your Android UI. Because Android devices support more than one screen orientation, you need to take special care to ensure that your UI can adapt to changes in screen orientation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.100.118