Pulse ring animation around a Google Maps marker in Android

后端 未结 2 1936
别那么骄傲
别那么骄傲 2020-12-13 22:44

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?

相关标签:
2条回答
  • 2020-12-13 23:07
    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()
          }
    }
    
    0 讨论(0)
  • 2020-12-13 23:25

    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);
        }
    
    0 讨论(0)
提交回复
热议问题