Chapter 11. A Widget Bestiary

As we have seen, there are three ways to implement a new behavior in an application. In increasing order of complexity, you can:

  • Find a toolbox widget that already does nearly what you need and extend it.

  • Use the handler mechanism demonstrated previously in Example 10-4.

  • Override event receiver methods and implement them yourself.

Handling raw events across multiple platforms can be quite complicated. Different devices, for instance, may have radically different keypads: for instance, four-key versus five-key D-pads. Some devices still require triple-tapping to enter alphabetic information. This kind of diversity is a serious issue in the mobile environment and can be a nightmare for the developer who wants to keep her application portable.

When designing your application, it’s clearly smart to let the framework do as much as possible. The best option is to find some toolbox widget that has nearly the behavior you require and extend it to meet your needs. The toolkit provides extensive tools for doing this: XML attributes, fine-grained and overridable methods, and so on.

If it isn’t possible to customize an existing widget, you should consider the listener mechanism, demonstrated previously in Example 10-5. Only when it is necessary to change the existing behavior of a widget should you consider overriding event receiver methods.

User interface frameworks have different names for the components from which they’re composed: the text boxes, buttons, canvases, and other components that you use to create your unique application user interface. Android generically calls them Views, and the documentation defines them simply as:

View: An object that knows how to draw itself to the screen.

So any object that draws itself is a View, and Views that can contain or group other Views are appropriately called ViewGroups. Views are arranged and displayed on the screen according to a Layout, which gives Android hints about how you’d like to see the Views arranged. In the next few sections we’ll look first at simple Views, then at ViewGroups, and finally at Layouts. Since expandability is a core principle for Android, we will also look at what you need to do to define your own custom Views and Layouts.

As we’ve already seen, Views and Layouts both have attributes that can either be defined in Java source code or in the XML file associated with the Activity that uses the View or Layout. When the attributes are in an XML file, they are “inflated” at runtime, meaning that they are applied to their respective Views by the Android framework to determine how the Views look and operate.

There are so many attributes that it doesn’t make sense to list them all in these examples. We describe the key ones, and the rest are explained in the documentation that comes with the Android SDK. A quick search for android.widget.view_name will give you the class definition for that View, including all the attributes available for it, and a description of each.

Android Views

The Views in the following section are the meat and potatoes of your application; essential widgets that you’ll use over and over and that your users will be familiar with from other applications.

TextView and EditText

A TextView, as shown in the line “This is some text” in Figure 11-1, is just what you’d expect: a place to display a text string. The vanilla TextView is for display only, whereas EditText is a predefined subclass of TextView that includes rich editing capabilities.

TextView, EditText, and Button
Figure 11-1. TextView, EditText, and Button

Each TextView has the attributes you’d expect of such a component: you can change its height, width, font, text color, background color, and so forth. TextViews also have some useful unique attributes:

autoLink

If set (true), finds URLs in the displayed text and automatically converts them to clickable links.

autoText

If set (true), finds and corrects simple spelling errors in the text.

editable

If set (true), indicates that the program has defined an input method to receive input text (default is false for TextView, and true for EditText).

inputMethod

Identifies the input method (EditText defines one for generic text).

Example 11-1 shows how to use a TextView and an EditText with Buttons. (Buttons are covered in the next section.) It also shows the XML layout file (main.xml), which uses pretty standard and recommended layout parameters.

Example 11-1. Layout file for TextView and EditView example
<?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:id="@+id/txtDemo"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    />
<EditText
    android:id="@+id/eTxtDemo"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    />
<Button
    android:id="@+id/btnDone"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Log it"
    />
</LinearLayout>

Example 11-2 contains the accompanying Java source (TextViewDemo.java).

Example 11-2. Java for TextView and EditView: TextViewDemo.java
package com.oreilly.demo;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class TextViewDemo extends Activity {
    private static TextView txt1;
    private static EditText etxt1;
    private static Button btn1;

    // Create a button click listener for the Done button.
    private final Button.OnClickListener btnDoneOnClick = new Button.OnClickListener() {1
        public void onClick(View v) {
          String input = etxt1.getText().toString();
          //Log the input string
          Log.v("TextViewDemo", input);
          etxt1.setText("");
        }
    };

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

        //Get pointers to the Views defined in main.xml
        txt1 = (TextView) findViewById(R.id.txtDemo);4
        etxt1 = (EditText) findViewById(R.id.eTxtDemo);
        btn1 = (Button) findViewById(R.id.btnDone);

        //Set the string displayed in TextView1
        txt1.setText("This is some text.");5

        //Set the OnClickListener for the Done button
        btn1.setOnClickListener(btnDoneOnClick);6
    }
}

Here are some of the highlights of the code:

1

Defines a ClickListener that we’ll attach to the “Log it” Button.

2

Because onCreate is executed just once, as soon as Android instantiates this View, we put all the configuration we need here.

3

Loads the XML layout file for the application by setting the ContentView to main.xml.

4

Finds the Views that are defined in main.xml.

5

Puts an initial string into the TextView. (We also could have done this in the XML file, as was done in the MicroJobs application in Initialization in MicroJobs.java.)

6

Connects the Button with the ClickListener.

Now the user can enter and edit text in the EditText, and when he clicks on “Log it”, the OnClickListener is called and the text is written to the logcat log. The string in the EditText is cleared out, and the widget is ready for another entry.

Button and ImageButton

The Button View is just a button, printed with some text to identify it, that the user can click to invoke some action. The previous section created a Button and connected it to an OnClickListener method that executes when the Button is clicked.

Android has a very visual, mobile-oriented user interface, so you might want to use a button with an image on it rather than one with text. Android provides the ImageButton View for just that purpose. You can adapt Example 11-2 to use an ImageButton by making one change in the XML file and another in the Java code:

  1. In main.xml, replace the Button definition for btnDone with an ImageButton:

    ...
    <ImageButton
      android:id="@+id/btnDone"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      />
    ...
  2. In TextViewDemo.java, redefine btn1 as an ImageButton and add a line to set the image to a PNG image in the drawable directory:

    ...
        private static ImageButton btn1;
    
    ...
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
    
            //Get pointers to the Views defined in main.xml
            txt1 = (TextView) findViewById(R.id.txtDemo);
            etxt1 = (EditText) findViewById(R.id.eTxtDemo);
            btn1 = (ImageButton) findViewById(R.id.btnDone);
    ...
            //Set the image for the Done button
            btn1.setImageResource(R.drawable.log);
    ...

The button now appears as shown in Figure 11-2.

Text boxes with an ImageButton
Figure 11-2. Text boxes with an ImageButton

Adapters and AdapterViews

Adapters and AdapterViews are an important and useful basis for several of the views discussed in the rest of this chapter. Using extensions to these classes, you can address an extremely wide variety of situations.

The AdapterView is a generic, list-oriented view of data. Any collection of data objects that can be ordered in some relatively stable way can be displayed through an AdapterView. An AdapterView is always associated with an Adapter, which acts as the bridge between it and the underlying data collection. The Adapter has two responsibilities:

  • At the request of the AdapterView, the Adapter must be able to find the data object that corresponds to a particular index. It must, in other words, be able to find the data object that is visible in the AdapterView at a particular location.

  • Inversely, the Adapter must be able to supply a view through which the data at a particular index can be displayed.

It takes only a moment’s reflection to understand how the AdapterView works: It is a ViewGroup that contains all the machinery necessary to serve as both the View and Controller for a collection of generic widgets. It can lay them out on the display, pass in clicks and keystrokes, and so on. It need never concern itself with what the subviews actually display; it distinguishes them only by their indexes. Whenever it needs to perform either of the two operations that are not entirely generic—creating a new view or getting the data object attached to a particular view—it relies on the Adapter to convert an index into either a data object or the view of a data object.

The AdapterView requests new views from an implementation of the Adapter interface, as it needs them, for display. For instance, as a user scrolls though a list of contacts, the AdapterView requests a new view for each new contact that becomes visible. As an optimization, the AdapterView may offer a view that is no longer visible (in this case, one that has scrolled off the display) for reuse. This can dramatically reduce memory churn and speed up display.

When offered a recycled view, however, the Adapter must verify that it is the right kind of view through which to display the data object at the requested index. This is necessary because the Adapter is not limited to returning instances of a single view class in response to the request for a view. If the Adapter represents several kinds of objects, it might create several different types of views, each applicable to some subset of the data objects in the collection. A list of contacts, for instance, might have two entirely different view classes: one for displaying acquaintances that are currently online and another for those who are not. The latter might completely ignore clicks, whereas the former would open a new chat session when clicked.

Although AdapterView and Adapter are both abstract and cannot be directly instantiated, the UI toolkit includes several prebuilt Adapters and AdapterViews that can be used unmodified or further subclassed to provide your own customizations. ListAdapter and SpinnerAdapter are particularly useful Adapters, while ListView, GridView, Spinner, and Gallery are all handy subclasses of AdapterView. If you plan to create your own subclass of AdapterView, a quick look at the code for one of these classes will get you off to a running start.

A good example of the use of an AdapterView can be found in Gallery and GridView. The Gallery view in that section is a subclass of AdapterView, and uses a subclass of Adapter called ImageAdapter.

CheckBoxes, RadioButtons, and Spinners

The Views we present in this section are probably familiar to you from other user interfaces. Their purpose is to allow the user to choose from multiple options. CheckBoxes are typically used when you want to offer multiple selections with a yes/no or true/false choice for each. RadioButtons are used when only one choice is allowed at a time.

Spinners are similar to combo boxes in some frameworks. A combo box typically displays the currently selected option, along with a pull-down list from which the user can click on another option to select it.

Android has adapted these familiar components to make them more useful in a touchscreen environment. Figure 11-3 shows the three types of multiple-choice Views laid out on an Android application, with the Spinner pulled down to show the options.

CheckBox, RadioButtons, and Spinner
Figure 11-3. CheckBox, RadioButtons, and Spinner

The layout XML file that created the screen in the figure looks 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"
    >
<CheckBox
  android:id="@+id/cbxBox1"
  android:layout_width="20dp"
  android:layout_height="20dp"
  android:checked="false"
  />
<TextView
  android:id="@+id/txtCheckBox"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="CheckBox: Not checked"
    />
<RadioGroup
  android:id="@+id/rgGroup1"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:orientation="vertical">
  <RadioButton android:id="@+id/RB1" android:text="Button1" />
  <RadioButton android:id="@+id/RB2" android:text="Button2" />
  <RadioButton android:id="@+id/RB3" android:text="Button3" />
  </RadioGroup>
<TextView
  android:id="@+id/txtRadio"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text="RadioGroup: Nothing picked"
  />
<Spinner
  android:id="@+id/spnMusketeers"
  android:layout_width="250dp"
  android:layout_height="wrap_content"
  android:layout_centerHorizontal="true"
  android:layout_marginTop="2dp"
  />
</LinearLayout>

The file just lists each View we want on the screen along with the attributes we want. A RadioGroup is really a ViewGroup, so it contains the appropriate RadioButton Views. Example 11-3 shows the Java file that responds to user clicks.

Example 11-3. Java for CheckBox, RadioButtons, and Spinner
package com.oreilly.select;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import com.google.android.maps.GeoPoint;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.AdapterView.OnItemSelectedListener;

public class SelectExample extends Activity {

  private CheckBox checkBox;
  private TextView txtCheckBox, txtRadio;
  private RadioButton rb1, rb2, rb3;
  private Spinner spnMusketeers;

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

        checkBox = (CheckBox) findViewById(R.id.cbxBox1);
        txtCheckBox = (TextView) findViewById(R.id.txtCheckBox);
        txtRadio = (TextView) findViewById(R.id.txtRadio);
        rb1 = (RadioButton) findViewById(R.id.RB1);
        rb2 = (RadioButton) findViewById(R.id.RB2);
        rb3 = (RadioButton) findViewById(R.id.RB3);
        spnMusketeers = (Spinner) findViewById(R.id.spnMusketeers);

        // React to events from the CheckBox
        checkBox.setOnClickListener(new CheckBox.OnClickListener() {
          public void onClick(View v){
                if (checkBox.isChecked()) {
                    txtCheckBox.setText("CheckBox: Box is checked");
                }
                else
                {
                  txtCheckBox.setText("CheckBox: Not checked");
                }
          }
        });

        // React to events from the RadioGroup
        rb1.setOnClickListener(new RadioGroup.OnClickListener() {
          public void onClick(View v){
            txtRadio.setText("Radio: Button 1 picked");
          }
        });

        rb2.setOnClickListener(new RadioGroup.OnClickListener() {
          public void onClick(View v){
            txtRadio.setText("Radio: Button 2 picked");
          }
        });

        rb3.setOnClickListener(new RadioGroup.OnClickListener() {
          public void onClick(View v){
            txtRadio.setText("Radio: Button 3 picked");
          }
        });

        // Set up the Spinner entries
        List<String> lsMusketeers = new ArrayList<String>();
        lsMusketeers.add("Athos");
        lsMusketeers.add("Porthos");
        lsMusketeers.add("Aramis");

        ArrayAdapter<String> aspnMusketeers = 
          new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, 
           lsMusketeers);
        aspnMusketeers.setDropDownViewResource
           (android.R.layout.simple_spinner_dropdown_item);
        spnMusketeers.setAdapter(aspnMusketeers);

    // Set up a callback for the spinner
    spnMusketeers.setOnItemSelectedListener(
        new OnItemSelectedListener() {
            public void onNothingSelected(AdapterView<?> arg0) { }

            public void onItemSelected(AdapterView<?> parent, View v, 
             int position, long id)  {

              // Code that does something when the Spinner value changes
            }
        });
    }
}

The Views work as follows:

CheckBox

The CheckBox View takes care of flipping its state back and forth and displaying the appropriate checkmark when the state is true. All you have to do is create an “OnClickListener” to catch click events, and you can add whatever code you want to react.

RadioGroup

As mentioned earlier, the RadioGroup View is really a ViewGroup that contains any number of RadioButton Views. The user can select only one of the buttons at a time, and you capture the selections by setting OnClickListeners for each RadioButton. Note that clicking on one of the RadioButtons does not fire a click event for the RadioGroup.

Spinner

Spinners require the most work of these three Views, but can also provide the best use of scarce screen real estate. As shown, the Spinner is normally collapsed to the currently selected entry, and when you touch the down arrow on the right, it presents a drop-down list of the other choices. To make that happen, you must:

  1. Create a list of the selections (which can be a dynamic list built and changed by your application).

  2. Create an ArrayAdapter from the list that the Spinner can use for its drop-down list. Note that the formats shown for the ArrayAdapter (simple_spinner_item and simple_spinner_dropdown_item) are defined by Android; they do not appear in your resource XML files.

  3. Create an onItemSelectedListener for the Spinner to capture select events. The listener has to contain both an onItemSelected method and an onNothingSelected method.

ViewGroups

ViewGroups are Views that contain child Views. Each ViewGroup class embodies a different set of assumptions about how to display its child Views. All ViewGroups descend from the android.view.ViewGroup class. Layouts, which we’ll discuss later in the chapter, are a subset of ViewGroups.

The Gallery ViewGroup (Figure 11-4) displays multiple items in a horizontally scrolling list. The currently selected item is locked in the center of the screen. Any items that approach the edge of the screen begin to fade, giving the user the impression that there may be more items “around the corner.” The user can scroll horizontally through the items within the gallery. This ViewGroup is useful when you want to present a large set of possible choices to the user without using too much screen real estate.

The Gallery ViewGroup
Figure 11-4. The Gallery ViewGroup

A GridView (Figure 11-5, shown later) is very similar to a Gallery. Like a Gallery, the GridView displays many child Views that the user can manipulate. But in contrast to a Gallery, which is a one-dimensional list that the user can scroll horizontally, a GridView is a two-dimensional array that the user can scroll vertically.

The Gallery and GridView classes both descend from the AdapterView class, so you need a subclass of Adapter to provide a standardized way to access the underlying data. Any class that implements the Adapter class must implement the following abstract functions from that class:

int getCount

Returns the number of items in the data set represented by the Adapter.

Object getItem(int position)

Returns the object in the Adapter function (Adapter class) at the given position.

long getItem(int position)

Returns the row ID within the Adapter of the object at the given position.

View getView(int position, View convertView, ViewGroup parent)

Returns a View object that will display the data in the given position in the data set.

The ApiDemos application’s views.Gallery1.java file shows off the Gallery ViewGroup nicely. The demo displays a variety of images for the user to select, and when the user does select one, the image’s index number briefly appears as toast.

The ApiDemos application also includes two example GridView Activities that show how to use the GridView. We will not examine the GridView here, because the Gallery example is so similar.

Example 11-4 shows how to use a Gallery ViewGroup. Example 11-4 shows the XML layout file (gallery_1.xml).

Example 11-4. Layout file for Gallery example
<?xml version="1.0" encoding="utf-8"?>
<Gallery xmlns:android="http://schemas.android.com/apk/res/android" 
  android:id="@+id/gallery"1
    android:layout_width="fill_parent"2
    android:layout_height="wrap_content"3
/>

Here are some of the highlights of the layout code:

1

The id for the Gallery View is gallery. As you have seen before, the id is used by the findViewById function to hook a Java Object to the XML object named in the layout file.

2

layout_width is set to fill_parent so that the Gallery’s width will be the same as the parent’s.

3

layout_height is set to wrap_content, meaning that the height will be as high as the tallest child.

The GridView ViewGroup
Figure 11-5. The GridView ViewGroup

Now we’ll turn our attention to the Java implementation, Gallery1.java, shown in Example 11-5. We’ve modified the code from ApiDemos slightly to remove some features that do not add to our understanding of the Gallery ViewGroup.

Example 11-5. Java for Gallery: Gallery1.java
public class Gallery1 extends Activity {

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

        // Reference the Gallery view
        Gallery g = (Gallery) findViewById(R.id.gallery);1
        // Set the adapter to our custom adapter (below)
        g.setAdapter(new ImageAdapter(this));2

        // Set a item click listener, and just Toast the clicked position
        g.setOnItemClickListener(new OnItemClickListener() {3
            public void onItemClick(AdapterView parent, View v, int position, long id) {
                Toast.makeText(Gallery1.this, "" + position, Toast.LENGTH_SHORT).show();4
            }
        });
    }

Here are some of the highlights of the code:

1

In the Gallery’s onCreate method, create a Gallery object hooked to the id named gallery from the XML layout.

2

Display each user option using the custom adapter defined in Example 11-6 (shown next).

3

Set up a click listener on the Gallery object.

4

Display the the index (position) within the ImageAdapter of the photo the user clicked on as a Toast pop up.

In Example 11-5, the setAdapter function tells the Gallery object to use the ImageAdapter object as its Adapter. Example 11-6 defines our ImageAdapter class. This ImageAdapter implements all of the abstract functions required in its base class, BaseAdapter. For the simple case of this demo, picture resources represent the data that the Gallery view is displaying. An integer array, mImageIds, contains the resource IDs of the picture resources.

Example 11-6. Java for Gallery’s Adapter
    public class ImageAdapter extends BaseAdapter {
        int mGalleryItemBackground;

        private Context mContext;

        private Integer[] mImageIds = {1
                R.drawable.gallery_photo_1,
                R.drawable.gallery_photo_2,
                R.drawable.gallery_photo_3,
                R.drawable.gallery_photo_4,
                R.drawable.gallery_photo_5,
                R.drawable.gallery_photo_6,
                R.drawable.gallery_photo_7,
                R.drawable.gallery_photo_8
        };

        public ImageAdapter(Context c) {
            mContext = c;

            TypedArray a = obtainStyledAttributes(android.R.styleable.Theme);
            mGalleryItemBackground = a.getResourceId(
                    android.R.styleable.Theme_galleryItemBackground, 0);
            a.recycle();
        }

        public int getCount() {
            return mImageIds.length;
        }

        public Object getItem(int position) {
            return position;
        }

        public long getItemId(int position) {
            return position;
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            ImageView i = new ImageView(mContext);

            i.setImageResource(mImageIds[position]);2
            i.setScaleType(ImageView.ScaleType.FIT_XY);3
            i.setLayoutParams(new Gallery.LayoutParams(136, 88));4

            // The preferred Gallery item background
            i.setBackgroundResource(mGalleryItemBackground);

            return i;
        }
    }
}

Here are some of the highlights of the code:

1

Defines the mImageIds array. Each element holds a resource reference to an image that appears in the Gallery, and each image resource name maps to the filename in the resources directory. Thus R.drawable.gallery_photo_1 maps directly to /res/drawable/gallery_photo_1.jpg in the resource directory.

2

Sets the image for this position in the Gallery to the image in the corresponding element of mImageIds.

3

setScaleType controls how the image is resized to match the size of its container.

4

This call to setLayoutParams sets the size of the ImageView container.

ListView and ListActivity

ListView is similar to Gallery, but uses a vertically scrolling list in place of Gallery’s horizontally scrolling list. To create a ListView that takes up the entire screen, Android provides the ListActivity class (Figure 11-6).

The ApiDemos application includes many examples of ListActivity. The simplest is the List1 class, which displays a huge number of cheese names in a list. The cheese names are kept in a simple String array (who knew there were that many cheese varieties!):

public class List1 extends ListActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Use an existing ListAdapter that will map an array
        // of strings to TextViews
        setListAdapter(new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, mStrings));
    }

    private String[] mStrings = {
            "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", 
              "Ackawi",
            "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", 
              "Airedale",
            ...
ListActivity
Figure 11-6. ListActivity

Filling the ListView in the ListActivity is a simple matter of calling setListAdapter and passing it an ArrayAdapter that contains a reference to the list of strings.

ScrollView

A ScrollView is a container for another View that lets the user scroll that View vertically (a scrollbar is optional). A ScrollView often contains a LinearLayout, which in turn contains the Views that make up the form.

Don’t confuse ScrollView with ListView. Both Views present the user with a scrollable set of Views, but the ListView is designed to display a set of similar things, such as the cheeses in the previous section. The ScrollView, on the other hand, allows an arbitrary View to scroll vertically. The Android documentation warns that one should never house a ListView within a ScrollView, because that defeats the performance optimizations of a ListView.

A ScrollView is a FrameLayout, which means that it can have only one child View. The most popular View for this purpose is a LinearLayout.

The following layout code from ApiDemos, scroll_view_2.xml, shows how to set up a ScrollView. The XML layout resource is sufficient; this example includes no extra Java code:

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:scrollbars="none">1
    <LinearLayout2
        android:id="@+id/layout"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">

        <TextView3
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="@string/scroll_view_2_text_1"/>

        <Button
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="@string/scroll_view_2_button_1"/>

    </LinearLayout>
</ScrollView>

Here are some of the highlights of the code:

1

The unnamed ScrollView fills the width of the screen and is as tall as it needs to be to contain all of its contents. It has no scrollbars, but that’s not a problem, because scrollbars act only as visual queues in Android; they’re not as important in UIs that scroll by flicking as opposed to mousing.

2

The child view is a LinearLayout.

3

The XML layout file has two controls within the LinearLayout: a TextView and a Button. The Java code that uses this layout creates 63 more buttons, to ensure that the example LinearLayout will be larger than the screen device and big enough to scroll.

The first tab of a TabHost ViewGroup
Figure 11-7. The first tab of a TabHost ViewGroup
The second tab of a TabHost ViewGroup
Figure 11-8. The second tab of a TabHost ViewGroup

TabHost

Most modern UIs provide an interface element that lets the user flip through many pages of information quickly using tabs, with each “screen” of information available when its tab is pressed. Android’s option is the TabHost View. Figures 11-7 through 11-10 show how it operates.

The third tab of a TabHost ViewGroup
Figure 11-9. The third tab of a TabHost ViewGroup
The fourth tab of a TabHost ViewGroup
Figure 11-10. The fourth tab of a TabHost ViewGroup

Android enables the developer to choose between three different approaches for setting the tab’s content. The developer can:

  • Set the content of a tab to an Intent. Figures 11-7 and 11-9 use this method.

  • Use a TabContentFactory to create the tab’s content on-the-fly. Figure 11-8 uses this method.

  • Retrieve the content from an XML layout file, much like that of a regular Activity. Figure 11-10 uses this method.

We’ll examine each of these possibilities using a modified Activity from the ApiDemos application. The fourth tab is not part of the ApiDemos, but combines some other TabHost demonstration Activities in ApiDemos.

Let’s start by looking at the tabs4.xml layout file (Example 11-7).

Example 11-7. Layout file for TabHost (tabs4.xml)
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <TextView android:id="@+id/view4"
        android:background="@drawable/green"1
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:text="@string/tabs_4_tab_4"/>2

</FrameLayout>

Here are some of the highlights of the code:

1

Defines a TextView view with an id of view4. We’ll insert the TextView into a tab in our Java code. Notice the nice green background for this tab body.

2

The referenced string is simply tab4.

And now we’ll dissect the Java code that produces the tabs (Example 11-8).

Example 11-8. Java for TabHost
public class Tabs4 extends TabActivity implements TabHost.TabContentFactory {1

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final TabHost tabHost = getTabHost();2

        LayoutInflater.from(this).inflate(R.layout.tabs4, tabHost.getTabContentView(), 
          true);3

        tabHost.addTab(tabHost.newTabSpec("tab1")
                .setIndicator("intent")4
                .setContent(new Intent(this, List1.class)));5

        tabHost.addTab(tabHost.newTabSpec("tab2")
                .setIndicator("factory", 
                  getResources().getDrawable(R.drawable.star_big_on))6
                .setContent(this));7

        tabHost.addTab(tabHost.newTabSpec("tab3")
                .setIndicator("destroy")
                .setContent(new Intent(this, Controls2.class)8
                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)));9

        tabHost.addTab(tabHost.newTabSpec("tab4")
                .setIndicator("layout")
                .setContent(R.id.view4));10
    }

    public View createTabContent(String tag) {11
        final TextView tv = new TextView(this);
        tv.setText("Content for tab with tag " + tag);
        return tv;
    }
}

Here are some of the highlights of the code:

1

To implement tabs, you need to extend TabActivity instead of just Activity. This gives you all the tab functionality.

2

The tabHost variable allows you to define the tabs and their contents.

3

This basically says “using the LayoutInflater from my current Context, inflate the XML layout referenced by R.layout.tabs4 into the content section of the tabHost.” Whew. As mentioned before, XML layout files are normally inflated automatically when setContentView runs. However, in this case the XML layout must be instantiated manually. Note that this XML layout is used only in the fourth tab.

4

Sets up the first tab (Figure 11-7). The title is arbitrary, but we’ve called this tab intent as documentation that its contents are an Intent.

5

Set the content of the first tab to the List1.class in this application. This simply brings up the referenced class in the tab. This is a slick way to make the contents of a regular application visible inside a tab.

6

Now we’re setting up the second tab (Figure 11-8). This is how you put an image on a tab face.

7

This tab’s contents are filled in by a factory method in this class. Notice that the class implements the TabHost.TabContentFactory interface.

8

Set the content for the third tab (Figure 11-9) from an Intent. Using an Intent here is similar to navigating from one Activity in your application to another by using an intent. However, using tabs, the user can navigate back and forth between separate parts of your application quickly and easily.

9

Adding this flag to the tabHost creates a new instance of the View each time it is displayed. In the case of the demo, all changes to the UI will be lost if you navigate away from the tab and then back to it.

10

This tab displays the TextView from the XML layout item referenced by R.id.view4. The TextView was set up in item 1 of Example 11-7.

11

This is the factory method that creates the view for the second tab. The factory must return a view that the tab will use as its content. In this case, we create a very simple TextView that displays the tag associated with the tab.

Layouts

Layouts are Android’s solution to the variety of screens that come on Android devices: they can have different pixel densities, different dimensions, and different aspect ratios. Typical Android devices, such as the HTC G1 mobile phone, even allow changing the screen orientation (portrait or landscape) while applications are running, so the layout infrastructure needs to be able to respond on the fly. Layouts are intended to give developers a way to express the physical relationship of Views as they are drawn on the screen. As Android inflates the Layout, it uses the developer requests to come up with a screen layout that best approximates what the developer has asked for.

Looking a little deeper, layouts in Android are in the form of a tree, with a single root and a hierarchy of Views. Look back at any of the XML Layout files in the previous section and you’ll see that the XML tags create just such a hierarchy, with a screen Layout as the root of the tree. Each View in the tree is termed the parent of the Views it contains and the child of the View that contains it. Layout is a two-pass process:

Measure pass

Traversing the tree from the root, each View in the layout records its dimensional request—in other words, how much vertical height and horizontal width it needs to display itself in the final display.

Layout pass

Again traversing the tree from the root, each parent View uses the available layout information to position its children as requested. If the requests can’t be followed explicitly, Android does its best to make everything fit on the screen. If there are no requests given, it uses a default set of layout parameters. Each parent can pass layout information on to its children, telling them where they are positioned and what screen dimensions they have been granted (they might get less than they requested).

A Layout is a View itself, so there’s nothing wrong with having multiple Layouts in a single layout XML file—they just have to be arranged in a hierarchy. So it’s perfectly valid to have a vertical LinearLayout that includes a TableLayout as one of its rows. You’ll learn a lot more about layouts in Chapter 12.

Frame Layout

The Frame Layout is sort of a null layout specification. It reserves space on the screen for a single View to be drawn, and the View is always located at the upper left of the space. There is no way to specify a different location for the View, and there can be only one View in the Layout. If more than one View is defined in the layout file, they are just drawn on top of each other, all pinned to the upper-left corner.

LinearLayout

LinearLayouts are used extensively in Android applications, and we used them in example code earlier. A LinearLayout asks that the contained Views be layed out as either a series of rows (vertical LinearLayout) or a series of columns (horizontal LinearLayout). In a vertical LinearLayout, all the rows are the same width (the width of the widest child). In a horizontal LinearLayout, there is one row of Views, all the same height (the height of the tallest child).

Figure 11-11 shows an example of a vertical LinearLayout, and Figure 11-12 is an example of a horizontal one. Both have EditText Views as children. Example 11-9 shows the XML resource file that produces the vertical layout, and Example 11-10 shows the file that created the horizontal one.

Example 11-9. Vertical LinearLayout resource 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"
    >
<EditText
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="EditText1"
    />
<EditText
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="EditText2"
    />
<EditText
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="EditText3"
    />
<EditText
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="EditText4"
    />
</LinearLayout>
Vertical LinearLayout
Figure 11-11. Vertical LinearLayout
Example 11-10. Horizontal LinearLayout resource file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<EditText
    android:layout_width="wrap_content"
    android:layout_height="fill_parent"
    android:text="E1"
    />
<EditText
    android:layout_width="wrap_content"
    android:layout_height="fill_parent"
    android:text="E2"
    />
<EditText
    android:layout_width="wrap_content"
    android:layout_height="fill_parent"
    android:text="E3"
    />
<EditText
    android:layout_width="wrap_content"
    android:layout_height="fill_parent"
    android:text="E4"
    />
</LinearLayout>
Horizontal LinearLayout
Figure 11-12. Horizontal LinearLayout

The horizontal layout might not look exactly as you would think: how come E4 is narrower than the other three? The answer is that there is a default minimum width for an EditText. If you build and run the horizontal example and type something into EditText E1, you’ll see that it expands in width as the line gets longer, which is just what we asked for with android:layout_width="wrap_content".

In addition to the usual dimensional parameters for child Views (width, height, padding), you can include a weight for each child (attribute android:layout_weight=;weight). The weight tells the layout manager how you want to use unfilled space, and defaults to a value of 0. If you specify children with weights greater than zero, the layout manager will allocate unused space to each child in proportion to its weight.

Figure 11-13 shows an example of a LinearLayout containing four EditTexts. The first two have no weights assigned. EditText3 has a weight of 1 and EditText4 has a weight of 2. The effect is to make EditText4 twice as big as EditText3, while EditText1 and EditText2 just split whatever space the layout leaves over.

Weighted LinearLayout
Figure 11-13. Weighted LinearLayout

TableLayout

A TableLayout is just what you’d expect: it lays out the included Views in the form of a table (similar to an HTML table). We can create a table of TextViews to show how you would create that kind of screen for an application. Here’s an example TableLayout XML file:

<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  android:id="@+id/tblJobs"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    >
     <TableRow
          android:layout_width="fill_parent"
          android:layout_height="wrap_content">
            <Button android:text="Cell 11"
            android:id="@+id/btnCel11"
            android:layout_width="20dip"
            android:layout_height="wrap_content"
            />
      <TextView
      android:id="@+id/txtCell12"
      android:layout_width="20dip"
      android:layout_height="wrap_content"
      android:text="Cell 12"
      />
      <TextView
      android:id="@+id/txtCell13"
      android:layout_width="20dip"
      android:layout_height="wrap_content"
      android:text="Cell 13"
      />
      <TextView
      android:id="@+id/txtCell14"
      android:layout_width="20dip"
      android:layout_height="wrap_content"
      android:text="Cell 14"
      />
     </TableRow>
     <TableRow
          android:layout_width="fill_parent"
          android:layout_height="wrap_content">
            <Button android:text="Cell 21"
            android:id="@+id/btnCo21"
            android:layout_width="80dip"
            android:layout_height="wrap_content"
            />
      <TextView
      android:id="@+id/txtCell22"
      android:layout_width="80dip"
      android:layout_height="wrap_content"
      android:text="Cell 22"
      />
      <TextView
      android:id="@+id/txtCell23"
      android:layout_width="80dip"
      android:layout_height="wrap_content"
      android:text="Cell 23"
      />
      <TextView
      android:id="@+id/txtCell24"
      android:layout_width="80dip"
      android:layout_height="wrap_content"
      android:text="Cell 24"
      />
     </TableRow>
</TableLayout>

Figure 11-14 shows the resulting layout on the emulator screen.

TableLayout
Figure 11-14. TableLayout

The structure of the XML file is pretty evident: the TableLayout tags contain a list of TableRows that in turn contain the Views you want to appear on each line of the table. Notice that the layout_width values are different in the two rows—all the widths in the first row are specified as 20dip, whereas the widths in the second row are specified as 28dip—yet the columns line up on the screen. To preserve the look of a table, Android makes each column as wide as the widest cell in that column.

Of course, the cells are addressable from your Java code, and you can add rows programmatically to the table, if that’s what your application needs to do.

AbsoluteLayout

An AbsoluteLayout puts views on the screen wherever you tell it to. It doesn’t try to resize anything, and it doesn’t try to line anything up; it just puts things where it’s told. You might think that it would be an easy type of layout to use, since you don’t have to second-guess how the layout manager is going to rearrange things on your screen, but in practice the use of AbsoluteLayout is a bad idea for almost all applications. You usually want your application to run on as many Android devices as possible, and the strength of the Android layout manager is that it will automatically adapt your screen layout from device to device. AbsoluteLayout bypasses most of the layout manager, and while your application may look perfect on the device you used for development, the odds are very good that it will look terrible on other Android devices.

That warning aside, let’s take a look at an AbsoluteLayout XML file:

<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout 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="Upper Left"
    android:layout_x="0.0px"
    android:layout_y="0.0px"
    />
<TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Middle"
    android:layout_x="140.0px"
    android:layout_y="200.0px"
    />
<TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Lower Right"
    android:layout_x="240.0px"
    android:layout_y="400.0px"
    />
</AbsoluteLayout>

As with any dimension in a layout file, the positions can be expressed in pixels (px), device-independent pixels (dp), scaled pixels (sp), inches (in), or millimeters (mm), and the dimension has to be a floating-point number. (For more about expressing sizes, see Dimensions in Android in Chapter 4.)

Figure 11-15 shows the resulting screen layout. Obviously, the position (0, 0) is the upper-left corner of the display, and the View is properly flush with the corner. The lower-right corner on the emulator is supposed to be (320, 480), but the View appears to be a little shy of that in both dimensions.

AbsoluteLayout
Figure 11-15. AbsoluteLayout

Just to caution against the use of AbsoluteLayout again, we suggest you try changing the emulator skin to show the screen in landscape mode (enter emulator -skin HVGA-L from a command or terminal window before you run the application), and you can see in Figure 11-16 that the application no longer looks right.

Same AbsoluteLayout in landscape mode
Figure 11-16. Same AbsoluteLayout in landscape mode

RelativeLayout

We’ve used RelativeLayout, often in combination with LinearLayout, throughout the MJAndroid application. The advantage of RelativeLayout is that you can express the relative positioning of the Views in the screen, and the layout manager will do its best to fit them all on the screen in the proper relations. An example follows:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
  <TextView
    android:id="@+id/txtText1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Text1"
    android:gravity="top"1
    android:layout_alignParentRight="true"2
    />
  <TextView
    android:id="@+id/txtText2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Text2"
    android:layout_below="@+id/txtText1"3
    />
  <Button
    android:id="@+id/btnButton1"
    android:layout_width="150dp"
    android:layout_height="wrap_content"
    android:text="Button1"
    android:layout_below="@+id/txtText2"4
  />
  <Button
    android:id="@+id/btnButton2"
    android:layout_width="150dp"
    android:layout_height="100dp"
    android:text="Button2"
    android:layout_toRightOf="@+id/btnButton1"5
    android:layout_alignTop="@+id/btnButton1"6
  />
</RelativeLayout>
1

Lays out Text1 at the top of the screen.

2

Aligns Text1 with the right side of its parent (which is the screen itself).

3

Places Text2 below Text1.

4

Places Button1 below Text2.

5

Places Button2 just to the right of Button1.

6

Aligns the tops of the two buttons.

Figure 11-17 shows what this looks like in portrait mode (the emulator default), and Figure 11-18 shows it in landscape mode. The layout manager has adjusted the arrangements in each case to match the layout hints we gave in the XML.

RelativeLayout in portrait mode
Figure 11-17. RelativeLayout in portrait mode
RelativeLayout in landscape mode
Figure 11-18. RelativeLayout in landscape mode
..................Content has been hidden....................

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