[This is part of the Android Diary.] In particular, drawing and interacting on them. Herewith a very specialized set of notes that might be of interest to anyone programming to Android’s very attractive map API. I’ve learned a lot already, but I’ll try to keep this up-to-date as I become more conversant with the state of the art.

[Ed. Note: I’ve been asked why on earth I’m putting all this time into a non-Sun platform. The answer: I’m now officially in our new Cloud Computing group. The connection between Mobile and Cloud seems painfully obvious.]

Three Big Pieces · Just like it says in all the online write-ups. You need a MapActivity and inside that a MapView and on top of that an Overlay, and the framework takes care of their interactions with each other very nicely.

Do It in XML · I ended up with a bunch of bits and pieces of UI: the map itself, the map’s standard zoom control, and the popup that appears when you click on an entry from a geotagged feed.

It turns out that you can specify them all in XML files in the res/layout directory. If they’re not always visible, who cares? The zoomer takes care of its own visibility, and you can switch your own bits&pieces in and out of sight with view.setVisibility, which the framework handles very intelligently. Here’s my XML:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@+id/container"
	android:layout_width="fill_parent" android:layout_height="fill_parent">
	<com.google.android.maps.MapView
		android:id="@+id/mapper" android:layout_width="fill_parent"
		android:layout_height="fill_parent" android:enabled="true"
		android:clickable="true" android:apiKey="...secret elided..." />
	<LinearLayout android:id="@+id/zoomer"
		android:layout_width="wrap_content" android:layout_height="wrap_content"
		android:layout_alignParentBottom="true"
		android:layout_centerHorizontal="true" />
	<RelativeLayout android:id="@+id/entrypopup"
                android:layout_width="fill_parent"
		android:layout_height="wrap_content" android:padding="10px"
		android:visibility="gone" 
		android:background="#80000000">
		<TextView android:id="@+id/title" android:layout_width="fill_parent"
			android:layout_height="wrap_content" android:text="Entry Popup..."
			android:textColor="#ffffffff"
			android:textSize="18.5sp" />
		<Button android:id="@+id/buttonVisit" android:text="Visit it"
			android:layout_width="wrap_content" android:layout_height="wrap_content"
			android:layout_below="@id/title" android:layout_alignParentRight="true"
			android:layout_marginLeft="10px" />
		<Button android:id="@+id/buttonCancel" android:text="Don’t visit"
			android:layout_width="wrap_content" android:layout_height="wrap_content"
			android:layout_below="@id/title"
			android:layout_toLeftOf="@id/buttonVisit" android:layout_marginLeft="10px" />
	</RelativeLayout>
</RelativeLayout>

Overlays Are Your Friends · If you want to decorate a map and interact with the decorations, ignore all the controlling and eventing calls on MapActivity and MapView, and go straight to Overlay.

You put all your map decorations in overlay.draw and the framework takes care of the rest. Be careful though: your overlay.draw routine is going to get called an insanely huge number of times, so think about being as lazy as possible inside it.

I wasted an immense amount of time dealing with various kind of “Touch” events at various levels before I realized that overlay.onTap just Does The Right Thing and furthermore takes care of the translation between map and screen co-ordinates.

I suspect it will be helpful to show the core of my event-handling logic:

    @Override
    public boolean onTap(GeoPoint point, MapView view) {
        checkTolerance(view);

        // entry here?
        Entry e = finder.find(point.getLatitudeE6(), point.getLongitudeE6());
        if (e != null) {
            
            // OK, they picked an entry; show them the popup.
            visitor.setEntry(e);
            popup.setVisibility(View.VISIBLE);
            TextView text = (TextView) container.findViewById(R.id.title);
            text.setText(e.title());
        }
    
        return super.onTap(point, view);
    }

Pretty straightforward, eh? The only thing that’s even remotely subtle is the checkTolerance call, which computes how “close” a touch to the location of an entry is considered to have hit it. Since the bookkeeping is in geographical rather than screen units, this changes every time you zoom or unzoom.

They say you’re supposed to return true if you’ve “handled” the event, but doing so led to some odd and hard-to-reproduce breakage, letting the superclass have its chance never did.

The Zoomer · You have to do a little bit of extra work if you want people to zoom into and out of your programmatically-displayed maps. I cribbed the answer from StackOverflow; hey, the first time I’ve linked there, I wonder if it’s a trend.

Dimensions · If you want to display something on a map and you know where it is, you need to use mapController.setCenter and mapController.zoomToSpan. Since the zoom levels are discrete, these are fairly blunt instruments and my maps often ended up bigger or smaller than I’d have liked. I think this isn’t a big deal, since the zoomer lets the user solve the problem in the way that best suits their needs.



Contributions

Comment feed for ongoing:Comments feed

From: Emanuele Vulcano (Jan 08 2009, at 05:30)

AAAAA

The Android XML fragment isn't quoted correctly and gets interpreted as XML inside your feed >_< owch.

[link]

From: Tim (Jan 08 2009, at 09:09)

Jeepers, it's 2009 and feed-readers still can't handle a CDATA section. Switched it to individually-escaped XML; should be better now.

[link]

author · Dad · software · colophon · rights
picture of the day
January 08, 2009
· Technology (77 fragments)
· · Mobile (84 more)

By .

I am an employee
of Amazon.com, but
the opinions expressed here
are my own, and no other party
necessarily agrees with them.

A full disclosure of my
professional interests is
on the author page.