2

When dragging a marker on a Google Maps map on Android, the marker is displayed below the finger which makes it almost impossible to precisely place the marker on the map. Is there a way to simply shift the marker by some pixels to the top to make it visible?

I tried moving the marker in onMarkerDragStart() but it immediatly returns to its former position when I continue dragging. I also tried constantly shifting the markers position in onMarkerDrag(). This gives unpredictable results when stopping to drag the marker.

Is there an official way to shift the marker while dragging it or has someone found a way to do so otherwise?

Hokascha
  • 1,709
  • 1
  • 23
  • 41

2 Answers2

1

I got pretty interested in this myself, so I tried a bunch of ways to do it.

I finally figured out that by default Maps API moves the marker to the right by it's width, when it starts being dragged. To fake the [1, 1] anchor after the drag, we need to move the marker left horizontally (x-axis) by it's width, and up vertically (y-axis) by it's height.

This way seems to be working properly and is pretty neat.

Here's an example:

// Get marker dimensions
Bitmap img = BitmapFactory.decodeResource(getResources(), R.drawable.marker);
final int width = img.getWidth();
final int height = img.getHeight();

mMap.setOnMarkerDragListener(new GoogleMap.OnMarkerDragListener() {
    @Override
    public void onMarkerDragStart(final Marker marker) {
        marker.setAnchor(1, 1);
    }

    @Override
    public void onMarkerDrag(Marker marker) {

    }

    @Override
    public void onMarkerDragEnd(final Marker marker) {
        Projection projection = mMap.getProjection();
        // Coordinates to point (in screen pixels)
        Point point = projection.toScreenLocation(marker.getPosition());
        // Move to [1, 1]
        point.x -= width;
        point.y -= height;
        // Point to coordinates
        LatLng coords = projection.fromScreenLocation(point);
        // Reset the anchor and use the coordinates
        marker.setAnchor(0, 0);
        marker.setPosition(coords);
    }
});

// Your marker
BitmapDescriptor bitmapDescriptor =
        BitmapDescriptorFactory.fromResource(R.drawable.marker);

mMap.addMarker(new MarkerOptions()
        .position(mPolylines.get(0).getPoints().get(1000))
        .icon(bitmapDescriptor)
        .anchor(0, 0)
        .draggable(true));

Note: this answer suggests you may need to wait for the map to be fully inflated before you can use the map's projection. You may want to edit accordingly, though I found it works fine without it.

Community
  • 1
  • 1
Simas
  • 43,548
  • 10
  • 88
  • 116
  • Tried that, setting the anchor back to 0,0 makes the marker jump on the map. I don't want to change the anchor (which specifies which position in the marker image should be the actual position of the marker on the map) but to shift the marker up including its anchor when dragging starts. – Hokascha Aug 08 '14 at 17:52
  • Thanks, I tried that, too. You are right, the marker stays at the position but the actual position (in latitude and longitude) is now not as expected. Which is normal as we change the anchor position. – Hokascha Aug 08 '14 at 18:01
  • Maybe the simplest solution would be to use a marker symbol that has its anchor point not in the bottom middle but at the top middle. That way there might be chance of seeing it while dragging ;-) – Hokascha Aug 08 '14 at 18:04
  • @Hokascha Ahh you're right, my *beautiful solution* fails :/ However you do know that the markers are moved a little by the API once the dragging starts, so they aren't really that invisible. – Simas Aug 08 '14 at 18:06
  • Yeah, and it looks like even Google had that in mind. From the onMarkerDragStart() docs: "The marker's location can be accessed via getPosition(); this position may be different to the position prior to the start of the drag because the marker is popped up above the touch point." So, they are popping it up a little bit, but there seems to be no way to control on how far the marker is popped up. Simply using the default marker images show the problem - you can't see the anchor point except your fingers are transparent. – Hokascha Aug 08 '14 at 18:09
  • it still leads to the marker jumping... Lat/Long-Position seems to be better, but now the marker itself jumps to the south... – Hokascha Aug 08 '14 at 18:32
  • Just saw your edit, you are right, I want the marker to stay up and not got back below the finger. Thats the way the API does it except the marker is popped up too few pixels to make the bottom middle (default anchor point) visible. Anyway. thanks for your effort. – Hokascha Aug 08 '14 at 18:39
0

Here are the basic steps and below is the full code. Tested and works great.

First, assume we are using a marker icon bitmap, size 27 pixels by 27 pixels and you want to move the anchor point from the default lower left of the icon to the lower right.
Move the anchor point to the lower right: marker.setAnchor(1,0);
One icon width in the x direction is 27 DPs, but we need to know how many pixels that is.
offsetPoint.x -= MapUtils.convertDpToPx(CreateRouteActivity.this, offsetDPX);
Now we know where the point on the screen is, so just grab the LatLng from that: marker.setPosition(projection.fromScreenLocation(offsetPoint));

If you want to move the icon even further away from your finger, just experiment with the ANCHOR_FACTOR value.

Here is my CreateRouteActivity:

private GoogleMap.OnMarkerDragListener onMarkerDragListener = new GoogleMap.OnMarkerDragListener() {
        float offsetDPX;
        float offsetDPY;
        Projection projection;

        @Override
        public void onMarkerDragStart(Marker marker) {
            // Save the projection every time a marker starts to drag. This keeps the anchors and drop points synced up.
            // If we don't save the projection at the point the marker starts to drag and the user zooms out, the icon pixel size
            // will remain the same but the LatLng distances will have way fewer pixels, messing up the drop point.
            projection = gMap().getProjection();

            // GoogleMap's default anchor is located at the lower left point of the marker icon.
            // An ANCHOR_FACTOR of 1 will move the drag point to the lower right of the icon.
            // An ANCHOR_FACTOR of 2 will move the drag point to the lower right x 2. (The icon will move from under the user's finger to 2 width's above and to the left.)
            float ANCHOR_FACTOR = 1f;
            // 27 is my original icon's width in pixels. 
            // Android increases the pixel size of the image in high-density screens, so make sure you use 27 DPs, not 27 pixels.
            offsetDPX = 27*ANCHOR_FACTOR; 
            offsetDPY = 27*(ANCHOR_FACTOR-1);
            // Always set the anchor by percentage of icon width. 0,0 is lower left. 1,0 is lower right.
            marker.setAnchor(ANCHOR_FACTOR,ANCHOR_FACTOR-1); 
        }

        @Override
        public void onMarkerDrag(Marker marker) {
            // If you want something to happen while you drag, put it here.
        }

        @Override
        public void onMarkerDragEnd(Marker marker) {
            // getPosition returns pixel location
            Point offsetPoint = projection.toScreenLocation(marker.getPosition());
            // We need to offset by the number of DPs, so convert DPs to pixels.
            offsetPoint.x -= MapUtils.convertDpToPx(CreateRouteActivity.this, offsetDPX); 
            offsetPoint.y -= MapUtils.convertDpToPx(CreateRouteActivity.this, offsetDPY);

            // set the marker's LatLng from offsetPoint (in pixels)
            marker.setPosition(projection.fromScreenLocation(offsetPoint));
    };

And some conversion tools in my MapUtils object:

    public static float convertDpToPx(Context context, float dp) {
        return dp * context.getResources().getDisplayMetrics().density;
    }

    public static float convertPxToDp(Context context, float px) {
        return px / context.getResources().getDisplayMetrics().density;
    }
beebopbogo
  • 29
  • 4