Ciclo de vida y eventos de MapView
El mapa se maneja con dos objetos: la vista y el objeto de estado. El estado (mapViewState) es la interfaz para la cámara, el diseño y la manipulación, y la vista simplemente se coloca en el árbol de UI de cada plataforma. La inicialización sigue etapas comunes, y la carga finalizada y las acciones del usuario se reciben como eventos.
01 · mapViewState
mapViewState es un objeto de estado que corresponde a una cara del mapa. Hay implementaciones por proveedor, pero los miembros públicos son comunes; el código de la aplicación solo necesita mirar la interfaz común.
El estado vive más tiempo que la vista. Incluso si se vuelve a crear la vista, la posición de la cámara y el diseño permanecen en el lado del estado, por lo que el mapa se restaura tal como estaba.
// Crea el estado con remember*, así el mismo mapa sobrevive a una rotación
val mapViewState =
rememberGoogleMapViewState(
mapDesign = GoogleMapDesign.Normal,
cameraPosition = initCameraPosition,
)
GoogleMapView(state = mapViewState, modifier = Modifier.fillMaxSize())// Un ObservableObject: mantenlo en la vista con @StateObject
@StateObject private var mapLibreState = MapLibreViewState(
mapDesignType: MapLibreDesign.OsmBright,
cameraPosition: viewModel.initCameraPosition
)
MapLibreMapView(state: mapLibreState) { MapViewContent() }const [mapViewState, setMapViewState] =
useState<MapViewStateInterface<MapDesignTypeInterface<unknown>> | null>(null);
// MapViewContainer es el envoltorio de cambio de proveedor de la app de ejemplo; no viene con el SDK
<MapViewContainer provider={provider} cameraPosition={INIT_CAMERA} onStateReady={setMapViewState}>
<Markers states={markerStates} />
</MapViewContainer>02 · Ciclo de vida de inicialización
La inicialización del mapa sigue las mismas etapas en todos los proveedores: carga del SDK, generación de la vista, generación de la instancia de mapa y finalización del dibujo de mosaicos. Como las etapas están comúnizadas como InitState, no necesita recordar por proveedor cuándo aún no se debe manipular.
Las manipulaciones de cámara y adiciones de superposiciones se aceptan a partir de MapCreated y se ponen en cola internamente. Si desea hacer algo después de que la carga haya finalizado, use onMapLoaded.
Carga el SDK del mapa. En la Web mediante inyección de scripts, en dispositivos móviles mediante proceso de inicialización. Si falla, pasa a Failed.
Envuelve la instancia de mapa nativa con un holder. A partir de aquí, los tipos específicos del SDK quedan ocultos.
Envuelve el holder con un controlador y prepara la API común y el cableado de eventos. El controlador se conecta al estado.
A través de la rotación de pantalla y regeneración
En Android, el estado se guarda con rememberSaveable; después de una rotación de pantalla o cambio de configuración, se restauran la posición de la cámara y el diseño. En los cambios de configuración, la vista del mapa en sí no se destruye, sino que se reutiliza.
Cuando se destruye
En una destrucción real (salida de la pantalla), se liberan conjuntamente la gestión de superposiciones, las rutas del servidor de mosaicos y el alcance de las corrutinas que posee el controlador. Al cambiar de proveedor, el mapa antiguo también se limpia por la misma ruta.
var ready by remember { mutableStateOf(false) }
MapLibreMapView(
state = mapViewState,
// Called once, when the tiles have finished drawing
onMapLoaded = { state ->
ready = true
state.fitBounds(routeBounds, padding = 48)
},
) {
// Declarations here are not lost before MapCreated — they are queued internally
Marker(markerState)
}
if (!ready) {
// Your own loading overlay, if you want one. The map does not need blocking
Box(Modifier.fillMaxSize()) { CircularProgressIndicator() }
}03 · Eventos
Los controladores que se pasan a la vista tienen la misma configuración en las tres plataformas. Los tipos también son comunes: la carga finalizada recibe el objeto de estado, el toque recibe coordenadas, el cambio de cámara recibe la posición de la cámara.
GoogleMapView(
state = mapViewState,
onMapLoaded = { state -> viewModel.onMapLoaded(state) },
onMapClick = { point -> viewModel.onMapClick(point) },
onCameraMove = { camera -> viewModel.onCameraChanged(camera) },
onCameraMoveEnd = { camera -> viewModel.onCameraSettled(camera) },
) { /* markers, overlays */ }MapLibreMapView(
state: mapLibreState,
onMapLoaded: { state in viewModel.onMapLoaded(state) },
onCameraMoveStart: viewModel.onMapCameraMoveStart,
onCameraMove: viewModel.onCameraChanged,
onCameraMoveEnd: viewModel.onMapCameraMoveEnd
) {
MapViewContent()
}<MapViewContainer
provider={provider}
cameraPosition={INIT_CAMERA}
onMapClick={() => setSelected(null)}
onCameraMove={setCameraPosition}
>
<Markers states={markerStates} />
</MapViewContainer>Los eventos relacionados con la cámara también actualizan cameraPosition del objeto de estado, por lo que siempre se puede leer la cámara más reciente sin adjuntar un controlador. Si desea evitar la recuperación durante el movimiento, use solo onCameraMoveEnd.
04 · Vía de escape a nativo
Para cuando la API común no es suficiente, hemos preparado una puerta para bajar a la instancia del mapa nativo. Normalmente no se usa, pero es una vía de escape cuando solo quieres usar una función específica del proveedor en un solo lugar.
Holder que envuelve el mapa nativo
El holder posee dos elementos: la vista de la plataforma y la instancia del mapa. El código específico del SDK se confina a partir de aquí y no se filtra al código común.
Conversión entre coordenadas y posición en pantalla
Las coordenadas geográficas y las posiciones de píxeles en la pantalla se pueden convertir mutuamente. Se usa al superponer una UI personalizada sobre el mapa.
// Sácalo solo cuando de verdad necesites la API nativa val holder = mapViewState.getMapViewHolder() val nativeMap = holder?.map // GoogleMap / MapLibreMap / ... val offset = holder?.toScreenOffset(point)
if let holder = mapViewState.getMapViewHolder() {
let nativeMap = holder.map
let offset = holder.toScreenOffset(position: point)
}const holder = mapViewState.getMapViewHolder(); const nativeMap = holder?.map; // google.maps.Map / maplibregl.Map / ...
El código que usa el titular depende del proveedor. Por favor, separa conscientemente las partes que quieres mantener comunes de las partes donde usas deliberadamente una API específica.