Pulse animation using GroundOverlay

I need to show location A and location B by impulse animation. I can achieve this using the code below. But the problem I am facing is that GroundOverlay changes its size when the zoom level changes. If the locations of A and B are close to each other (i.e., the map scaling scale is high), the pulse radius is too large. When I zoom out, it gets too small.

How to keep the size of the overlay, regardless of the zoom level of the map.

Below is the code: An animated transparent circle on Google Maps v2 does NOT animate 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 two locations are far apart] [ enter image description here ] 1

If two locations are close to each other, I get this behavior enter image description here

For the first image, if I zoom in, then I see a momentum animation.

Is there a way to keep the radius of the pulse the same regardless of the zoom level?

+1
source share
2 answers

This is because GroundOverlay scales with the google map. To avoid this, you must recreate the overlay for each zoom level with a fixed radius for that zoom level and latitude ( meters_to_pixels in the source code example). To avoid a GroundOverlay rest, you must save the created GroundOverlay object and delete it before creating a new one. To do this, you will need some changes to the showRipples() method - it should return the 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; } } 
0
source

So, let's say you want the radius of the overlay circle to be a fixed size (relative to the screen pixels), for example, 1/10 of the screen width (at current magnification).

  // compute width of visible region // get lat-lng of left and right points LatLng left = googleMap.getProjection().getVisibleRegion().farLeft; LatLng right = googleMap.getProjection().getVisibleRegion().farRight; // compute distance between points float[] results = new float[1]; Location.distanceBetween(left.latitude, left.longitude,right.latitude,right.longitude, results); // scale to desired relative radius size float scaledRadius = results[0] * 0.10F; // and use that for radius - taken from OP code and use 'scaledRadius' final GroundOverlay circle = googleMap.addGroundOverlay(new GroundOverlayOptions() .position(latLng, scaledRadius).image(BitmapDescriptorFactory.fromBitmap(bitmap))); 

In this example, the width is used as the scale axis, but you can use the height or diagonal (using different projection points).

The use of "far" can be replaced by "near" - it is used to take into account the slope, so you have to experiment.

So, now your resource value is a scaling factor, and not an absolute radius value - therefore, for this example, you should set the resource value to 0.10F and use it where it is hardcoded above.

If you want the impulse (and overlay) to work after / during scaling, you will need to update the overlay circle width (circle.setWidth (scaledRadius)) using the onCameraIdle event - using the same calculation as above for scaledRadius, for example:

 public void onCameraIdle() { if (circle != null) { // compute scaled radius as in above code... // The 1-argument version is specifically width circle.setDimensions(scaledRadius); } } 
0
source

Source: https://habr.com/ru/post/979895/


All Articles