Previous section   Next section

Mobile Phone Handheld Hardware Hardware Rick Rogers John Lombardo O'Reilly Media, Inc. O'Reilly Media Android Application Development, 1st Edition

9.5. Working with MapViews

The MapView encapsulates a lot of very complex mapping software and is available for you in your Android applications—for free. Here are some of the things you can do with a MapView, with only a little programming on your part:

  • Show a street map of any area in the world, with up-to-date mapping information courtesy of Google

  • Change the MapView to show:


    Street view

    Photographs taken at street level for many areas in North America


    Satellite view

    An aerial, photographic view of the area


    Traffic view

    Real-time traffic information superimposed on the map or satellite views

  • Move the map under program control

  • Plot your own graphics in overlays on top of the map

  • Respond to user touch events on the map

9.5.1. MapView and MyLocationOverlay Initialization

The map in MicroJobs has two modes:

  • At startup, and when we select "Current Location" from the Spinner, we want to display a map of our current location, and we want that map to track us as we move around. For this map we will use the MyLocationOverlay class.

  • When we select a specific location from the Spinner, we want to display a map of that location, turn off location updates, and not track movement.

Let's look again at the code in MicroJobs.java that initializes the MapView and the MyLocationOverlay that tracks our current location:

@Override
 public void onCreate(Bundle savedInstanceState) {

...

     mvMap = (MapView) findViewById(R.id.mapmain);

     // get the map controller
     final MapController mc = mvMap.getController();

     mMyLocationOverlay = new MyLocationOverlay(this, mvMap);
     mMyLocationOverlay.runOnFirstFix(
         new Runnable() {
             public void run() {
                 mc.animateTo(mMyLocationOverlay.getMyLocation());
                 mc.setZoom(16);
             }
         });

  Drawable marker = getResources().getDrawable(R.drawable.android_tiny_image);
  marker.setBounds(0, 0, marker.getIntrinsicWidth(), marker.getIntrinsicHeight());
        mvMap.getOverlays().add(new MJJobsOverlay(marker));

        mvMap.setClickable(true);
        mvMap.setEnabled(true);
        mvMap.setSatellite(false);
        mvMap.setTraffic(false);
        mvMap.setStreetView(false);

        // start out with a general zoom
        mc.setZoom(16);
...
    /**
     * Required method to indicate whether we display routes
     */
    @Override
    protected boolean isRouteDisplayed() { return false; }

Here are some of the highlights of the code:

MyLocationOverlay encapsulates a wealth of location and mapping code. In our single call to the constructor we:

  • Ask Android to figure out what location providers are available in our environment (GPS, Cell ID, triangulation).

  • Connect to the "best" of those location providers.

  • Ask the location provider to send us periodic location updates as our handset moves.

  • Link to routines that will automatically move our map as needed to track any changes in location.

MyLocationOverlay also allows us to place a compass rose on the MapView and have that updated as well, but we won't be using that in MJAndroid.

The map attributes set by the code are:


setClickable

We want users to be able to click (tap) on a job to cause MJAndroid to display more detail about that job, so we set this to true.


setEnabled

This method is actually inherited from android.view.View. Google doesn't tell us exactly what this means in the case of a MapView, but presumably it enables the standard map functions (zooming, panning, etc.).


setSatellite

Setting this flag adds a satellite view from the composite map, whereas clearing the flag removes the view. To start with, we don't want the satellite information on the map.


setTraffic

Similarly, setting or clearing this flag adds or removes current traffic information from the map, respectively. Again, we don't want to start with traffic information on the map.


setStreetView

We don't want street views right now either, although we'll let the user enable them later.

Zooming in Android Maps

Android maps come already equipped with support for zooming in and out. The "i" key zooms in on the map, and the "o" key zooms out. Maps can also zoom in and out under program control, through the MapController.

There are several methods defined for zooming, all via the MapController. Android defines 21 zoom levels for maps. At zoom level 1, the equator of the earth is 256 pixels long. Every step up in zoom level multiplies that by 2. Google warns that the higher-resolution maps are not available worldwide. All of the zoom methods clamp the zoom level to the range 1 through 21 if you ask MapController to go beyond those limits.

The methods that control zoom, along with their parameters, are:


zoomIn

Zooms in one level.


zoomOut

Zooms out one level.


setZoom(int zoomlevel)

Zooms to the given level, restricting it to the range 1 to 21.


zoomInFixing(int xpixel, int ypixel), zoomOutFixing(int xpixel, int ypixel)

Zoom in one level, but keep the given point fixed on the screen. Normally when you zoom in and out, the center of the screen is the only point that stays fixed. These routines let you pick any point on the map to be the fixed point.


zoomToSpan(int latSpanE6, int longSpanE6)

Attempts to zoom so the given span is displayed on the map. What it actually does is select the zoom level that is the closest match for the span requested. The latitude and longitude span parameters are expressed as integers with a value 106 times the actual value in degrees. For instance, a latitude/longitude span of 2.5 degrees by 1.0 degrees would be expressed as zoomToSpan(2500000, 1000000).


9.5.2. Pausing and Resuming a MapActivity

Mobile applications have unique requirements, due mostly to the constrained resources available to execute applications. For now let's focus on MapActivities and talk about a way we can help save battery power. The good news is that Android makes it pretty easy.

In a mobile environment, battery life is everything, and if we're not the application that is currently being displayed, we want to do everything we can to minimize the power we consume. You recall from the discussion of the Android lifecycle (Chapter 1) that when an Activity (such as MicroJobs) starts another Activity (such as MicroJobsList) the new Activity takes over the screen, and the calling Activity gets pushed onto a stack of Activities that are waiting to run. At that time, Android calls the onPause() routine in the calling Activity so it can prepare itself to go into hibernation. At this point in MicroJobs.java (or just about any MapActivity that uses location updates), we want to turn off location updates. Doing so will at least save the cycles devoted to doing the update, and may allow the handset to save even more power by putting the location provider in a quiescent state.

When the called Activity (in our case, MicroJobsList) exits and the calling Activity is popped off the stack and takes control of the screen, the framework calls the onResume method in the calling Activity. In a MapActivity, we want to turn on location updates again when this method is invoked.

In MicroJobs, the onPause and onResume methods are straightforward:

/**
    * @see com.google.android.maps.MapActivity#onPause()
    */
   @Override
   public void onPause() {
       super.onPause();
       mMyLocationOverlay.disableMyLocation();
   }

   /**
    * @see com.google.android.maps.MapActivity#onResume()
    */
   @Override
   public void onResume() {
       super.onResume();
       mMyLocationOverlay.enableMyLocation();
   }

Note that if we'd had a compass rose as part of our MyLocationOverlay, we would have to disable and enable it as well. Otherwise, the system would be updating the direction of the compass rose even when it wasn't being displayed, thereby wasting cycles and battery power.

9.5.3. Controlling the Map with Menu Buttons

We want to give the user the ability to turn on satellite, traffic, and street views of the map. In addition, we'll throw in a few menu buttons to enable zooming and another way of getting to the Jobs List.

Android has a sophisticated set of menu capabilities that includes three types of menus (options, context, and submenus), each with its own capabilities, icon menu buttons, and other advanced features. We just use text-based menu buttons, and so we need to do two things:

  1. Create the menu of buttons that will be displayed.

  2. Catch the menu events and invoke appropriate actions.

The following code creates the menu in MicroJobs.java:

/**
 * Set up menus for this page
 *
 * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
 */
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    boolean supRetVal = super.onCreateOptionsMenu(menu);
    menu.add(Menu.NONE, 0, Menu.NONE, getString(R.string.map_menu_zoom_in));
    menu.add(Menu.NONE, 1, Menu.NONE, getString(R.string.map_menu_zoom_out));
    menu.add(Menu.NONE, 2, Menu.NONE, getString(R.string.map_menu_set_satellite));
    menu.add(Menu.NONE, 3, Menu.NONE, getString(R.string.map_menu_set_map));
    menu.add(Menu.NONE, 4, Menu.NONE, getString(R.string.map_menu_set_traffic));
    menu.add(Menu.NONE, 5, Menu.NONE, getString(R.string.map_menu_show_list));
    return supRetVal;
}

We create menu buttons by overriding the onCreateOptionsMenu method, where we are passed a menu parameter for the Activity's menu. After dutifully allowing the superclass a chance to do what it needs to do, we simply add items (buttons) to the menu using menu.add. The version of menu.add that we've chosen takes four parameters:


int groupid

Android allows you to group menu items so you can quickly change the whole menu at once. We don't have a need for that in MicroJobs, so Menu.NONE says we don't need it.


int itemid

We need a unique identifier for this menu item so we can tell later whether it was picked.


int order

The itemid we defined in the second parameter does not imply order. If we cared about the order in which the items were presented, we'd do that with this parameter. Since we don't care, we use Menu.NONE again.


int titleRes

The ID of the string resource we want to use for the button title. Note that this is an integer, not a string, so the menu strings need to be predefined in string.xml, under the res directory. You recall that Android takes care of compiling the strings in res/strings.xml into a .java file (R.java) that assigns an integer to each string. The getString method retrieves that integer for you (despite the name, the method returns an integer, not a string).

To catch the menu events, we override the onOptionsItemSelected method:

/**
 * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
 */
@Override
public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case 0:
                // Zoom in
                zoomIn();
                return true;
            case 1:
                // Zoom out
                zoomOut();
                return true;
            case 2:
                // Toggle satellite views
                mvMap.setSatellite(!mvMap.isSatellite());
                return true;
            case 3:
                // Toggle street views
                mvMap.setStreetView(!mvMap.isStreetView());
                return true;
            case 4:
                // Toggle traffic views
                mvMap.setTraffic(!mvMap.isTraffic());
                return true;
            case 5:
                // Show the job list activity
                startActivity(new Intent(MicroJobs.this, MicroJobsList.class));
                return true;
        }
        return false;
    }

We are passed the selected MenuItem, and the switch has a case for each button that we defined for the menu. The code for each case is similar to code that we've seen before.

9.5.4. Controlling the Map with the KeyPad

Some users might prefer to control the map through the keypad (generally one "click," versus two "clicks" to cause a Menu event). Enabling this behavior also demonstrates how to respond to KeyPad events in general, so we've added some code to zoom in, zoom out, and back out of the current Activity:

/**
 * @see android.app.Activity#onKeyDown(int, android.view.KeyEvent)
 */
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    switch (keyCode) {
        case KeyEvent.KEYCODE_DPAD_UP: // zoom in
            zoomIn();
            return true;
        case KeyEvent.KEYCODE_DPAD_DOWN: // zoom out
            zoomOut();
            return true;
        case KeyEvent.KEYCODE_BACK: // go back (meaning exit the app)
            finish();
            return true;
        default:
            return false;
    }
}

To catch key down events, we simply override onKeyDown and provide a switch for the different keys that are of interest. In addition to the keycodes you would expect (KEYCODE_A, ...KEYCODE_Z and things like KEYCODE_SPACE, KEYCODE_SHIFT_LEFT, and KEYCODE_SHIFT_RIGHT), Android includes keycodes that may or may not appear on any particular device (e.g., KEYCODE_CAMERA and KEYCODE_VOLUME_UP). A complete set of keycodes can be found at .

          
      Previous section   Next section