Android Google Maps v2:用自定义View/ImageView绘制高性能无延迟雷达图方案咨询
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,Paintas class-level variables to avoid creating new objects inonDraw()(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 triggersinvalidate()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

