[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.
Comment feed for ongoing:
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]