This widget implements a very specific adaptation of google_maps_cluster_manager, allowing different ,markers to be shown depending on whether it is a Cluster or a Place (Cluster of size 1). Different markers can also be shown depending on its location (allows the marker to wait for information from external APIs).
Add the following dependencies to your pubspec.yaml
- google_maps_cluster_manager: https://pub.dev/packages/google_maps_cluster_manager
- google_maps_flutter: https://pub.dev/packages/google_maps_flutter
Follow setup on
- google_maps_flutter to enable Google Maps
Just call the GoogleMapWidgetClusterMarkers
constructor to instantiate the widget.
A controller can be supplied to expose more package functions, e.g. to refresh the map or update places.
GoogleMapWidgetClusterMarkersController controller = GoogleMapWidgetClusterMarkersController()
GoogleMapWidgetClusterMarkers(
controller: controller,
clusterMarkerTextStyle: const TextStyle(
fontSize: 100,
fontWeight: FontWeight.w600,
color: Colors.white,
),
places: [
MyPlace(
name: 'Tottenham Court Road',
latLng: const LatLng(51.51630, -0.13000),
),
MyPlace(
name: 'Chinatown',
latLng: const LatLng(51.51090, -0.13160),
),
MyPlace(
name: 'Covent Garden',
latLng: const LatLng(51.51170, -0.12400),
),
MyPlace(
name: 'Imperial College',
latLng: const LatLng(51.4988, -0.1749),
),
],
defaultPlaceMarker: Container(
color: Colors.orange,
height: 100,
width: 100,
child: const Icon(
Icons.circle,
size: 150,
),
),
defaultClusterMarker: const Icon(
Icons.hexagon,
size: 150,
),
clusterMarker: const Icon(
Icons.hexagon,
size: 150,
),
placeMarkerBuilder: (latLngId) => Stack(
alignment: Alignment.center,
children: [
const Icon(
Icons.circle,
size: 150,
),
Text(
/// This can return data from some API query.
'${Random().nextInt(9)}',
style: const TextStyle(
fontSize: 80,
fontWeight: FontWeight.w600,
color: Colors.blue,
),
),
],
),
),
There are three types of build cycles, used to implement the package
FIRST BUILD
- DefaultRepaintBoundaryGenerator builds
- DefaultRenderRepaintBoundaries converted to DefaultBitmaps
- GoogleMap built
- ClusterManager initialised (with Places, GoogleMapController.id) but no clusters in view (because map has not rendered)
- GoogleMap renders and calls onCameraIdle
SECOND BUILD
- MapState.refreshMarkerBuilderAndUpdateMarkerCallback() to get clusters in view and use DefaultBitmaps
THIRD BUILD
- MapState.updateCachedPlacesAndClusters() splits clusterManager.clusters into a List and List in state (so that there is a cached reference when updateMap is called). CachedCluster and CachedPlace contain a repaintBoundaryKey and clusterManagerId (so that markerBuilder knows which one to use). Additionally, CachedCluster contains clusterSize to display on the Marker, and CachedPlace contains the latLngId so that API requests can be made to furnish the Marker with information (e.g., occupancy).
- ClusterBoundaryGenerator and PlaceBoundaryGenerator then builds a RepaintBoundary for each CachedCluster and CachedPlace, using both the repaintBoundaryKey and other info (clusterSize, latLngId)
- MapState.storeBitmapsInCache() then converts each CachedPlace and CachedCluster repaintBoundaryKey into a bitmap and stores it
- ClusterManager.updateMap is then called, and markerBuilderCallback uses the newly generated bitmaps
FIRST BUILD
- MapState.refreshMarkerBuilderAndUpdateMarkerCallback() to get clusters in view and use DefaultBitmaps
SECOND BUILD
- MapState.updateCachedPlacesAndClusters() splits clusterManager.clusters into a List and List in state (so that there is a cached reference when updateMap is called). CachedCluster and CachedPlace contain a repaintBoundaryKey and clusterManagerId (so that markerBuilder knows which one to use). Additionally, CachedCluster contains clusterSize to display on the Marker, and CachedPlace contains the latLngId so that API requests can be made to furnish the Marker with information (e.g., occupancy).
- ClusterBoundaryGenerator and PlaceBoundaryGenerator then builds a RepaintBoundary for each CachedCluster and CachedPlace, using both the repaintBoundaryKey and other info (clusterSize, latLngId)
- MapState.storeBitmapsInCache() then converts each CachedPlace and CachedCluster repaintBoundaryKey into a bitmap and stores it
- ClusterManager.updateMap is then called, and markerBuilderCallback uses the newly generated bitmaps
3.1.3. An updatePlacesDoubleBuildCycle, which occurs occurs whenever the places in clusterManager is updated. This cycle uses the second and third build of initMapTripleBuildCycle
FIRST BUILD
- MapState.refreshMarkerBuilderAndUpdateMarkerCallback() to get clusters in view and use DefaultBitmaps
SECOND BUILD
- MapState.updateCachedPlacesAndClusters() splits clusterManager.clusters into a List and List in state (so that there is a cached reference when updateMap is called). CachedCluster and CachedPlace contain a repaintBoundaryKey and clusterManagerId (so that markerBuilder knows which one to use). Additionally, CachedCluster contains clusterSize to display on the Marker, and CachedPlace contains the latLngId so that API requests can be made to furnish the Marker with information (e.g., occupancy).
- ClusterBoundaryGenerator and PlaceBoundaryGenerator then builds a RepaintBoundary for each CachedCluster and CachedPlace, using both the repaintBoundaryKey and other info (clusterSize, latLngId)
- MapState.storeBitmapsInCache() then converts each CachedPlace and CachedCluster repaintBoundaryKey into a bitmap and stores it
- ClusterManager.updateMap is then called, and markerBuilderCallback uses the newly generated bitmaps
- clusterManagerId == ClusterManager.cluster.getId(), has format lat_lng_clusterSize
- latLngId has format lat_lng
- RepaintBoundary is converted into Bitmap via: RepaintBoundary > RenderRepaintBoundary > Image > ByteDate > BitmapDescriptor
TODO: how to contribute to the package, how to file issues, what response they can expect from the package authors, and more.