Search code examples
androidgoogle-mapsanimationgmsgroundoverlay

Pulse animation using GroundOverlay


I need to show location A and the location B by pulse animation. I am able to achieve that using the below code. But the problem I am facing is the GroundOverlay changes its size when the zoom level changes. If the location A and B are close to each other(i.e the map zoom in level is high) the radius of the pulse is too big. When I zoom out then it becomes too small.

How can I keep the size of the overlay same irrespective of the zoom level of the map.

The below code is referred from here: Animated Transparent Circle on Google Maps v2 is NOT animating correctly

private void showRipples(LatLng latLng, int color) {
    GradientDrawable d = new GradientDrawable();
    d.setShape(GradientDrawable.OVAL);
    d.setSize(500, 500);
    d.setColor(ContextCompat.getColor(Activity.this, color));
    d.setStroke(0, Color.TRANSPARENT);

    final Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth()
            , d.getIntrinsicHeight()
            , Bitmap.Config.ARGB_8888);

    // Convert the drawable to bitmap
    final Canvas canvas = new Canvas(bitmap);
    d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
    d.draw(canvas);

    // Radius of the circle
    final int radius = getResources().getDimensionPixelSize(R.dimen.ripple_radius);

    // Add the circle to the map
    final GroundOverlay circle = googleMap.addGroundOverlay(new GroundOverlayOptions()
            .position(latLng, 2 * radius).image(BitmapDescriptorFactory.fromBitmap(bitmap)));

    // Prep the animator
    PropertyValuesHolder radiusHolder = PropertyValuesHolder.ofFloat("radius", 1, radius);
    PropertyValuesHolder transparencyHolder = PropertyValuesHolder.ofFloat("transparency", 0, 1);

    ValueAnimator valueAnimator = new ValueAnimator();
    valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
    valueAnimator.setRepeatMode(ValueAnimator.RESTART);
    valueAnimator.setValues(radiusHolder, transparencyHolder);
    valueAnimator.setDuration(DURATION);
    valueAnimator.setEvaluator(new FloatEvaluator());
    valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            float animatedRadius = (float) valueAnimator.getAnimatedValue("radius");
            float animatedAlpha = (float) valueAnimator.getAnimatedValue("transparency");
            circle.setDimensions(animatedRadius * 2);
            circle.setTransparency(animatedAlpha);

        }
    });

    // start the animation
    valueAnimator.start();

}

[this is what I get when the two locations are far from each other][enter image description here]1

If the two locations are close to each other I get this behaviour enter image description here

For the first image if I zoom in, then I do see the pulse animation.

Is there a way I can keep the radius of the pulse same irrespective of the zoom level ?


Solution

  • This happens because GroundOverlay is zoomed together with the google map. To avoid that you should recreate overlay for each zoom level with corrected radius for that zoom level and latitude (meters_to_pixels in example source code). For avoid GroundOverlay recreation you should store created GroundOverlay object and remove it before creating new. For that you need some changes in your showRipples() method - it should returns created overlay. Full source code for example with one marker:

    public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {
    
        private static final String TAG = MainActivity.class.getSimpleName();
    
        private final LatLng RED_MARKER = new LatLng(-37.884312, 145.000623);
    
        private GoogleMap mGoogleMap;
        private MapFragment mMapFragment;
    
        private GroundOverlay mRedPoint = null;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mMapFragment = (MapFragment) getFragmentManager()
                    .findFragmentById(R.id.map_fragment);
            mMapFragment.getMapAsync(this);
        }
    
        @Override
        public void onMapReady(GoogleMap googleMap) {
            mGoogleMap = googleMap;
    
            mGoogleMap.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() {
                @Override
                public void onCameraIdle() {
                    // if overlay already exists - remove it
                    if (mRedPoint != null) {
                        mRedPoint.remove();
                    }
                    mRedPoint = showRipples(RED_MARKER, Color.RED);
                }
            });
            mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(RED_MARKER, 16));
        }
    
        private GroundOverlay showRipples(LatLng latLng, int color) {
            GradientDrawable d = new GradientDrawable();
            d.setShape(GradientDrawable.OVAL);
            d.setSize(500, 500);
            d.setColor(color);
            d.setStroke(0, Color.TRANSPARENT);
    
            final Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth()
                    , d.getIntrinsicHeight()
                    , Bitmap.Config.ARGB_8888);
    
            // Convert the drawable to bitmap
            final Canvas canvas = new Canvas(bitmap);
            d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
            d.draw(canvas);
    
            // Radius of the circle for current zoom level and latitude (because Earth is sphere at first approach)
            double meters_to_pixels = (Math.cos(mGoogleMap.getCameraPosition().target.latitude * Math.PI /180) * 2 * Math.PI * 6378137) / (256 * Math.pow(2, mGoogleMap.getCameraPosition().zoom));
            final int radius = (int)(meters_to_pixels * getResources().getDimensionPixelSize(R.dimen.ripple_radius));
    
            // Add the circle to the map
            final GroundOverlay circle = mGoogleMap.addGroundOverlay(new GroundOverlayOptions()
                    .position(latLng, 2 * radius).image(BitmapDescriptorFactory.fromBitmap(bitmap)));
    
            // Prep the animator
            PropertyValuesHolder radiusHolder = PropertyValuesHolder.ofFloat("radius", 1, radius);
            PropertyValuesHolder transparencyHolder = PropertyValuesHolder.ofFloat("transparency", 0, 1);
    
            ValueAnimator valueAnimator = new ValueAnimator();
            valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
            valueAnimator.setRepeatMode(ValueAnimator.RESTART);
            valueAnimator.setValues(radiusHolder, transparencyHolder);
            valueAnimator.setDuration(1000);
            valueAnimator.setEvaluator(new FloatEvaluator());
            valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    float animatedRadius = (float) valueAnimator.getAnimatedValue("radius");
                    float animatedAlpha = (float) valueAnimator.getAnimatedValue("transparency");
                    circle.setDimensions(animatedRadius * 2);
                    circle.setTransparency(animatedAlpha);
    
                }
            });
    
            // start the animation
            valueAnimator.start();
    
            return circle;
        }
    
    }