如何在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
相关产品推荐
相关产品推荐

