Documentación / Básicos / Info Bubble

Información burbuja (InfoBubble)

Los globos vinculados a marcadores se pueden escribir directamente en el código de la interfaz de usuario de cada plataforma. El contenido es un Composable en Compose, una View en SwiftUI o un elemento en React. El SDK solo se encarga de la alineación y el dibujo de la cola.

ANDROID
com.mapconductor.compose.info
iOS
MapConductorCore
REACT
@mapconductor/js-sdk-react

01 · Cómo funciona

Común para las 3 plataformas: la estructura consiste en mantener el marcador seleccionado como un estado y dibujar la burbuja solo mientras esté seleccionado. La apertura y cierre de la burbuja la decide el estado de la aplicación, no el SDK.

Tiene un estado de selección

PASO 01

Mantiene el marcador seleccionado (o ID) como state. Si se muestran varios a la vez, se usa Set.

Colocar como hijo del mapa

PASO 02

Coloca Marker y InfoBubble como hijos del contenedor del mapa y dibuja InfoBubble solo cuando está seleccionado.

Cerrar al tocar el mapa

PASO 03

Cancela la selección en onMapClick del mapa. onClick del marcador establece la selección.

Ejemplo · Marcador seleccionado y burbuja abierta

02 · 4 patrones

Las 4 páginas bajo `infobubble` en la aplicación de ejemplo corresponden directamente a los 4 patrones de implementación. Al elegir un patrón y una plataforma, se muestra el código del ejemplo correspondiente.

Plataforma

Una línea de texto

Configuración mínima. Muestra una cadena de `extra` del marcador en una línea. Como el marco, la cola y el relleno tienen valores predeterminados, solo necesita escribir el texto del contenido.

SimpleTextBubblePage.kt
var selectedMarker by remember { mutableStateOf<MarkerState?>(null) }
val markerState = remember {
    MarkerState(
        position = GeoPoint.fromLatLong(37.7749, -122.4194),
        icon = DefaultMarkerIcon(fillColor = Color.Blue, label = "SF"),
        extra = "San Francisco - The Golden Gate City",
        onClick = { selectedMarker = it },
    )
}

MapViewContainer(
    modifier = Modifier.fillMaxSize(),
    state = mapViewState,
    onMapClick = { selectedMarker = null },
    onMapLoaded = { selectedMarker = markerState },
) {
    Marker(markerState)

    selectedMarker?.let { marker ->
        InfoBubble(marker = marker) {
            Text(
                text = marker.extra as? String ?: "No information",
                color = MaterialTheme.colorScheme.primary,
                modifier = Modifier.padding(4.dp),
            )
        }
    }
}
Vista previa
San Francisco - The Golden Gate City
SF

La cola por defecto queda justo encima del marcador (tailOffset x:0.5 / y:1.0).

Cambiar estilo

Patrón para cambiar el estilo del marco. Si solo cambia el color y las esquinas redondeadas, Android y React usan argumentos separados, iOS pasa `InfoBubbleStyle`. Si dibuja el marco y la cola por su cuenta, las 3 plataformas usan `InfoBubbleCustom` (Compose: `Canvas`, SwiftUI: `Shape`, React: CSS `::before` / `::after`).

StyledInfoBubblePage.kt
val markerState1 by remember {
    mutableStateOf(
        MarkerState(
            id = "marker1",
            position = GeoPoint.fromLatLong(37.7749, -122.4194),
            icon = DefaultMarkerIcon(
                fillColor = Color.Blue,
                infoAnchor = Offset(0.5f, 0.25f),
                label = "1",
            ),
            draggable = true,
            onClick = onMarkerClick,
        ),
    )
}

selectedMarker?.let { marker ->
    val text = GeoPoint.from(marker.position).toUrlValue(6)
    InfoBubbleCustom(
        marker = marker,
        tailOffset = Offset(0f, 0.5f), // Conectado en el centro del borde izquierdo
    ) {
        RightTailInfoBubble(
            bubbleColor = Color.White,
            borderColor = Color.Black,
        ) {
            Text(text = text, color = MaterialTheme.colorScheme.primary)
        }
    }
}
StyledInfoBubblePage.kt · RightTailInfoBubble
@Composable
private fun RightTailInfoBubble(
    bubbleColor: Color,
    borderColor: Color,
    contentPadding: Dp = 8.dp,
    cornerRadius: Dp = 4.dp,
    tailSize: Dp = 8.dp,
    content: @Composable () -> Unit,
) {
    Box(modifier = Modifier.wrapContentSize()) {
        Canvas(modifier = Modifier.matchParentSize()) {
            val path = Path().apply {
                // Dibuja un rectángulo redondeado y añade una cola triangular en el centro del borde izquierdo
                lineTo(tail, height / 2 + tail / 2)
                lineTo(0f, height / 2)
                lineTo(tail, height / 2 - tail / 2)
                close()
            }
            drawPath(path, color = bubbleColor, style = Fill)
            drawPath(path, color = borderColor, style = Stroke(width = 2f))
        }
        Box(modifier = Modifier.padding(start = contentPadding + tailSize)) {
            content()
        }
    }
}
StyledInfoBubblePage.tsx
const marker1 = createMarkerState({
  id: 'marker1',
  position: createGeoPoint({ latitude: 37.7749, longitude: -122.4194 }),
  icon: new ColorDefaultIcon('#2563eb', {
    label: '1',
    labelTextColor: '#ffffff',
    infoAnchor: { x: 0.5, y: 0.25 },
  }),
  draggable: true,
  onClick: state => setSelectedId(state.id),
});

const activeMarker = markers.find(m => m.id === selectedId);

<Markers states={markers} />
{activeMarker && (
  <InfoBubbleCustom marker={activeMarker} tailOffset={{ x: 0, y: 0.5 }}>
    <div className="right-tail-info-bubble">
      {activeMarker.position.toUrlValue(6)}
    </div>
  </InfoBubbleCustom>
)}
index.css
.right-tail-info-bubble {
  position: relative;
  width: max-content;
  max-width: 220px;
  padding: 8px;
  margin-left: 8px;
  border: 2px solid #000;
  border-radius: 4px;
  background: #fff;
  color: #2563eb;
}
/* Usa ::before / ::after para superponer un triángulo hacia la izquierda como cola */
Vista previa
1
37.7749,-122.419404

El punto de conexión resulta de tailOffset (lado de la burbuja) y del infoAnchor del icono (lado del marcador).

Contenido enriquecido

Coloque un objeto en `extra` e incruste un diseño con título, descripción y calificación. El marco se puede ajustar con `bubbleColor` / `borderColor` / `contentPadding` / `cornerRadius`, y el código del contenido puede concentrarse solo en el diseño del contenido.

RichContentBubblePage.kt
data class LocationInfo(
    val name: String,
    val description: String,
    val rating: Float,
) : Serializable

selectedMarker?.let { marker ->
    val info = marker.extra as? LocationInfo ?: return@let
    InfoBubble(
        marker = marker,
        bubbleColor = if (isDarkTheme) Color.Black else Color.White,
        borderColor = if (isDarkTheme) Color.Gray else Color.Black,
        contentPadding = 16.dp,
        cornerRadius = 12.dp,
    ) {
        Column(modifier = Modifier.width(200.dp)) {
            Text(info.name, style = MaterialTheme.typography.headlineSmall,
                fontWeight = FontWeight.Bold)
            Spacer(Modifier.height(8.dp))
            Text(info.description, style = MaterialTheme.typography.bodyMedium)
            Spacer(Modifier.height(8.dp))
            Row(verticalAlignment = Alignment.CenterVertically) {
                repeat(5) { index ->
                    Icon(Icons.Default.Star, contentDescription = null,
                        tint = if (index < info.rating.toInt()) Color.Yellow else Color.Gray,
                        modifier = Modifier.size(16.dp))
                }
                Text(" ${info.rating}/5", style = MaterialTheme.typography.bodySmall)
            }
        }
    }
}
Vista previa
Golden Gate Park

A large urban park with gardens, museums, and recreational areas.

★★★★4.5/5
🌳

Los ejemplos de Android e iOS también cambian los colores de marco y fondo para el modo oscuro.

Mostrar varios simultáneamente

Si mantiene la selección como Set, puede mantener múltiples burbujas abiertas simultáneamente. Dibuje InfoBubble solo para los marcadores abiertos y agregue operaciones de toque en el contenido para que la burbuja se cierre por sí misma.

MultipleBubblesPage.kt
var selectedMarkers by remember { mutableStateOf(setOf<String>()) }

val onMarkerClick: OnMarkerEventHandler = { markerState ->
    selectedMarkers =
        if (selectedMarkers.contains(markerState.id)) {
            selectedMarkers - markerState.id
        } else {
            selectedMarkers + markerState.id
        }
}

MapViewContainer(
    state = mapViewState,
    onMapClick = { selectedMarkers = emptySet() },
) {
    markerStates.forEach { markerState ->
        Marker(markerState)

        if (selectedMarkers.contains(markerState.id)) {
            InfoBubble(
                marker = markerState,
                bubbleColor = Color.White,
                borderColor = Color.Black,
            ) {
                Column(modifier = Modifier.clickable(true, onClick = {
                    selectedMarkers = selectedMarkers - markerState.id
                })) {
                    Text(markerState.extra as String, fontWeight = FontWeight.Bold)
                    Text("Tap to close", color = Color.Gray)
                }
            }
        }
    }
}
Vista previa
Restaurant ATap to close
1
Hotel BTap to close
2
Shop CTap to close
3

En todos los ejemplos, tocar el mapa las cierra todas (vuelve a un Set vacío).

03 · API

InfoBubble es un componente de alto nivel que dibuja un marco con cola, e InfoBubbleCustom es un componente de bajo nivel que solo realiza la alineación. iOS pasa los argumentos de estilo agrupados en InfoBubbleStyle.

PARAM
ANDROID
REACT
Descripción
marker
Obligatorio
Obligatorio
Estado del marcador al que se vincula la burbuja. Calcula el punto de conexión a partir de la posición y el tamaño del ícono.
bubbleColor
Color.White
'#ffffff'
Color de fondo de la burbuja.
borderColor
Color.Black
'#000000'
Color de la línea del borde.
contentPadding
8.dp
8
Relleno interno del contenido.
cornerRadius
4.dp
4
Radio de las esquinas redondeadas.
tailSize
8.dp
8
Tamaño de la cola.
content / children
Obligatorio
Obligatorio
Contenido de la burbuja. Pasa el código de UI de cada plataforma tal como está.
InfoBubbleStyle · iOS

iOS pasa esta estructura completa a style en lugar de argumentos individuales.

InfoBubbleStyle(
  bubbleColor: Color,
  borderColor: Color,
  contentPadding: CGFloat,
  cornerRadius: CGFloat,
  tailSize: CGFloat
)
InfoBubbleCustom

Patrón donde el marco y la cola se dibujan por sí mismos. El SDK solo se encarga de la alineación.

marker: MarkerState
tailOffset: Offset   // ios: CGPoint
content / children
InfoBubble (position)

Muestra la burbuja directamente en las coordenadas en lugar del marcador. En las 3 plataformas está unificado pasando position a InfoBubble (sobrecarga de InfoBubble(position:) en Compose/iOS).

position: GeoPoint
// react: <InfoBubble position=… />
// compose / ios: InfoBubble(position:)

04 · Nota

No se superpone incluso si no se especifica un ícono

Los marcadores a los que no se les pasa icon se tratan como el pin predeterminado (48px, borde inferior como referencia) y la burbuja se desplaza por su tamaño real. No se superpondrá al marcador.

Desplazar con infoAnchor

El punto de conexión del lado del ícono se puede cambiar con infoAnchor (Offset en Compose, x/y en React). La burbuja sigue incluso durante el arrastre.

El contenido de la burbuja puede recibir eventos

Se pueden colocar directamente clickable de Compose, gestos de SwiftUI y onClick de React. Funcionan independientemente de onMapClick del mapa, por lo que la operación de cerrar puede estar del lado del contenido.

Páginas relacionadas