You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Google Map自定义标记的圆形图标周围实现涟漪效果?

这可能是个基础问题,但我未找到相关问题的有效解决方案,特此提问:我需要在Google Map标记的圆形图标周围实现涟漪效果,以下是我目前的尝试代码:

@Override public void onLocationChanged(final Location location) { 
    mLastLocation=location; 
    GradientDrawable d = new GradientDrawable(); 
    d.setShape(GradientDrawable.OVAL); 
    d.setSize(500,500); 
    d.setColor(0x555751FF); 
    d.setStroke(5, Color.TRANSPARENT); 
    Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth() , d.getIntrinsicHeight() , Bitmap.Config.ARGB_8888); 
    final Canvas canvas = new Canvas(bitmap); 
    d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 
    d.draw(canvas); 
    final int radius = 100; 
    final GroundOverlay circle = mMap.addGroundOverlay(new GroundOverlayOptions()
        .position(new LatLng(location.getLatitude(), location.getLongitude()), radius * 2)
        .image(BitmapDescriptorFactory.fromBitmap(bitmap)));
}
实现Google Map标记涟漪效果的可行方案

你的思路方向是对的,用GroundOverlay承载圆形图层,但缺少扩散动画+透明度渐变的核心逻辑,下面给你两种可以直接落地的实现方式:

方法一:多图层叠加属性动画(简单易上手)

这种方式通过创建多个半透明圆形GroundOverlay,依次给它们设置缩放和透明度动画,模拟自然的涟漪扩散效果:

private List<GroundOverlay> rippleOverlays = new ArrayList<>();
private static final int RIPPLE_COUNT = 3; // 涟漪层数
private static final long RIPPLE_DURATION = 2000; // 单个涟漪动画时长

@Override
public void onLocationChanged(final Location location) {
    mLastLocation = location;
    LatLng currentLatLng = new LatLng(location.getLatitude(), location.getLongitude());
    
    // 先清除旧的涟漪图层,避免地图堆积过多元素
    for (GroundOverlay overlay : rippleOverlays) {
        overlay.remove();
    }
    rippleOverlays.clear();
    
    // 循环创建多层涟漪
    for (int i = 0; i < RIPPLE_COUNT; i++) {
        // 生成半透明圆形Bitmap
        GradientDrawable d = new GradientDrawable();
        d.setShape(GradientDrawable.OVAL);
        d.setSize(500, 500);
        // 每层涟漪透明度递减,让视觉层次更自然
        int alpha = (int) (120 - (i * 40));
        d.setColor(Color.argb(alpha, 87, 81, 255));
        d.setStroke(0, Color.TRANSPARENT);
        
        Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        d.draw(canvas);
        
        // 添加初始状态的GroundOverlay
        GroundOverlayOptions options = new GroundOverlayOptions()
                .position(currentLatLng, 100) // 初始大小(直径)
                .image(BitmapDescriptorFactory.fromBitmap(bitmap))
                .transparency(0.5f); // 初始透明度
        
        GroundOverlay overlay = mMap.addGroundOverlay(options);
        rippleOverlays.add(overlay);
        
        // 启动动画,每层延迟错开,模拟涟漪依次扩散的效果
        startRippleAnimation(overlay, i * (RIPPLE_DURATION / RIPPLE_COUNT));
    }
}

private void startRippleAnimation(final GroundOverlay overlay, long delay) {
    // 缩放动画:从初始大小放大到3倍
    ValueAnimator scaleAnimator = ValueAnimator.ofFloat(1f, 3f);
    scaleAnimator.setDuration(RIPPLE_DURATION);
    scaleAnimator.setStartDelay(delay);
    scaleAnimator.setRepeatCount(ValueAnimator.INFINITE);
    scaleAnimator.addUpdateListener(animation -> {
        float scale = (float) animation.getAnimatedValue();
        overlay.setDimensions(100 * scale); // 100是初始直径,按比例放大
    });
    
    // 透明度动画:从半透明渐变到完全透明
    ValueAnimator alphaAnimator = ValueAnimator.ofFloat(0.5f, 0f);
    alphaAnimator.setDuration(RIPPLE_DURATION);
    alphaAnimator.setStartDelay(delay);
    alphaAnimator.setRepeatCount(ValueAnimator.INFINITE);
    alphaAnimator.addUpdateListener(animation -> {
        float alpha = (float) animation.getAnimatedValue();
        overlay.setTransparency(alpha);
    });
    
    // 同时启动两个动画,让缩放和透明渐变同步
    AnimatorSet animatorSet = new AnimatorSet();
    animatorSet.playTogether(scaleAnimator, alphaAnimator);
    animatorSet.start();
}

方法二:自定义AnimatedDrawable结合Marker(更流畅贴合)

如果希望涟漪和定位Marker图标绑定得更紧密,可以自定义一个带涟漪动画的Drawable,直接作为Marker图标使用:

// 自定义涟漪动画Drawable
public class RippleDrawable extends Drawable {
    private Paint mPaint;
    private int mCenterX, mCenterY;
    private float mRadius;
    private float mMaxRadius;
    private ValueAnimator mAnimator;

    public RippleDrawable(int color, float maxRadius) {
        mPaint = new Paint();
        mPaint.setColor(color);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
        mMaxRadius = maxRadius;
        
        // 初始化扩散动画
        mAnimator = ValueAnimator.ofFloat(0, maxRadius);
        mAnimator.setDuration(2000);
        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mAnimator.addUpdateListener(animation -> {
            mRadius = (float) animation.getAnimatedValue();
            // 半径越大透明度越低,模拟涟漪消散效果
            int alpha = (int) (255 * (1 - mRadius / mMaxRadius));
            mPaint.setAlpha(alpha);
            invalidateSelf();
        });
        mAnimator.start();
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        mCenterX = bounds.centerX();
        mCenterY = bounds.centerY();
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint);
    }

    @Override
    public void setAlpha(int alpha) {}

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {}

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }
    
    // 记得在页面销毁时停止动画,避免内存泄漏
    public void stopAnimation() {
        if (mAnimator != null && mAnimator.isRunning()) {
            mAnimator.cancel();
        }
    }
}

// 在onLocationChanged中使用自定义Drawable
@Override
public void onLocationChanged(final Location location) {
    mLastLocation = location;
    LatLng currentLatLng = new LatLng(location.getLatitude(), location.getLongitude());
    
    // 创建自定义涟漪Drawable
    RippleDrawable rippleDrawable = new RippleDrawable(0x555751FF, 100);
    // 转成Bitmap用于Marker图标
    Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    rippleDrawable.setBounds(0, 0, 200, 200);
    rippleDrawable.draw(canvas);
    
    // 添加Marker,也可以把涟漪和定位图标叠加绘制
    MarkerOptions markerOptions = new MarkerOptions()
            .position(currentLatLng)
            .icon(BitmapDescriptorFactory.fromBitmap(bitmap));
    mMap.addMarker(markerOptions);
}

额外注意事项

  • 页面销毁时一定要停止所有动画,避免内存泄漏;
  • 可以根据需求调整涟漪层数、动画时长、颜色透明度等参数,适配不同视觉风格;
  • 如果是实时定位场景,记得及时移除旧的Marker或GroundOverlay,避免地图性能下降。

内容的提问来源于stack exchange,提问作者M.Yogeshwaran

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 03:16:31