Dokumentation / Grundlagen / Info Bubble

Informationsblase (InfoBubble)

Mit Marker verknüpfte Sprechblasen können Sie direkt im UI-Code der jeweiligen Plattform schreiben. Der Inhalt ist ein Composable in Compose, ein View in SwiftUI oder ein Element in React. Das SDK übernimmt nur die Ausrichtung und das Zeichnen des Schweifs.

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

01 · Funktionsweise

Gemeinsam für die 3 Plattformen: Der Aufbau besteht darin, den ausgewählten Marker als Zustand zu halten und die Blase nur zu zeichnen, solange er ausgewählt ist. Das Öffnen und Schließen der Blase wird vom state der App entschieden, nicht vom SDK.

Hat einen Auswahlzustand

SCHRITT 01

Behält den aktuell ausgewählten Marker (oder ID) als state. Bei gleichzeitiger Anzeige mehrerer wird Set verwendet.

Als Kind der Karte platzieren

SCHRITT 02

Platziert Marker und InfoBubble nebeneinander als Kinder des Kartencontainers und rendert InfoBubble nur, wenn sie ausgewählt ist.

Beim Antippen der Karte schließen

SCHRITT 03

Hebt die Auswahl in onMapClick der Karte auf. onClick des Markers legt die Auswahl fest.

Beispiel – Marker ausgewählt und Blase geöffnet

02 · 4 Muster

Die 4 Seiten unter `infobubble` in der Beispiel-App entsprechen direkt den 4 Implementierungsmustern. Wenn Sie ein Muster und eine Plattform wählen, wird der Code des entsprechenden Beispiels angezeigt.

Plattform

Eine Textzeile

Minimale Konfiguration. Zeigt eine Zeichenkette aus `extra` des Markers in einer Zeile an. Da Rahmen, Schweif und Abstände Standardwerte haben, müssen Sie nur den Text des Inhalts schreiben.

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),
            )
        }
    }
}
Vorschau
San Francisco - The Golden Gate City
SF

Der Standardzipfel sitzt direkt über dem Marker (tailOffset x:0.5 / y:1.0).

Stil ändern

Muster zum Ändern des Rahmenstils. Wenn Sie nur Farbe und abgerundete Ecken ändern, verwenden Android und React separate Argumente, iOS übergibt `InfoBubbleStyle`. Wenn Sie Rahmen und Schweif selbst zeichnen, verwenden alle 3 Plattformen `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), // Verbunden in der Mitte der linken Kante
    ) {
        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 {
                // Ein abgerundetes Rechteck zeichnen und in der Mitte der linken Kante einen dreieckigen Schwanz ansetzen
                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;
}
/* Mit ::before / ::after ein nach links zeigendes Dreieck als Schwanz darüberlegen */
Vorschau
1
37.7749,-122.419404

Der Verbindungspunkt ergibt sich aus tailOffset (Blasenseite) und dem infoAnchor des Icons (Markerseite).

Rich Content

Legen Sie ein Objekt in `extra` und betten Sie ein Layout mit Überschrift, Beschreibung und Bewertung ein. Der Rahmen kann über `bubbleColor` / `borderColor` / `contentPadding` / `cornerRadius` angepasst werden, und der Code des Inhalts kann sich nur auf das Layout des Inhalts konzentrieren.

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)
            }
        }
    }
}
Vorschau
Golden Gate Park

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

★★★★4.5/5
🌳

Die Beispiele für Android und iOS tauschen außerdem Rahmen- und Hintergrundfarbe für den Dunkelmodus.

Mehrere gleichzeitig anzeigen

Wenn Sie die Auswahl als Set speichern, können Sie mehrere Blasen gleichzeitig geöffnet halten. Zeigen Sie InfoBubble nur für geöffnete Marker an und fügen Sie Tap-Operationen im Inhalt hinzu, damit die Blase selbst geschlossen werden kann.

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)
                }
            }
        }
    }
}
Vorschau
Restaurant ATap to close
1
Hotel BTap to close
2
Shop CTap to close
3

In jedem Beispiel schließt ein Klick auf die Karte alle wieder (zurück zu einem leeren Set).

03 · API

InfoBubble ist eine High-Level-Komponente, die einen Rahmen mit Schwanz zeichnet, und InfoBubbleCustom ist eine Low-Level-Komponente, die nur die Positionierung vornimmt. iOS übergibt Stilargumente zusammengefasst in InfoBubbleStyle.

PARAM
ANDROID
REACT
Beschreibung
marker
Erforderlich
Erforderlich
Status des Markers, mit dem die Blase verknüpft ist. Berechnet den Verbindungspunkt aus der Position und der Icon-Größe.
bubbleColor
Color.White
'#ffffff'
Hintergrundfarbe der Blase.
borderColor
Color.Black
'#000000'
Farbe der Rahmenlinie.
contentPadding
8.dp
8
Innerer Abstand des Inhalts.
cornerRadius
4.dp
4
Radius der abgerundeten Ecken.
tailSize
8.dp
8
Größe des Schwanzes.
content / children
Erforderlich
Erforderlich
Inhalt der Blase. Übergeben Sie den UI-Code jeder Plattform direkt.
InfoBubbleStyle · iOS

iOS übergibt diese Struktur komplett an style statt einzelner Argumente.

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

Muster, bei dem Rahmen und Schweif selbst gezeichnet werden. Das SDK übernimmt nur die Positionierung.

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

Zeigt die Blase direkt an Koordinaten statt am Marker. Auf allen drei Plattformen ist es einheitlich, indem position an InfoBubble übergeben wird (Überladung von InfoBubble(position:) in Compose/iOS).

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

04 · Hinweis

Überlappt sich nicht, auch wenn kein Symbol angegeben ist

Marker, denen kein icon übergeben wird, werden als Standard-Pin behandelt (48px, unterer Kante als Bezug) und die Blase wird um deren tatsächliche Größe verschoben. Sie überlappt nicht den Marker.

Mit infoAnchor verschieben

Der Verbindungspunkt auf der Symbolseite kann mit infoAnchor (Offset in Compose, x/y in React) geändert werden. Die Blase folgt auch während des Ziehens.

Der Inhalt der Blase kann Ereignisse empfangen

clickable von Compose, Gesten von SwiftUI und onClick von React können direkt platziert werden. Sie arbeiten unabhängig von onMapClick der Karte, sodass der Schließvorgang auf der Inhaltsseite erfolgen kann.

Verwandte Seiten