Chapter 9

Employing Fancy Widgets and Containers

The widgets and containers covered so far are not only found in many GUI toolkits (in one form or fashion), but also are widely used in building GUI applications—whether web-based, desktop, or mobile. The widgets and containers described in this chapter are a little less widely used, though you will likely find many to be quite useful.

Pick and Choose

With limited-input devices like phones, having widgets and dialogs that are aware of the type of stuff someone is supposed to be entering is very helpful. These elements minimize keystrokes and screen taps, as well as reduce the chance of making some sort of error (e.g., entering a letter somewhere only numbers are expected).

As shown in Chapter 5, EditText has content-aware flavors for entering numbers and text. Android also supports widgets (DatePicker and TimePicker) and dialogs (DatePickerDialog and TimePickerDialog) for helping users enter dates and times.

DatePicker and DatePickerDialog allow you to set the starting date for the selection, in the form of a year, month, and day of month value. Note that the month runs from 0 for January through 11 for December. Each lets you provide a callback object (OnDateChangedListener or OnDateSetListener) where you are informed of a new date selected by the user. It is up to you to store that date someplace, particularly if you are using the dialog, since there is no other way for you to get at the chosen date later.

Similarly, TimePicker and TimePickerDialog let you set the initial time the user can adjust, in the form of an hour (0 through 23) and a minute (0 through 59). You can indicate if the selection should be in 12-hour mode with an AM/PM toggle or in 24-hour mode (what in the United States is thought of as “military time” and in the rest of the world as “the way times are supposed to be”). You can also provide a callback object (OnTimeChangedListener or OnTimeSetListener) to be notified of when the user has chosen a new time, which is supplied to you in the form of an hour and minute.

For example, from the Fancy/Chrono sample project, here's a trivial layout containing a label and two buttons, which will pop up the dialog flavors of the date and time pickers:

<?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/dateAndTime"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
  />
  <Button android:id="@+id/dateBtn"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Set the Date"
     />
  <Button android:id="@+id/timeBtn"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Set the Time"
    />
</LinearLayout>

The more interesting stuff comes in the Java source:

public class ChronoDemo extends Activity {
  DateFormat fmtDateAndTime=DateFormat.
getDateTimeInstance();
  TextView dateAndTimeLabel;
  Calendar dateAndTime=Calendar.
getInstance();
  DatePickerDialog.OnDateSetListener d=new DatePickerDialog.
OnDateSetListener() {
    public void
onDateSet(DatePicker view, int year, int monthOfYear,
                int dayOfMonth) {
      dateAndTime.
set(Calendar.YEAR, year);
      dateAndTime.
set(Calendar.MONTH, monthOfYear);
      dateAndTime.
set(Calendar.DAY_OF_MONTH, dayOfMonth);
      
updateLabel();
    }
  };
  TimePickerDialog.OnTimeSetListener t=new TimePickerDialog.
OnTimeSetListener() {
    public void
onTimeSet(TimePicker view, int hourOfDay,
                         int minute) {
      dateAndTime.
set(Calendar.HOUR_OF_DAY, hourOfDay);
      dateAndTime.
set(Calendar.MINUTE, minute);
      
updateLabel();
    }
  };

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

    Button btn=(Button)
findViewById(R.id.dateBtn);
    btn.setOnClickListener(new View.OnClickListener() {
      public void
onClick(View v) {
        new
DatePickerDialog(ChronoDemo.this,
            d,
            dateAndTime.
get(Calendar.YEAR),
            dateAndTime.
get(Calendar.MONTH),
            dateAndTime.
get(Calendar.DAY_OF_MONTH)).show();
      }
    });

    btn=(Button)
findViewById(R.id.timeBtn);

    btn.
setOnClickListener(new View.OnClickListener() {
      public void
onClick(View v) {
        new
TimePickerDialog(ChronoDemo.this,
                    t,
                    dateAndTime.
get(Calendar.HOUR_OF_DAY),
                    dateAndTime.
get(Calendar.MINUTE),
                    true).
show();
      }
    });

    dateAndTimeLabel=(TextView)
findViewById(R.id.dateAndTime);

    
updateLabel();
  }

  private void
updateLabel() {
    dateAndTimeLabel.
setText(fmtDateAndTime
                             .
format(dateAndTime.getTime()));
  }
}

The model for this activity is just a Calendar instance, initially set to be the current date and time. We pour it into the view via a DateFormat formatter. In the updateLabel() method, we take the current Calendar, format it, and put it in the TextView.

Each button is given an OnClickListener callback object. When the button is clicked, either a DatePickerDialog or a TimePickerDialog is shown. In the case of the DatePickerDialog, we give it an OnDateSetListener callback that updates the Calendar with the new date (year, month, and day of month). We also give the dialog the last-selected date, getting the values from the Calendar. In the case of the TimePickerDialog, it gets an OnTimeSetListener callback to update the time portion of the Calendar, the last-selected time, and a true indicating we want 24-hour mode on the time selector.

With all this wired together, the resulting activity looks like Figures 9–1, 9–2, and 9–3.

image

Figure 9–1. The ChronoDemo sample application, as initially launched

image

Figure 9–2. The same application, showing the date picker dialog

image

Figure 9–3. The same application, showing the time picker dialog

Time Keeps Flowing Like a River

If you want to display the time, rather than have users enter it, you may wish to use the DigitalClock or AnalogClock widgets. These are extremely easy to use, as they automatically update with the passage of time. All you need to do is put them in your layout and let them do their thing.

For example, from the Fancy/Clocks sample application, here is an XML layout containing both DigitalClock and AnalogClock widgets:

<?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"
  >
  <AnalogClock android:id="@+id/analog"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_alignParentTop="true"
     />
  <DigitalClock android:id="@+id/digital"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_below="@id/analog"
     />
</RelativeLayout>

Without any Java code other than the generated stub, we can build this project and get the activity shown in Figure 9–4.

image

Figure 9–4. The ClocksDemo sample application

If you are looking for more of a timer, Chronometer may be of interest. With a Chronometer, you can track elapsed time from a starting point. You simply tell it when to start() and stop(), and possibly override the format string that displays the text. Figure 9–5 shows an example.

image

Figure 9–5. The Views/Chronometer API demo from the Android 2.0 SDK

Making Progress

If you need to be doing something for a long period of time, you owe it to your users to do two things:

  • Use a background thread.
  • Keep them apprised of your progress, lest they think your activity has wandered away and will never come back.

The typical approach to keeping users informed of progress is to display some form of progress bar, or “throbber” (like the animated graphic that appears near the upper-right corner of many web browsers). Android supports this through the ProgressBar widget.

A ProgressBar widget keeps track of progress, defined as an integer, with 0 indicating no progress has been made. You can define the maximum end of the range—the value that indicates progress is complete—via setMax(). By default, ProgressBar starts with a progress of 0, though you can start from some other position via setProgress().

If you prefer your progress bar to be indeterminate, use setIndeterminate(), setting it to true.

In your Java code, you can either positively set the amount of progress that has been made (via setProgress()) or increment the progress from its current amount (via incrementProgressBy()). You can find out how much progress has been made via getProgress().

Since the ProgressBar is tied closely to the use of threads—a background thread doing work, updating the UI thread with new progress information—its use is demonstrated in Chapter 15.

Seeking Resolution

A subclass of ProgressBar is SeekBar. A ProgressBar is an output widget, telling the user how much progress has been made. Conversely, the SeekBar is an input widget, allowing the user to select a value along a range of possible values, as shown in Figure 9–6.

image

Figure 9–6. The Views/SeekBar API demo from the Android 2.0 SDK

The user can drag the thumb or click either side of it to reposition the thumb. The thumb then points to a particular value along a range. That range will be 0 to some maximum value, 100 by default, which you control via a call to setMax(). You can determine the current position via getProgress(), or find out when the user makes a change to the thumb's position by registering a listener via setOnSeekBarChangeListener().

You saw a variation on this theme with the RatingBar example in Chapter 8.

Put It on My Tab

The general Android philosophy is to keep activities short and sweet. If there is more information than can reasonably fit on one screen, albeit perhaps with scrolling, then it perhaps belongs in another activity kicked off via an Intent, as will be described in Chapter 18. However, that can be complicated to set up. Moreover, sometimes there legitimately is a lot of information that needs to be collected to be processed as an atomic operation.

In a traditional UI, you might use tabs to hold the information, such as a JTabbedPane in Java/Swing. In Android, you now have the option of using a TabHost container in much the same way. In this setup, a portion of your activity's screen is taken up with tabs, which, when clicked, swap out part of the view and replace it with something else. For example, you might have an activity with a tab for entering a location and a second tab for showing a map of that location.

Some GUI toolkits refer to tabs as just the things a user clicks to toggle from one view to another. Others refer to tabs as the combination of the clickable buttonlike element and the content that appears when it is chosen. Android treats the tab buttons and contents as discrete entities, which I'll refer to as “tab buttons” and “tab contents” in this section.

The Pieces

You need to use the following items to set up a tabbed portion of a view:

  • TabHost is the overarching container for the tab buttons and tab contents.
  • TabWidget implements the row of tab buttons, which contain text labels and, optionally, icons.
  • FrameLayout is the container for the tab contents. Each tab content is a child of the FrameLayout.

This is similar to the approach that Mozilla's XUL takes. In XUL's case, the tabbox element corresponds to Android's TabHost, the tabs element corresponds to TabWidget, and tabpanels corresponds to FrameLayout.

The Idiosyncrasies

There are a few rules to follow, at least in this milestone edition of the Android toolkit, in order to make the three tab pieces work together:

  • You must give the TabWidget an android:id of @android:id/tabs.
  • If you wish to use the TabActivity, you must give the TabHost an android:id of @android:id/tabhost.

TabActivity, like ListActivity, wraps a common UI pattern (an activity made up entirely of tabs) into a pattern-aware activity subclass. You do not necessarily have to use TabActivity—a plain activity can use tabs as well.

For example, here is a layout definition for a tabbed activity, from Fancy/Tab:

<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/tabhost"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <LinearLayout
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TabWidget android:id="@android:id/tabs"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
     />
    <FrameLayout android:id="@android:id/tabcontent"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent">
      <AnalogClock android:id="@+id/tab1"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_centerHorizontal="true"
       />
      <Button android:id="@+id/tab2"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:text="A semi-random button"
      />
    </FrameLayout>
  </LinearLayout>
</TabHost>

Note that the TabWidget and FrameLayout are immediate children of the TabHost, and the FrameLayout itself has children representing the various tabs. In this case, there are two tabs: a clock and a button. In a more complicated scenario, the tabs could be some form of container (e.g., LinearLayout) with their own contents.

Wiring It Together

The Java code needs to tell the TabHost which views represent the tab contents and what the tab buttons should look like. This is all wrapped up in TabSpec objects. You get a TabSpec instance from the host via newTabSpec(), fill it out, and then add it to the host in the proper sequence.

TabSpec has two key methods:

  • setContent(): Indicates what goes in the tab content for this tab, typically the android:id of the view you want shown when this tab is selected.
  • setIndicator(): Sets the caption for the tab button and, in some flavors of this method, supplies a Drawable to represent the icon for the tab.

Note that tab “indicators” can actually be views in their own right, if you need more control than a simple label and optional icon.

Also note that you must call setup() on the TabHost before configuring any of these TabSpec objects. The call to setup() is not needed if you are using the TabActivity base class for your activity.

For example, here is the Java code to wire together the tabs from the preceding layout example:

package com.commonsware.android.fancy;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TabHost;

public class TabDemo extends Activity {
  @Override
  public void
onCreate(Bundle icicle) {
    super.onCreate(icicle);
    
setContentView(R.layout.main);

    TabHost tabs=(TabHost)
findViewById(R.id.tabhost);

    tabs.
setup();

    TabHost.TabSpec spec=tabs.
newTabSpec("tag1");

    spec.
setContent(R.id.tab1);
    spec.
setIndicator("Clock");
    tabs.
addTab(spec);

    spec=tabs.
newTabSpec("tag2");
    spec.
setContent(R.id.tab2);
    spec.
setIndicator("Button");
    tabs.
addTab(spec);
  }
}

We find our TabHost via the familiar findViewById() method, and then have it setup(). After that, we get a TabSpec via newTabSpec(), supplying a tag whose purpose is unknown at this time. Given the spec, we call setContent() and setIndicator(), and then call addTab() back on the TabHost to register the tab as available for use. Finally, we can choose which tab is the one to show via setCurrentTab(), providing the 0-based index of the tab.

The results are shown in Figures 9–7 and 9–8.

image

Figure 9–7. The TabDemo sample application, showing the first tab

image

Figure 9–8. The same application, showing the second tab

Adding Them Up

TabWidget is set up to allow you to easily define tabs at compile time. However, sometimes, you want to add tabs to your activity during runtime. For example, imagine an e-mail client where individual messages are opened in their own tab, for easy toggling between messages. In this case, you don't know how many tabs you will need or what their contents will be until runtime, when the user chooses to open a message. Fortunately, Android also supports adding tabs dynamically at runtime.

Adding tabs dynamically at runtime works much like the compile-time tabs described in the previous section, except you use a different flavor of setContent()—one that takes a TabHost.TabContentFactory instance. This is just a callback that will be invoked. You provide an implementation of createTabContent(), and use it to build and return the View that becomes the content of the tab.

Let's take a look at an example (Fancy/DynamicTab). First, here is some layout XML for an activity that sets up the tabs and defines one tab, containing a single button:

<?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">
  <TabHost android:id="@+id/tabhost"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TabWidget android:id="@android:id/tabs"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
    />
    <FrameLayout android:id="@android:id/tabcontent"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      android:paddingTop="62px">
      <Button android:id="@+id/buttontab"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:text="A semi-random button"
      />
    </FrameLayout>
  </TabHost>
</LinearLayout>

Now we want to add new tabs whenever the button is clicked. That can be accomplished with the following code:

public class DynamicTabDemo extends Activity {
  @Override
  public void
onCreate(Bundle icicle) {
    super.
onCreate(icicle);
    
setContentView(R.layout.main);

    final TabHost tabs=(TabHost)
findViewById(R.id.tabhost);

    tabs.
setup();

    TabHost.TabSpec spec=tabs.
newTabSpec("buttontab");
    spec.
setContent(R.id.buttontab);
    spec.
setIndicator("Button");
    tabs.
addTab(spec);

    Button btn=(Button)tabs.
getCurrentView().findViewById(R.id.buttontab);

    btn.
setOnClickListener(new View.OnClickListener() {
      public void
onClick(View view) {
        TabHost.TabSpec spec=tabs.
newTabSpec("tag1");

        spec.
setContent(new TabHost.TabContentFactory() {
          public View
createTabContent(String tag) {
            return(new
AnalogClock(DynamicTabDemo.this));
          }
        });
        spec.
setIndicator("Clock");
        tabs.
addTab(spec);
      }
    });
  }
}

In our button's setOnClickListener() callback, we create a TabHost.TabSpec object and give it an anonymous TabHost.TabContentFactory. The factory, in turn, returns the View to be used for the tab—in this case, just an AnalogClock. The logic for constructing the tab's View could be much more elaborate, such as using LayoutInflater to construct a view from layout XML.

Initially, when the activity is launched, we have just the one tab, as shown in Figure 9–9. Figure 9–10 shows all three tabs.

image

Figure 9–9. The DynamicTab application, with the single initial tab

image

Figure 9–10. The DynamicTab application, with three dynamically created tabs

Intents and Views

In the preceding examples, the contents of each tab were set to be a View, such as a Button. This is easy and straightforward, but it is not the only option. You can also integrate another activity from your application via an Intent.

Intents are ways of specifying something you want accomplished, and then telling Android to go find something to accomplish it. Frequently, these are used to cause activities to spawn. For example, whenever you launch an application from the main Android application launcher, the launcher creates an Intent and has Android open the activity associated with that Intent. This whole concept, and how activities can be placed in tabs, is described in Chapter 18.

Flipping Them Off

Sometimes, you want the overall effect of tabs (only some Views visible at a time), but you do not want the actual UI implementation of tabs. Maybe the tabs take up too much screen space. Maybe you want to switch between perspectives based on a gesture or a device shake. Or maybe you just like being different.

The good news is that the guts of the view-flipping logic from tabs can be found in the ViewFlipper container, which can be used in other ways than the traditional tab.

ViewFlipper inherits from FrameLayout, in the same way you use it to describe the innards of a TabWidget. However, initially, the ViewFlipper container just shows the first child view. It is up to you to arrange for the views to flip, either manually by user interaction or automatically via a timer.

For example, here is a layout for a simple activity (Fancy/Flipper1) using a Button and a ViewFlipper:

<?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"
    >
  <Button android:id="@+id/flip_me"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="Flip Me!"
      />
  <ViewFlipper android:id="@+id/details"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:textStyle="bold"
      android:textColor="#FF00FF00"
      android:text="This is the first panel"
    />
    <TextView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:textStyle="bold"
      android:textColor="#FFFF0000"
      android:text="This is the second panel"
    />
    <TextView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:textStyle="bold"
      android:textColor="#FFFFFF00"
      android:text="This is the third panel"
    />
  </ViewFlipper>
</LinearLayout>

Notice that the layout defines three child views for the ViewFlipper, each a TextView with a simple message. Of course, you could have very complicated child views, if you so chose.

Manual Flipping

To manually flip the views, we need to hook into the Button and flip them ourselves when the button is clicked:

public class FlipperDemo extends Activity {
  ViewFlipper flipper;

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

    flipper=(ViewFlipper)
findViewById(R.id.details);

    Button btn=(Button)
findViewById(R.id.flip_me);

    btn.
setOnClickListener(new View.OnClickListener() {
      public void
onClick(View view) {
        flipper.
showNext();
      }
    });
  }
}

This is just a matter of calling showNext() on the ViewFlipper, as you can on any ViewAnimator class.

The result is a trivial activity: click the button, and the next TextView in sequence is displayed, wrapping around to the first after viewing the last, as shown in Figures 9–11 and 9–12.

image

Figure 9–11. The Flipper1 application, showing the first panel

image

Figure 9–12. The same application, after switching to the second panel

Of course, this could be handled more simply by having a single TextView and changing the text and color on each click. However, you can imagine that the ViewFlipper contents could be much more complicated, like the contents you might put into a TabView.

Adding Contents on the Fly

As with the TabWidget, sometimes, your ViewFlipper contents may not be known at compile time. And as with TabWidget, you can add new contents on the fly with ease.

For example, let's look at another sample activity (Fancy/Flipper2), using this 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"
    >
  <ViewFlipper android:id="@+id/details"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
  </ViewFlipper>
</LinearLayout>

Notice that the ViewFlipper has no contents at compile time. Also notice that there is no Button for flipping between the contents—more on this in the next section.

For the ViewFlipper contents, we will create large Button widgets, each containing one of the random words used in many chapters in this book. Then we will set up the ViewFlipper to automatically rotate between the Button widgets, using an animation for transition.

public class FlipperDemo2 extends Activity {
  static String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
                         "consectetuer", "adipiscing", "elit",
                         "morbi", "vel", "ligula", "vitae",
                         "arcu", "aliquet", "mollis", "etiam",
                         "vel", "erat", "placerat", "ante",
                         "porttitor", "sodales", "pellentesque",
                         "augue", "purus"};
  ViewFlipper flipper;

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

    flipper=(ViewFlipper)
findViewById(R.id.details);

    flipper.
setInAnimation(AnimationUtils.loadAnimation(this,
                                        R.anim.push_left_in));
    flipper.
setOutAnimation(AnimationUtils.loadAnimation(this,
                                        R.anim.push_left_out));

    for (String item : items) {
      Button btn=new
Button(this);

      btn.
setText(item);

      flipper.
addView(btn,
                     new ViewGroup.LayoutParams(
                             ViewGroup.LayoutParams.FILL_PARENT,
                             ViewGroup.LayoutParams.FILL_PARENT));
    }

    flipper.
setFlipInterval(2000);
    flipper.
startFlipping();
  }
}

After getting our ViewFlipper widget from the layout, we first set up the “in” and “out” animations. In Android terms, an animation is a description of how a widget leaves (out) or enters (in) the viewable area. Animations are resources, stored in res/anim/ in your project. For this example, we are using a pair of animations supplied by the SDK samples, available under the Apache 2.0 license. As their names suggest, widgets are “pushed” to the left, either to enter or leave the viewable area.

NOTE: Animation is a complex beast. I cover it in my book The Busy Coder's Guide to Advanced Android Development (CommonsWare LLC, 2009).

Automatic Flipping

After iterating over the funky words, turning each into a Button, and adding the Button as a child of the ViewFlipper, we set up the flipper to automatically flip between children (flipper.setFlipInterval(2000);) and to start flipping (flipper.startFlipping();).

The result is an endless series of buttons. Each appears and then slides out to the left after 2 seconds, being replaced by the next button in sequence, wrapping around to the first after the last has been shown. Figure 9–13 shows an example.

image

Figure 9–13. The Flipper2 application, showing an animated transition

The auto-flipping ViewFlipper is useful for status panels or other situations where you have a lot of information to display, but not much room. The caveat is that, since it automatically flips between views, expecting users to interact with individual views is dicey, because the view might switch away partway through their interaction.

Getting in Someone’s Drawer

For a long time, Android developers yearned for a sliding-drawer container that worked like the one on the home screen, containing the icons for launching applications. The official implementation was in the open source code but was not part of the SDK, until Android 1.5, when the developers released SlidingDrawer for others to use.

Unlike most other Android containers, SlidingDrawer moves, switching from a closed to an open position. This puts some restrictions on which container holds the SlidingDrawer. It needs to be in a container that allows multiple widgets to sit atop each other. RelativeLayout and FrameLayout satisfy this requirement; FrameLayout is a container purely for stacking widgets atop one another. On the flip side, LinearLayout does not allow widgets to stack (they fall one after another in a row or column), and so you should not have a SlidingDrawer as an immediate child of a LinearLayout.

Here is a layout showing a SlidingDrawer in a FrameLayout, from the Fancy/DrawerDemo project:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#FF4444CC"
    >
  <SlidingDrawer
    android:id="@+id/drawer"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:handle="@+id/handle"
    android:content="@+id/content">
    <ImageView
      android:id="@id/handle"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/tray_handle_normal"
    />
    <Button
      android:id="@id/content"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      android:text="I'm in here!"
    />
  </SlidingDrawer>
</FrameLayout>

The SlidingDrawer should contain two things:

  • A handle, frequently an ImageView or something along those lines, such as the one used here, pulled from the Android open source project
  • The contents of the drawer itself, usually some sort of container, but a Button in this case

Moreover, SlidingDrawer needs to know the android:id values of the handle and contents, via the android:handle and android:content attributes, respectively. This tells the drawer how to animate itself as it slides open and closed.

Figure 9–14 shows what the SlidingDrawer looks like closed, using the supplied handle, and Figure 9–15 shows it open.

image

Figure 9–14. A SlidingDrawer, closed

image

Figure 9–15. A SlidingDrawer, open

As you might expect, you can open and close the drawer from Java code, as well as via user touch events (which are handled by the widget, so that's not something you need to worry about). However, you have two sets of these methods: ones that take place instantaneously (open(), close(), and toggle()) and ones that use the animation (animateOpen(), animateClose(), animateToggle()).

You can lock() and unlock() the drawer; while locked, the drawer will not respond to touch events.

You can also register three types of callbacks if you wish:

  • A listener to be invoked when the drawer is opened
  • A listener to be invoked when the drawer is closed
  • A listener to be invoked when the drawer is “scrolled” (i.e., the user drags or flings the handle)

For example, the launcher's SlidingDrawer toggles the icon on the handle from open to closed to “delete” (if you long-tap something on the desktop). It accomplishes this, in part, through callbacks like these.

SlidingDrawer can be vertical or horizontal. Note, though, that it keeps its orientation despite the screen orientation. In other words, if you rotate the Android device or emulator running DrawerDemo, the drawer always opens from the bottom—it does not always stick to the original side from which it opened. This means that if you want the drawer to always open from the same side, as the launcher does, you will need separate layouts for portrait versus landscape, a topic discussed in Chapter 20.

Other Good Stuff

Android offers AbsoluteLayout, where the contents are laid out based on specific coordinate positions. You tell AbsoluteLayout where to place a child in precise x and y coordinates, and Android puts it that location, no questions asked.

On the plus side, AbsoluteLayout gives you precise positioning. On the minus side, it means your views will look right only on screens of a certain dimension, or you will need to write a bunch of code to adjust the coordinates based on screen size. Since Android screens might run the gamut of sizes, and new sizes crop up periodically, using AbsoluteLayout could get quite annoying.

NOTE: AbsoluteLayout is officially deprecated, meaning that while it is available to you, its use is discouraged.

Android also has the ExpandableListView. This provides a simplified tree representation, supporting two levels of depth: groups and children. Groups contain children; children are “leaves” of the tree. This requires a new set of adapters, since the ListAdapter family does not provide any sort of group information for the items in the list.

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

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