The following tutorial addresses how to perform ‘hit’ testing for user ‘clicks’ in a View. By hit testing, we mean the ability to determine when a user’s selection of a specific Point in a View overlaps with a region that we are monitoring for further action.

In other words at the end of this MapView tutorial, your users will be able to click on any icon that you draw onto the map, and you’ll be able to take whatever action you like such as displaying a transparent popup window (as we do in the tutorial).

Here’s what the final result will look like:

Screenshot of Tutorial 2 results

We’ll assume you already know how to add a MapView to a layout and create an Overlay and will jump right into how to test whether a user selection ‘hit’ one of those mapped icons.

Our icons are rendered by an extension of Overlay we have named MapLocationOverlay which has 2 primary methods called during an Overlay.draw(). We’ll go through each of these in detail:

drawMapLocations(canvas, mapView, shadow);
drawInfoWindow(canvas,
mapView, shadow);

More important perhaps, we’ll discuss the following method which performs the hit testing of each user tap on the screen.

isHitMapLocation(mapView,point);

Starting with locations on our MapView

We start by creating a class, MapLocation, to store our map location name, latitude, & longitude. Four instances of MapLocation are created as shown in the screenshot of San Francisco above:

mapLocations = new ArrayList<MapLocation>();
mapLocations.add(new MapLocation(“North Beach”,37.799800872802734,-122.40699768066406));
mapLocations.add(new MapLocation(“China Town”,37.792598724365234,-122.40599822998047));
mapLocations.add(new MapLocation(“Fisherman’s Wharf”,37.8091011047,-122.416000366));
mapLocations.add(new MapLocation(“Financial District”,37.79410171508789,-122.4010009765625));

These map locations will be drawn to the MapView and used for testing user clicks.

Drawing Map Locations

Before we can test for users clicking our icon, we need to first learn how those icons are drawn to the screen. Once you are comfortable setting the screen coordinates for drawing of your icon, it will be simple to test those same coordinates for ‘hits’ (user clicks” on that icon).

Screen coordinates start at (0,0) in the upper-left and end at the bottom-right (screenWidth,screenHeight) of our screen. To draw our location’s icon, we must first know how to translate from the latitude/longitude coordinates of our map location to these x & y screen coordinates. Android provides this function for us via the Projection class which is available from the MapView passed in Overlay’s draw() method: MapView.getProjection()

public void draw(Canvas canvas, MapView mapView, boolean shadow)

To use Projection, simply pass an int[2] to Projection along with our location’s latitude/longitude as a Point. The projection does its magic and returns the screen coordinates of our map location.

int[] screenCoords = new int[2];
mapView.getProjection().getPointXY(testLocation.getPoint(), screenCoords);

As we will be drawing a bitmap balloon icon to the screen, we must ensuring that the bottom middle of our icon is directly on top of our location’s latitude & longitude screen coordinates (as shown in the image below). This accurate positioning of our icon will be key to hit testing later on.

Screen coordinates of icon

To draw the balloon icon then, we call drawBitmap() and ensure the top/left of our icon is properly offset.

canvas.drawBitmap(icon, screenCoords[0] – icon.width()/2, screenCoords[1] – icon.height(),null);

And that’s it, our icons is now properly drawn on the screen. Now we need to perform ‘hit’ tests for user interaction with these icons.

Listening for Map Taps & Then Testing for ‘Hits’

User taps on the MapView are captured by overriding Overlay’s onTouchEvent() method and then testing for overlap with our icons’ locations on the screen. If a hit occurs and new information popup displayed (or a prior information popup removed), then we invalidate the map so Overlay.draw() is called.

@Override
public boolean onTouchEvent(MotionEvent event, MapView mapView) {

// Store whether prior popup was displayed so call invalidate() to remove it if necessary.
boolean isRemovePriorPopup = selectedMapLocation != null;

// Next test whether a new popup should be displayed
selectedMapLocation = getHitMapLocation(mapView,event);
if ( isRemovePriorPopup || selectedMapLocation != null) {

mapView.invalidate();

}

// Lastly return true if we handled this onTap()
return selectedMapLocation != null;

}

So here’s the real point of this tutorial…how do we match the screen coordinates that the user clicks to the latitude & longitude of our icon on the map?

Just as we determined the location of our map for drawing on the screen, we will now create a Rectangle to represent that drawn icon and use the Rectangle.contains() method to test whether the user’s MotionEvent occurred within that Rectangle.

private MapLocation getHitMapLocation(MapView mapView, MotionEvent    event) {

// Track which MapLocation was hit…if any
MapLocation hitMapLocation = null;

RectF hitTestRecr = new RectF();
int[] screenCoords = new int[2];
Iterator<MapLocation> iterator = mapView.getMapLocations().iterator();
while(iterator.hasNext()) {

MapLocation testLocation = iterator.next();

// As above, translate MapLocation lat/long to screen coordinates
mapView.getProjection().getPointXY(testLocation.getPoint(), screenCoords);

// Use this information to create a ‘hit” testing Rectangle to represent the size
// of our location’s icon at the correct location on the screen.

// As we want the base of our balloon icon to be at the exact location of
//
our map location, we set our Rectangle’s location so the bottom-middle of
//
our icon is at the screen coordinates of our map location (shown above).
hitTestRecr.set(-bubbleIcon.width()/2,-bubbleIcon.height(),bubbleIcon.width()/2,0);

// Next, offset the Rectangle to location of our location’s icon on the screen.
hitTestRecr.offset(screenCoords[0],screenCoords[1]);

// Finally test for match between ‘hit’ Rectangle and location clicked by the user.
// If a hit occurred, then we stop processing and return the result;

if (hitTestRecr.contains(event.getX(),event.getY()) {

hitMapLocation = testLocation;
break;

}

}

return hitMapLocation;

}

And that’s it for hit testing. If a hit occurred in our Rectangle, we track the selected map location and render a popup window above the map location’s icon with the name of the location.

Drawing a Popup Information Window

The following code for displaying a popup window may look complex, but the goal is simple – to set the correct screen coordinates for the information window to display directly above & centered on our location’s icon.

private void drawInfoWindow(Canvas canvas, MapView mapView, boolean shadow) {

// Again get our screen coordinate
int[] selDestinationOffset = new int[2];
mapView.getProjection().getPointXY(selectedMapLocation.getPoint(), selDestinationOffset);

// Setup the info window with the right size & location
int INFO_WINDOW_WIDTH = 125;
int INFO_WINDOW_HEIGHT = 25;
RectF infoWindowRect = new RectF(0,0,INFO_WINDOW_WIDTH,INFO_WINDOW_HEIGHT);
int infoWindowOffsetX = selDestinationOffset[0]-INFO_WINDOW_WIDTH/2;
int infoWindowOffsetY = selDestinationOffset[1]-INFO_WINDOW_HEIGHT-bubbleIcon.height();
infoWindowRect.offset(infoWindowOffsetX,infoWindowOffsetY);

// Draw inner info window
canvas.drawRoundRect(infoWindowRect, 5, 5, getInnerPaint());

// Draw border for info window
canvas.drawRoundRect(infoWindowRect, 5, 5, getBorderPaint());

// Draw the MapLocation’s name
int TEXT_OFFSET_X = 10;
int TEXT_OFFSET_Y = 15;
canvas.drawText(selectedMapLocation.getName(),infoWindowOffsetX+TEXT_OFFSET_X,infoWindowOffsetY+TEXT_OFFSET_Y,getTextPaint());

}

And that’s it. Please let me know of any points that need clarification or that I should expand/improve upon.

Here are the source files for this tutorial as well as all my other tutorials.

Happy coding,

Anthony (Acopernicus)

Tagged with →  
Share →

43 Responses to Android Tutorial 2: “Hit” testing on a View (MapView)

  1. adrian says:

    great tutorial, thanks!

  2. [...] 1: Transparent Panel (Linear Layout) On MapView (Google Map) Tutorial 2: “Hit” testing on a View (MapView) Tutorial 3: Custom Media Streaming with MediaPlayer Possibly related posts: (automatically [...]

  3. [...] 1: Transparent Panel (Linear Layout) On MapView (Google Map) Tutorial 2: “Hit” testing on a View (MapView) Tutorial 3: Custom Media Streaming with MediaPlayer Possibly related posts: (automatically [...]

  4. [...] Journey Home « Welcome to Pocket Journey Android Tutorial 2: “Hit” testing on a View (MapView) [...]

  5. Chris says:

    Any chance you could update this with the current SDK? PixelCalculator doesn’t seem to exist anymore. What is the correct API’s?

    Thanks.

  6. Can you let us know, what changes would be required to upgrade this code to SDK-1.0 .

  7. Biosopher says:

    I’ll try to update the tutorials once soon but will probably be another couple weeks.

    The primary change is that PixelCalculator is no longer present. It has been renamed to Projection and is accessed from MapView.getProjection().

    Let me know if that change solves your problem.

    Anthony

  8. The code of tutorial2.zip attach on this page is not compatible with the code of the site. In the site SDK1.0 in attach SDK m5-r15

  9. [...] Journey Home « Android Tutorial 2: “Hit” testing on a View (MapView) Android Video & Screenshots Released [...]

  10. AndroidMani says:

    how about displaying a button? Like to search nearby, or show a menu specific to the placeholder?

  11. Biosopher says:

    We’ll be updating this tutorial sometime…probably not very soon though. :-(

    For displaying buttons or menus, the best choice is to simply popup an XML-defined layout when the users clicks the appropriate location. See tutorial #1.

    Anthony

  12. hggcrazy says:

    great thank you!

  13. dedachi says:

    I want to integrate a Chat Application on the markers for particular
    locations on Google Maps.

    Eg: If I am going to visit location x, and I have located it on Google
    Maps.
    Now upon clicking on the marker, I should be able to pop up a chat application which can enable me to chat with friends. Is this possible ? Please suggest

  14. adnroidUser says:

    I had seen your example to display the bubble in the google map.

    Without using tutorial2.xml, its possible to call the MapLocationViewer inside the class such that

    //setContentView(R.layout.tutorial2); //To avoid

    alternatively I planned to call
    setContentView((View)new MapLocationViewer(????));

    Question:
    1) How to get the context of the current activty to pass in setContentView((View)new MapLocationViewer(????));
    2) How to get a view from resource id

  15. happyhan says:

    Dear Anthony,
    I have a trouble with mapview overlay. I want to receive
    a sms which contains latitude and longitude,then I hope to draw a icon on mapview according to these location info. Now I have received a sms and parsed it,then transfer it to mapview. but the overlay is not implemented. So would you give me some advice ?

    Best Regards

    happyhan

  16. xxJyen says:

    hi, Anthony.

    how do i combine tutorial2 and tutorial5 together?
    (when hit on the map, show the transparentPanel)

    sorry for the pool English :(

  17. Biosopher says:

    Hi xxJyen,

    Just display the panel when the user touches the screen. I.e. listen for the onTouchEvent and then display the popup.

    Anthony

    • xxJyen says:

      hello!

      i want users click the specific location on map and pop it’s information(transparentPanel).

      i have tried to modify LocationOverlay and LocationViewer, but i cant get the instance of popup, location_name..etc

      becuz the findViewByid method doesnt work in OverLay.
      how could I solve this problem

  18. Biosopher says:

    Hi xxJyen,

    You should reread the two tutorials as all the information is in there. You don’t need to do anything special with LocationOverlay and LocationViewer other than what’s in the tutorial. Not sure why you would need the findViewByid method in OverLay.

    Anthony

  19. xxJyen says:

    hi, Anthony.

    i read the two tutorials again.

    tutorial2 teaches me how to draw on the canvas directly when user clicks the overlayitems.

    tutorial5 teaches me how to show an anime when user clicks the button.

    is that right?

    i want to modify the tutorial2. when users clicks the overlayitems, show the info by pop a transparency panel instead of drawing on the canvas.

    so first i create a layout like this

    // id:xx

    second, i try to change the method of displaying info.
    in the MapLocationOverlay void draw(Canvas, MapView, Boolean)

    // drawMapLocations(canvas, mapView, shadow)
    // drawInfoWindow (canvas, mapView, shadow)
    // i try to display some infomation just like tutorail5, so..

    TransparentPanel popup= (TransparentPanel)findViewById(R.id.xx);


    popup.setVisibility(View.VISIBLE);
    popup.startAnimation( animShow );
    showButton.setEnabled(false);
    hideButton.setEnabled(true);

    but the code cant pass the compile :(

    xxJyen

    • xxJyen says:

      layout

      - com.pocketjourney.view.mapLocationviewer
      - com.pocketjourney.view.TransparentPanel //id:xx
      — Button
      — ImageView
      — TextView

  20. Ben Tseng says:

    Thank you very huge!!
    I tried to display information windows by onTap()
    However,I always failed to remove other windows.

    Thanks again.

  21. sandrar says:

    Hi! I was surfing and found your blog post… nice! I love your blog. :) Cheers! Sandra. R.

  22. Andrew says:

    Your tutorial is amazing. Thanks a lot! I just have one question though.

    I noticed you have a getter for the mapview in your MapLocationViewer which looks to be a java layout. Is there a way to call that getter? I’ve tried using

    mv = (MapView)this.findViewById(R.id.map_location_viewer);

    in the MapActivity class but since MapLocationViewer is a linear layout, it throws an error because it’s not a MapView. I mainly wanted to get the MapView so I can move to the user’s current gps location. Thanks!

  23. Saturne says:

    So Nice
    thank
    I have a question, what’s the differens between onTouchEvent() and onTap()?

  24. Kevin Dorson says:

    HI,
    This tutorial is very useful. I’m stuck in the next STEP
    Could you someone please help me in creating an onclick event for the Gray color label which displays the place name to a seperate activity with the details of that place???
    This is like the next step to this tutorial and I’m stuck here

    • yoyo says:

      hi,
      thanks for your work…it is very nice.

      and i am also face this problem ….and hava no idea
      to do….so please tell if you are working out the prolem.

      thanks again….

  25. karthik says:

    HI,
    I need to pass some data from activity to the class which extends the Linear layout. In this case, how do i achieve that since Intent is working only between Activities to Activities.
    My exact point would be like Data transfer happening between Tutorial2 Class to the Class which extends the Linear Layout..

    ALso, is there any specific reason tht the new class which etends the LinearLayout was defined instead defining everything in the main Tutorial2 class which extends the MapActivity

  26. Steve says:

    Thanks a lot for posting this tutorial, it helped me out more than any other mapview tutorial. :)

  27. kids says:

    I’m very sorry for my poor english , wish u can pardon me…thanks

    After anti-investigation on the latitude and longitude drawn on MAP
    But I want to change into a label, I can choose the label you want to click
    I will show the information stored in SQL there are two activity but I’ve managed to overlay the map
    No way to click on the option had no idea that part of the code may need to modify the link to getHitMapLocation

    I’ve two activity

    This is first one

    http://paste.plurk.com/show/297082/

    and then second one

    http://paste.plurk.com/show/296979/

    I can overlay but no way to onTap

    look like http://www.flickr.com/photos/44995622@N07/4920261104/

    It’s my ideal

    how have any way or proposed for me

  28. [...] Hi. I am having the same proble and its a major headache.lol This link provides another approach you can take. Android Tutorial 2: “Hit” testing on a View (MapView) Pocket Journey [...]

  29. [...] Tutorial 1: Transparent Panel (Linear Layout) On MapView (Google Map) Tutorial 2: “Hit” testing on a View (MapView) [...]

  30. khanhkhanh says:

    thanks you very much. i am starting to learn android, and i need very example similer. thanks …………

  31. [...] that it needs mapview to do it see tutorial below Android Tutorial 2: “Hit” testing on a View (MapView) Pocket Journey __________________ Best regards, Cor de Visser, Netherlands, HTC Magic 2.2.1 Amazing Guitar [...]

  32. jagadeeshbabu says:

    i’m developing the streaming mediaplayer i want search as in albums,tracks,artist 3 should be get all in same functionality.when ever iam searching the mediaplayer

  33. Android says:

    Nice same this only, but with zoom controls and shadows for the marker is on this example http://android-codes-examples.blogspot.com/2011/04/google-map-example-in-android-with-info.html

  34. Sam says:

    i am getting out of memory exception. Here is my logcat report:

    06-17 12:23:21.219: ERROR/dalvikvm-heap(3773): 731520-byte external allocation too large for this process.

    06-17 12:23:21.219: ERROR/dalvikvm(3773): Out of memory: Heap Size=8839KB, Allocated=5230KB, Bitmap Size=15056KB

    06-17 12:23:21.219: ERROR/GraphicsJNI(3773): VM won’t let us allocate 731520 bytes

    06-17 12:23:21.219: DEBUG/AndroidRuntime(3773): Shutting down VM

    06-17 12:23:21.219: WARN/dalvikvm(3773): threadid=1: thread exiting with uncaught exception (group=0x400259f8)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): FATAL EXCEPTION: main

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): java.lang.OutOfMemoryError: bitmap size exceeds VM budget

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at android.graphics.Bitmap.nativeCreate(Native Method)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at android.graphics.Bitmap.createBitmap(Bitmap.java:574)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at com.google.android.maps.ZoomHelper.createSnapshot(ZoomHelper.java:444)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at com.google.android.maps.ZoomHelper.beginZoom(ZoomHelper.java:194)
    0
    6-17 12:23:21.229: ERROR/AndroidRuntime(3773): at com.google.android.maps.MapView$2.onScaleBegin(MapView.java:371)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at android.view.ScaleGestureDetector.onTouchEvent(ScaleGestureDetector.java:248)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at com.google.android.maps.MapView.onTouchEvent(MapView.java:646)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at android.view.View.dispatchTouchEvent(View.java:3765)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:905)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:944)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:944)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:944)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:944)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1701)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1116)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at android.app.Activity.dispatchTouchEvent(Activity.java:2093)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1685)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at android.view.ViewRoot.handleMessage(ViewRoot.java:1802)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at android.os.Handler.dispatchMessage(Handler.java:99)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at android.os.Looper.loop(Looper.java:144)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at android.app.ActivityThread.main(ActivityThread.java:4937)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at java.lang.reflect.Method.invokeNative(Native Method)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at java.lang.reflect.Method.invoke(Method.java:521)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)

    06-17 12:23:21.229: ERROR/AndroidRuntime(3773): at dalvik.system.NativeStart.main(Native Method)

  35. praveen says:

    is there any way to set the Info widow size dynamically as per the Text length in that window?

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Current day month ye@r *