Android’s MapView with Interactive Overlays

A member of reddit asked a question on /r/androiddev. I figured I would help him out by writing a blog post on the issue.

Android’s MapView allows developers to get use Google Maps rather easily as a View in their project. More documentation can be found on MapView here and here. You will need an API key in order to use MapView. Click here to get one.

I currently utilize MapView in my GPS Fog of War application (that I haven’t worked on in forever). In GPSFoW, I have an Overlay item that you can click on to see current latitude and longitude information. I am going to briefly go over how to implement MapView and get Overlays working in your own Android app.

The first necessary item you will need is a class that extends MapActivity. In GPSFow, I also implement LocationListener, so my view can get GPS information directly. I won’t cover that in this post.

So, make a new class with this header (call it whatever you want).


...
import com.google.android.maps.*;
import android.graphics.drawable.*;
import android.graphics.drawable.Drawable;

    public class Map extends MapActivity
    {
    ...
    }

Great! So now we have a Map class that is all set to handle a MapView with overlays. There’s one problem, however. We don’t have an Overlay yet! So before we get any further, let’s make an Overlay class.

...
import android.app.AlertDialog;
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.MapView;
import com.google.android.maps.OverlayItem;
import android.graphics.drawable.Drawable;
import android.content.Context;
public class MapOverlay extends ItemizedOverlay
{
    private ArrayList<OverlayItem> mOverlays = new ArrayList<OverlayItem>(); // this holds the overlays that are added to this overlay.
    private Context mContext; // this holds the context of this overlay, which is the MapView
     
    public MapOverlay(Drawable arg0, Context context)
    {
         super(boundCenterBottom(arg0));
         mContext = context;
         populate();
    }

    // adds an overlay to our internal list of OverlayItems
    public void addOverlay(OverlayItem item) {
        mOverlays.add(item);
        populate();
    }
         
    @Override
    protected OverlayItem createItem(int i) {
        return mOverlays.get(i);
    }

    @Override
    public int size() {
        return mOverlays.size();
    }
         
    // removes a specified overlay from our internal OverlayItems
    public void removeOverlay(OverlayItem item) {
        mOverlays.remove(item);
    }
         
    private ArrayList<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();

    @Override
    protected boolean onTap(int index){
        try
        {
            OverlayItem item = mOverlays.get(index);
            AlertDialog.Builder dialog = new AlertDialog.Builder(mContext);
            dialog.setTitle(item.getTitle());
            dialog.setMessage(item.getSnippet());
            dialog.show();
            return true;
        }
        catch (Exception ex)
        {
            Context context = mContext;
            CharSequence text = ex.toString() + " ID = MapOverlay onTap"
            int duration = Toast.LENGTH_LONG;
            Toast toast = Toast.makeText(context,text,duration);
            toast.show();
            return false;
        }
    }
}

So, that’s the gist of the MapOverlay class. It’s a good chunk of code, and I’ll try to explain what everything does. The constructor takes a Drawable that represents the Overlay and and context so Android knows where to place the Overlay. ItemizedOverlays allow touch events to occur on them, and so are a perfect object to use in an overlay that needs to handle touch events. populate() ends up being very important, as the application crashed whenever I didn’t have this piece of code in.

Internally, the class holds onto a bunch of OverlayItems internally. While I’ve never really had more than one at a given time, it’s feasible to place multiple OverlayItems into the single Overlay class. They will share the same Drawable as whatever was passed to that particular instance of MapOverlay. For the sake of having multiple different Drawables, I would suggest having multiple MapOverlays instead of multiple OverlayItems inside a single MapOverlay class. The add and remove functions just allow me to specifically remove an overlay item in my Map class.

The real meat and potatoes here is the Overriden onTap function. This is where you can put code to interact with the Overlay when you touch it on the map. In GPSFoW, I set the OverlayItems to have the latitude and longitude as the title and something really basic as the body and show that in the onTap through an AlertDialog. This is where you will put whatever you want to occur when the user touches the Overlay.

Okay, so now to actually implement the thing. Let’s go back to our Map class. We need to add the MapOverlay to it and then add some things to interact with to the map!

...
import com.google.android.maps.*;
import android.graphics.drawable.*;
import android.graphics.drawable.Drawable;
public class Map extends MapActivity
{
    // some things we need throughout the class
    MapView mapView; // the actual MapView where all the cool things happen!
    List<Overlay> mapOverlays; // the overlays contained on the MapView
    Drawable drawable1; // the drawable of the MapOverlay (since you have multiple MapOverlays, we want more than one of these).
    Drawable drawable2;
    MapOverlay mapOverlay; // the first MapOverlay
    MapOverlay mapOverlay2; // the second MapOverlay

    @Override
    public void onCreate(Bundle savedInstanceState) 
    {

         super.onCreate(savedInstanceState);
 
         setContentView(R.layout.map); // I have defined a "map" layout in my resources. I'll go over this later
         mapView = (MapView) findViewById(R.id.mapview); // in your .xml for your resources, you need to define a MapView item. I'll go over this later
         mapView.setBuiltInZoomControls(true); // I'm guessing you want zoom controls
         mapOverlays = mapView.getOverlays(); // Get the overlays from the mapView
         drawable1 = this.getResources().getDrawable(R.drawable.androidmarker); // These are images that I have in my project. Change these as necessary
         drawable2 = this.getResources().getDrawable(R.drawable.busmarker);
         mapOverlay = new MapOverlay(drawable1, this); // initialize and add the overlays to mapView
         mapOverlay2 = new MapOverlay(drawable2, this);
         mapOverlays.add(mapOverlay);
         mapOverlays.add(mapOverlay2);
         
    }
}

Now, I have a bunch of other code in GPSFoW related to actually obtaining and processing location data from the GPS and Coarse Network Location. I haven’t included that here, as it makes the process more involved. Let me know if you want help with this too.

On to the resources I said I would get to later.

I have a map.xml in my layout folder:


<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/map" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent">

    <com.google.android.maps.MapView android:id="@+id/mapview" android:layout_width="fill_parent" android:layout_height="fill_parent" android:clickable="true" android:apiKey="FILL YOUR KEY IN HERE"/>

    <!--
    release key: android:apiKey="This is where I keep my release key, for easy access (different than the debug key, keep that in mind)"/>
    -->
</FrameLayout>

I really hope this was helpful. If you need any additional help or are confused, please let me know and I’ll do my best to assist you. For anyone reading this, if you see mistakes, please let me know. I hate spreading false / inaccurate information or otherwise dangerous (unsafe) code. Thanks guys!

Leave a Reply

  • (will not be published)

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>