Wednesday, August 4, 2010

Testing if a User has Clicked One of Your MapView OverlayItems

If you are interested in developing Android applications which have a mapping component, you have undoubtedly heard of Google's mapping API. It's an incredibly usefull API, but has some holes.

For example, they haven't released code to detect if one of your OverlayItems has been clicked by the user. Actually, they've documented that the ItemizedOverlay class has a method called hitTest which does not seem to work properly.

Very quickly, here's a method you can implement in your ItemizedOverlay which will check if any of your OverlayItems have been clicked. The meat of this code is the runHitTest method. Be sure to notice, however, that we've saved a copy of the defaultMarker that was passed in when the SomeItemizedOverlay class was instantiated. This is important, because we need to know what the marker's bounds are. These bounds can be changed. Take a look at boundCenterBottom or boundCenter, for example.

 import java.util.ArrayList;  
   
 import android.app.Activity;  
 import android.graphics.Point;  
 import android.graphics.drawable.Drawable;  
 import android.util.Log;  
 import android.view.MotionEvent;  
 import android.view.View;  
   
 import com.google.android.maps.GeoPoint;  
 import com.google.android.maps.ItemizedOverlay;  
 import com.google.android.maps.MapView;  
 import com.google.android.maps.Projection;  
   
 public class SomeItemizedOverlay extends ItemizedOverlay<SomeOverlay> {  
        
      private ArrayList<SomeOverlay> mOverlays = new ArrayList<SomeOverlay>();  
      private Activity mContext;  
      private Drawable  mMarker;  
        
      private SomeItemizedOverlay(Drawable defaultMarker) {  
           super(boundCenterBottom(defaultMarker));  
           mMarker = boundCenterBottom(defaultMarker);  
      }  
   
      public SomeItemizedOverlay(Drawable defaultMarker, Activity ctx) {  
           super(boundCenterBottom(defaultMarker));  
           mContext = ctx;  
           mMarker = boundCenterBottom(defaultMarker);  
      }  
        
      @Override  
      protected SomeOverlay createItem(int i) {  
           return mOverlays.get(i);  
      }  
   
      @Override  
      public int size() {  
           return mOverlays.size();  
      }  
        
      public void addOverlay(SomeOverlay overlay) {  
           mOverlays.add(overlay);  
           populate();  
      }  
   
      public boolean onTap(GeoPoint p, MapView v) {  
         // Create and add an OverlayItem  
      }  
        
      public boolean onTouchEvent(MotionEvent e, MapView v) {  
           return runHitTest(e,v);  
      }  
   
      private boolean runHitTest(MotionEvent e, MapView v) {  
           if( e.getAction() != MotionEvent.ACTION_DOWN )  
                return false;  
             
           int x = new Float(e.getX()).intValue();  
           int y = new Float(e.getY()).intValue();  
             
           Projection proj = v.getProjection();  
                       
           for(int i = 0; i < mOverlays.size(); i++) {  
                // We need to translate the geographical location  
                // of this particular OverlayItem to it's corresponding  
                // on-screen pixel location.  
                GeoPoint gpt = mOverlays.get(i).getPoint();  
                Point  loc = proj.toPixels(gpt, null);  
                  
                // Now we need to translate the clicked pixel (x,y)  
                // to this OverlayItem's coordinate frame.  
                int x_ovrly = x - loc.x;  
                int y_ovrly = y - loc.y;  
                  
                // Now we need to check if the translated point is in  
                // the Drawable used to mark this OverlayItem.  
                if( mMarker.getBounds().contains(x_ovrly, y_ovrly) ) {  
                     Log.v("MyApplication", "hit item: " + i);  
                     return true;  
                }  
           }  
           return false;  
      }  
 }  
   

I hope you find this useful!

Edit: You could alternately use this version of runHitTest(). It responds only to taps, where the other version responded to any gesture that began on an instance of SomeOverlay.

      private boolean runHitTest(MotionEvent e, MapView v) {  
           // Only handle the event if it truly represents a tap.  
           if( ! (e.getAction() == MotionEvent.ACTION_UP && mInteractionState == MotionEvent.ACTION_DOWN) ) {  
                mInteractionState = e.getAction();  
                return false;  
           } else {  
                mInteractionState = e.getAction();  
           }  
   
           int x = new Float(e.getX()).intValue();  
           int y = new Float(e.getY()).intValue();  
             
           Projection proj = v.getProjection();  
                       
           for(int i = 0; i < mOverlays.size(); i++) {  
                // We need to translate the geographical location  
                // of this particular OverlayItem to it's corresponding  
                // on-screen pixel location.  
                VicinityOverlay ovrly = mOverlays.get(i);   
                GeoPoint gpt = ovrly.getPoint();  
                Point  loc = proj.toPixels(gpt, null);  
                  
                // Now we need to translate the clicked pixel (x,y)  
                // to this OverlayItem's coordinate frame.  
                int x_ovrly = x - loc.x;  
                int y_ovrly = y - loc.y;  
                  
                // Now we need to check if the translated point is in  
                // the Drawable used to mark this OverlayItem.  
                if( mMarker.getBounds().contains(x_ovrly, y_ovrly) ) {  
                     Log.v("MyApplication", "hit item: " + i);  
                     return true;  
                }  
           }  
           return false;  
      }  

Where mInteractionState is a private int in the SomeItemizedOverlay class.

No comments:

Post a Comment