Present on an Android Surface
AndroidMapPresentation presents a MapState on an
Android Surface without a Compose UI view. The same state can move between a
Surface and MaplibreMap. The
Android Auto demo
uses it in a car app.
Present the state
Section titled “Present the state”Create the state with MapRuntime.createMapState. Its base style,
style content, camera operations, and click handlers work as they do with
MaplibreMap. One presentation presents a state at a time. Close it before
presenting the state elsewhere.
Construct the presentation on the main thread with a Lifecycle. Rendering
pauses while the lifecycle is stopped. Destroying the lifecycle closes the
presentation. Closing the state also closes it.
/** Your code owns the MapState and each Surface. Call these methods on the main thread. */class SurfaceMapHost(context: Context, state: MapState, lifecycle: Lifecycle) : AutoCloseable { val presentation = AndroidMapPresentation(context, state, lifecycle) private var binding: AndroidMapPresentation.SurfaceBinding? = null
fun onSurfaceAvailable(surface: Surface, width: Int, height: Int, density: Float) { binding = presentation.attachSurface(surface, width, height, density) }
fun onSurfaceChanged(width: Int, height: Int, density: Float) { binding?.update(width, height, density) }
fun onSurfaceDestroyed() { binding?.close() binding = null }
fun onConfigurationChanged(configuration: Configuration) { presentation.updateConfiguration(configuration) }
override fun close() { presentation.close() }}attachSurface takes the Surface’s size in physical pixels and its density in
physical pixels per logical pixel. Keep the returned
AndroidMapPresentation.SurfaceBinding and pass size or density changes to
update. A density change recreates the native map.
Close the binding before releasing the Surface. Closing waits for rendering to stop using the Surface. Attaching another Surface closes the previous binding. A closed binding ignores later updates.
The map produces no frames until its style can be presented. Show your own loading content on the Surface until then. A map without a style renders black.
Pass a Configuration to updateConfiguration when the locale, layout
direction, font scale, or night mode changes. Style content reads it through
LocalConfiguration and LocalContext, and reads the Surface density through
LocalDensity. UI composables and view-dependent locals are not available in
style content.
failure reports an error that ended the presentation. Create a new
presentation to retry.
Pass gestures
Section titled “Pass gestures”The presentation has no gesture recognizer. Pass gestures your code recognized
to MapState.panBy, MapState.fling,
MapState.scaleBy, and MapState.click. They follow the
camera permissions and callbacks in the presentation’s interactions, interrupt
camera animations, and dispatch clicks to interactive layers. Convert physical
pixels to logical pixels first.
/** Converts physical pixels to the map's logical pixels before passing gestures. */class SurfaceInput(private val state: MapState, private val density: Float) { fun pan(deltaX: Float, deltaY: Float) { state.panBy(logical(deltaX, deltaY)) }
fun fling(velocityX: Float, velocityY: Float) { state.fling(logical(velocityX, velocityY)) }
fun scale(factor: Float, focusX: Float?, focusY: Float?) { val anchor = if (focusX != null && focusY != null) logical(focusX, focusY) else null state.scaleBy(factor.toDouble(), anchor) }
fun click(x: Float, y: Float) { state.click(logical(x, y)) }
private fun logical(x: Float, y: Float) = DpOffset((x / density).dp, (y / density).dp)}