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

Android Google Maps v2:用自定义View/ImageView绘制高性能无延迟雷达图方案咨询

Solution: Custom View Overlay for Radar on Google Maps v2

I get exactly what you're asking for—ditching the finicky Ground/Tile/Marker approaches and using a custom View's onDraw() to render your radar bitmap smoothly, with zero flicker and tight map sync. Let's break this down step by step with working code.

1. Layout Structure

First, we'll stack the custom View directly on top of the MapFragment using a FrameLayout (or ConstraintLayout if you prefer). This ensures the radar stays visible above the map and moves in lockstep:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Google Maps Fragment -->
    <fragment
        android:id="@+id/map_fragment"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <!-- Custom Radar View (draws on top of map) -->
    <com.yourpackage.RadarOverlayView
        android:id="@+id/radar_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

2. Custom Radar Overlay View Implementation

This View will hold references to your radar bitmap, its geographic bounds, and the GoogleMap instance. We'll use the map's Projection to convert LatLng coordinates to screen positions, then draw the bitmap in the correct place. We'll also hook into map camera events to trigger redraws when the map moves.

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.Projection
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.LatLngBounds

class RadarOverlayView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    // Your radar bitmap and its geographic bounds (set these from your data)
    private var radarBitmap: Bitmap? = null
    private var radarGeoBounds: LatLngBounds? = null

    // Reference to GoogleMap to get projection and camera updates
    private var googleMap: GoogleMap? = null

    // Reusable objects to avoid GC churn (critical for performance)
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val srcRect = Rect()
    private val destRect = Rect()

    init {
        // Make the View transparent so the map shows through
        setBackgroundColor(Color.TRANSPARENT)
    }

    fun setGoogleMap(map: GoogleMap) {
        this.googleMap = map
        // Listen for camera changes to trigger redraws when map moves/zooms
        map.setOnCameraMoveListener { invalidate() }
        // Also trigger a redraw when camera idle (for final position)
        map.setOnCameraIdleListener { invalidate() }
    }

    fun updateRadarData(bitmap: Bitmap, geoBounds: LatLngBounds) {
        this.radarBitmap = bitmap
        this.radarGeoBounds = geoBounds
        // Trigger redraw when new radar data arrives
        invalidate()
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        val bitmap = radarBitmap ?: return
        val geoBounds = radarGeoBounds ?: return
        val projection = googleMap?.projection ?: return

        // Convert geographic bounds to screen coordinates
        val topLeft = projection.toScreenLocation(geoBounds.northeast)
        val bottomRight = projection.toScreenLocation(geoBounds.southwest)

        // Calculate destination rectangle on the View's canvas
        destRect.set(
            topLeft.x,
            topLeft.y,
            bottomRight.x,
            bottomRight.y
        )

        // Use the entire bitmap as source
        srcRect.set(0, 0, bitmap.width, bitmap.height)

        // Draw the bitmap—no flicker because we're directly updating the canvas
        canvas.drawBitmap(bitmap, srcRect, destRect, paint)
    }
}

3. Activity Setup

In your Activity (or Fragment), initialize the map and link it to your custom View:

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLngBounds

class RadarMapActivity : AppCompatActivity() {

    private lateinit var radarView: RadarOverlayView

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

        radarView = findViewById(R.id.radar_view)

        // Initialize Google Map
        val mapFragment = supportFragmentManager.findFragmentById(R.id.map_fragment) as SupportMapFragment
        mapFragment.getMapAsync { googleMap ->
            // Link map to radar view
            radarView.setGoogleMap(googleMap)

            // Example: Load your radar bitmap and its bounds (replace with your actual data)
            val radarBitmap = BitmapFactory.decodeResource(resources, R.drawable.your_radar_bitmap)
            val radarBounds = LatLngBounds(
                LatLng(37.7749, -122.4194), // Southwest
                LatLng(37.8049, -122.3894)  // Northeast
            )

            // Update radar data (call this whenever you have a new radar frame)
            radarView.updateRadarData(radarBitmap, radarBounds)
        }
    }
}

Key Performance & Anti-Flicker Tips

  • Reusable Objects: We use Rect, Paint as class-level variables to avoid creating new objects in onDraw() (which causes GC spikes and flicker).
  • Selective Redraws: We only call invalidate() when the map moves (via camera listeners) or when new radar data arrives—no unnecessary redraws.
  • Direct Canvas Drawing: Unlike GroundOverlay, we're not adding/removing overlays; we're just updating the canvas directly, which is far faster.
  • Transparent Background: The View is transparent so the map shows through without extra compositing overhead.

How to Trigger invalidate()

As shown in the code:

  • Call radarView.updateRadarData() whenever you have a new radar bitmap (this triggers invalidate() internally).
  • The map's camera listeners automatically trigger invalidate() when the map moves/zooms, so the radar stays aligned.

This approach gives you full control over the drawing process, matches the performance you had with Maps v1, and eliminates the flicker/delay issues from the overlay approaches you tried.

内容的提问来源于stack exchange,提问作者user1572522

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:15:15