Add animation as Marker google maps android

I wanted to add an animation as the "current marker selected" in my map as shown below:

enter image description here

The animation is a LottieAnimation resource (extends from ImageView).

I had been searching and google maps developer docs said that I can only use Bitmap resources as markers. I tried to convert this one as a bitmap but without success.

Uber or Lyft has some animations as markers or for current position, so how they achieve this?

Or is it possible that they are listening to the map movement and update the location of their marker without using the marker property?

Upvotes: 5

Views: 7279

Answers (1)

Tam Huynh
Tam Huynh

Reputation: 2487

As far as I know, Google map Marker and InfoWindow is drawn on the map as a flat bitmap. So you cannot do animation, component touch interaction or even draw a shadow around them officially.

Uber app uses a different layer placed on top of the map (You can try to do some gesture on the map and see that the marker move but a bit delay). It's not easy because you have to catch every map's gesture and animate the overlay view accordingly. I tried that before but it's too much work to do so I left it behind.

Your Pulse effect can be achieved using Circle with ValueAnimator

private @ColorInt int mPulseEffectColor;
private int[] mPulseEffectColorElements;
private ValueAnimator mPulseEffectAnimator;
private Circle mPulseCircle;


private void initPulseEffect() {
    mPulseEffectColor = ContextCompat.getColor(mContext, R.color.pink);
    mPulseEffectColorElements = new int[] {
            Color.red(mPulseEffectColor),
            Color.green(mPulseEffectColor),
            Color.blue(mPulseEffectColor)
    };

    mPulseEffectAnimator = ValueAnimator.ofFloat(0, calculatePulseRadius(current_map_zoom_level));
    mPulseEffectAnimator.setStartDelay(3000);
    mPulseEffectAnimator.setDuration(400);
    mPulseEffectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
}

Calculate radius base on map's zoom level

private static float calculatePulseRadius(float zoomLevel) {
    return (float) Math.pow(2, 16 - zoomLevel) * 160;
}

Register OnCameraIdleListener to listen to map's zoom changed

@Override
public void onCameraIdle() {
    CameraPosition cameraPosition = mMap.getCameraPosition();
    if (mPulseEffectAnimator != null)
        mPulseEffectAnimator.setFloatValues(0, calculatePulseRadius(cameraPosition.zoom));
}

Start the animation

private void startPulseAnimation() {
    if (mPulseCircle != null)
        mPulseCircle.remove();

    if (mPulseEffectAnimator != null) {
        mPulseEffectAnimator.removeAllUpdateListeners();
        mPulseEffectAnimator.removeAllListeners();
        mPulseEffectAnimator.end();
    }

    if (your_marker_coordinate != null) {
        mPulseCircle = mMap.addCircle(new CircleOptions()
                .center(your_marker_coordinate)
                .radius(0).strokeWidth(0)
                .fillColor(mPulseEffectColor));
        mPulseEffectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                if (mPulseCircle == null)
                    return;

                int alpha = (int) ((1 - valueAnimator.getAnimatedFraction()) * 128);
                mPulseCircle.setFillColor(Color.argb(alpha,
                        mPulseEffectColorElements[0], mPulseEffectColorElements[1], mPulseEffectColorElements[2]));
                mPulseCircle.setRadius((float) valueAnimator.getAnimatedValue());
            }
        });
        mPulseEffectAnimator.addListener(new AnimatorListenerAdapter {
            @Override
            public void onAnimationEnd(Animator animation) {
                mPulseEffectAnimator.setStartDelay(2000);
                mPulseEffectAnimator.start();
            }
        });
        mPulseEffectAnimator.start();
    }
}

Explanation:

  • Use calculatePulseRadius to recalculate the radius base on the map zoom level, because the circle's radius is measured by the real-world distance.
  • Use int[] mPulseEffectColorElements to store r,g,b of the circle color so I don't have to recreate the color every frame. I only want to change the alpha value
  • Listen to Animator end and start it again with delay.

This code only applied for 1 circle at a time, multiple circles require harder work but same logic.

Result:

demo

Upvotes: 8

Related Questions