8

I want to add a pulse ring animation on blue dot current user location in Android google mapFragment (like Uber).

Can anybody help me with this thing?

sunil
  • 796
  • 1
  • 8
  • 28

2 Answers2

19

I have found a solution to add pulsating animation to a marker. Here is the map part, Here variable "map" denotes your map.

private Circle lastUserCircle;
private long pulseDuration = 1000;
private ValueAnimator lastPulseAnimator;

private void addPulsatingEffect(LatLng userLatlng){
           if(lastPulseAnimator != null){
                lastPulseAnimator.cancel();
                Log.d("onLocationUpdated: ","cancelled" );
            }
            if(lastUserCircle != null)
                lastUserCircle.setCenter(userLatlng);
            lastPulseAnimator = valueAnimate(userLocation.getAccuracy(), pulseDuration, new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    if(lastUserCircle != null)
                        lastUserCircle.setRadius((Float) animation.getAnimatedValue());
                    else {
                        lastUserCircle = map.addCircle(new CircleOptions()
                                .center(userLatlng)
                                .radius((Float) animation.getAnimatedValue())
                                .strokeColor(Color.RED)
                                .fillColor(Color.BLUE));
                    }
                }
            }); 

}
protected ValueAnimator valueAnimate(float accuracy,long duration, ValueAnimator.AnimatorUpdateListener updateListener){
        Log.d( "valueAnimate: ", "called");
        ValueAnimator va = ValueAnimator.ofFloat(0,accuracy);
        va.setDuration(duration);
        va.addUpdateListener(updateListener);
        va.setRepeatCount(ValueAnimator.INFINITE);
        va.setRepeatMode(ValueAnimator.RESTART);

        va.start();
        return va;
    }

You have to call this on location updates by adding PositionChangedListener. You can easily find that in Google map docs. After that, for each update call the above method.

Fixing the pulse radius to somewhat same value, so that it's neither too big, nor too small

Write this method

protected float getDisplayPulseRadius(float radius) {
        float diff = (map.getMaxZoomLevel() - map.getCameraPosition().zoom);
        if (diff < 3)
            return radius;
        if (diff < 3.7)
            return radius * (diff / 2);
        if (diff < 4.5)
            return (radius * diff);
        if (diff < 5.5)
            return (radius * diff) * 1.5f;
        if (diff < 7)
            return (radius * diff) * 2f;
        if (diff < 7.8)
            return (radius * diff) * 3.5f;
        if (diff < 8.5)
            return (float) (radius * diff) * 5;
        if (diff < 10)
            return (radius * diff) * 10f;
        if (diff < 12)
            return (radius * diff) * 18f;
        if (diff < 13)
            return (radius * diff) * 28f;
        if (diff < 16)
            return (radius * diff) * 40f;
        if (diff < 18)
            return (radius * diff) * 60;
        return (radius * diff) * 80;
    }

And change this line

userLocation.getAccuracy()

to

getDisplayPulseRadius(userLocation.getAccuracy()

And also

.radius((Float) animation.getAnimatedValue())

to

.radius(getDisplayPulseRadius((Float) animation.getAnimatedValue()))

If you want an effect like the color fades to transparent when it gets big, you can use this just in the next line where you are setting the radius inside the animator

circle.setFillColor(adjustAlpha(pulseAroundMeFillColor, 1 - animation.getAnimatedFraction()));

private int adjustAlpha(int color, float factor) {
        int alpha = Math.round(Color.alpha(color) * factor);
        int red = Color.red(color);
        int green = Color.green(color);
        int blue = Color.blue(color);
        return Color.argb(alpha, red, green, blue);
    }
soshial
  • 5,906
  • 6
  • 32
  • 40
Debanjan
  • 2,817
  • 2
  • 24
  • 43
  • help me. where your "userLocation.getAccuracy()" – kemdo Dec 16 '17 at 08:15
  • Hi, I didn't understand your query. "userLocation" is a android.location.Location object. You can check a project of mine with many map functions here. https://github.com/itsdebs/MapEasy and the file with all these map related implementations here. https://github.com/itsdebs/MapEasy/blob/master/app/src/main/java/com/vagabond/mapeasy/maphandler/MapManagerImpl.java See, if this answers your query. – Debanjan Dec 16 '17 at 08:23
  • @Debanjan: I have to zoom in to see the animation. Any idea how can I set fixed size ? thanks – user2234 Feb 21 '18 at 04:17
  • @Neha sorry for being late. That is tough. I tried finding out the coordinates in the screen, to show pulsating effects, but that was never accurate, so I am attaching the only option possible here in the answer. You can change this with trial and error methods. – Debanjan Feb 23 '18 at 07:15
  • You sir, are pretty freaking awesome. – Ian Wambai Oct 17 '18 at 13:13
7
import android.animation.ValueAnimator
import android.graphics.Color
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.MapFragment
import com.google.android.gms.maps.model.Circle
import com.google.android.gms.maps.model.CircleOptions
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.MarkerOptions

class MainActivity : AppCompatActivity() {

     private val pulseCount = 4

     private val animationDuration = (pulseCount + 1) * 1000

     private val SAN_FRANCISCO_LOCATION = LatLng(37.7749295, -122.4194155)

     private var gMap: GoogleMap? = null

     private var circles = Array<Circle?>(pulseCount, { null })

     override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_main)

          (fragmentManager.findFragmentById(R.id.mpFrgmnt) as MapFragment).getMapAsync { map ->
               gMap = map
               setCurrentLocation()
          }
     }

     private fun setCurrentLocation() {
           gMap?.let { gMap ->
    gMap.moveCamera(CameraUpdateFactory.newLatLngZoom(SAN_FRANCISCO_LOCATION, 17f))
        gMap.animateCamera(CameraUpdateFactory.zoomIn())
        gMap.animateCamera(CameraUpdateFactory.zoomTo(17f), animationDuration, null)
        gMap.addMarker(MarkerOptions().position(SAN_FRANCISCO_LOCATION).title("San Francisco !"))

            val from = 0
            val to = 100
            val fraction = 255 / to

            for (i in 0 until pulseCount) {
                  addPulseAnimator(gMap, circles, SAN_FRANCISCO_LOCATION, from, to, fraction, i)
            }
        }
    }

     private fun addPulseAnimator(gMap: GoogleMap, circles: Array<Circle?>, latLng: LatLng, from: Int, to: Int, colorFraction: Int, currentPosition: Int) {
           val valueAnimator = ValueAnimator.ofInt(from, to)
           valueAnimator.duration = animationDuration.toLong()
           valueAnimator.repeatCount = ValueAnimator.INFINITE
           valueAnimator.repeatMode = ValueAnimator.RESTART
           valueAnimator.startDelay = currentPosition * 1000L
           valueAnimator.addUpdateListener { valueAnimator ->

        val radius = valueAnimator.animatedValue as Int

        circles[currentPosition]?.let { circle ->
            circle.center = latLng
            circle.radius = radius.toDouble()
            circle.fillColor = Color.argb((to - radius) * colorFraction, 48, 118, 254)
            circle.strokeWidth = 0f

        } ?: run {
            circles[currentPosition] = gMap.addCircle(CircleOptions()
                    .center(latLng)
                    .radius(radius.toDouble())
                    .fillColor(Color.argb((to - radius) * colorFraction, 48, 118, 254))
                    .strokeWidth(0f))
                   }
             }
             valueAnimator.start()
      }
}
mithil1501
  • 506
  • 9
  • 20