Hokascha
Hokascha

Reputation: 2229

Google Maps Android API - How to shift anchor of marker while dragging

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?

Upvotes: 2

Views: 3052

Answers (2)

beebopbogo
beebopbogo

Reputation: 49

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;
    }

Upvotes: 0

Simas
Simas

Reputation: 44118

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.

Upvotes: 1

Related Questions