Skip to content

Commit

Permalink
feat: add support for Advanced Markers (#1243)
Browse files Browse the repository at this point in the history
Upgrading the Maps SDK for Android dependency to v18.2.0 switches to the upgraded renderer as default renderer. See release notes at https://developers.google.com/maps/documentation/android-sdk/release-notes#October_18_2023

The upgraded renderer is required for Advanced Markers.

* feat: add support for Advanced Markers
* feat: add api() for Play Services and removed dependency from the app-level module
---------

Co-authored-by: Angela Yu <5506675+wangela@users.noreply.github.com>
  • Loading branch information
kikoso and wangela authored Oct 27, 2023
1 parent 4e17637 commit c4cd47c
Show file tree
Hide file tree
Showing 8 changed files with 1,426 additions and 2 deletions.
1 change: 0 additions & 1 deletion demo/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ android {
dependencies {

// [START_EXCLUDE silent]
implementation 'com.google.android.gms:play-services-maps:18.1.0'
implementation project(':library')

implementation 'androidx.appcompat:appcompat:1.7.0-alpha01'
Expand Down
1 change: 1 addition & 0 deletions demo/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
<activity android:name=".BigClusteringDemoActivity" />
<activity android:name=".VisibleClusteringDemoActivity" />
<activity android:name=".CustomMarkerClusteringDemoActivity" />
<activity android:name=".CustomAdvancedMarkerClusteringDemoActivity" />
<activity android:name=".ZoomClusteringDemoActivity" />
<activity android:name=".ClusteringViewModelDemoActivity"/>
<activity android:name=".TileProviderAndProjectionDemo" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/*
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.maps.android.utils.demo;

import java.util.Random;

import android.graphics.Color;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.OnMapsSdkInitializedCallback;
import com.google.android.gms.maps.model.AdvancedMarker;
import com.google.android.gms.maps.model.AdvancedMarkerOptions;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.PinConfig;
import com.google.maps.android.clustering.Cluster;
import com.google.maps.android.clustering.ClusterItem;
import com.google.maps.android.clustering.ClusterManager;
import com.google.maps.android.clustering.view.DefaultAdvancedMarkersClusterRenderer;
import com.google.maps.android.utils.demo.model.Person;

import androidx.annotation.NonNull;

/**
* This sample demonstrates how to make use of the new Advanced Markers.
*/
public class CustomAdvancedMarkerClusteringDemoActivity extends BaseDemoActivity implements
ClusterManager.OnClusterClickListener<Person>, ClusterManager.OnClusterInfoWindowClickListener<Person>,
ClusterManager.OnClusterItemClickListener<Person>, ClusterManager.OnClusterItemInfoWindowClickListener<Person>,
OnMapsSdkInitializedCallback {
private ClusterManager<Person> mClusterManager;
private final Random mRandom = new Random(1984);

@Override
public void onMapsSdkInitialized(@NonNull MapsInitializer.Renderer renderer) {
switch (renderer) {
case LATEST:
Log.d("MapsDemo", "The latest version of the renderer is used.");
break;
case LEGACY:
Log.d("MapsDemo", "The legacy version of the renderer is used.");
break;
default:
break;
}
}

private class AdvancedMarkerRenderer extends DefaultAdvancedMarkersClusterRenderer<Person> {

public AdvancedMarkerRenderer() {
super(getApplicationContext(), getMap(), mClusterManager);
}

@Override
protected void onBeforeClusterItemRendered(@NonNull Person person,
@NonNull AdvancedMarkerOptions markerOptions) {
markerOptions
.icon(BitmapDescriptorFactory.fromPinConfig(getPinConfig().build()))
.title(person.name);
}

@Override
protected void onClusterItemUpdated(@NonNull Person person, @NonNull Marker marker) {
// Same implementation as onBeforeClusterItemRendered() (to update cached markers)
marker.setIcon(BitmapDescriptorFactory.fromPinConfig(getPinConfig().build()));
marker.setTitle(person.name);
}

private PinConfig.Builder getPinConfig() {
PinConfig.Builder pinConfigBuilder = PinConfig.builder();
pinConfigBuilder.setBackgroundColor(Color.MAGENTA);
pinConfigBuilder.setBorderColor(getResources().getColor(R.color.colorPrimaryDark));
return pinConfigBuilder;
}

private View addTextAsMarker(int size) {
TextView textView = new TextView(getApplicationContext());
textView.setText("I am a cluster of size " + size);
textView.setBackgroundColor(Color.BLACK);
textView.setTextColor(Color.YELLOW);
return textView;
}

@Override
protected void onBeforeClusterRendered(@NonNull Cluster<Person> cluster,
@NonNull AdvancedMarkerOptions markerOptions) {
markerOptions.iconView(addTextAsMarker(cluster.getSize()));
}

@Override
protected void onClusterUpdated(@NonNull Cluster<Person> cluster, AdvancedMarker marker) {
marker.setIconView(addTextAsMarker(cluster.getSize()));
}


@Override
protected boolean shouldRenderAsCluster(@NonNull Cluster<Person> cluster) {
// Always render clusters.
return cluster.getSize() > 1;
}
}


@Override
public boolean onClusterClick(Cluster<Person> cluster) {
// Show a toast with some info when the cluster is clicked.
String firstName = cluster.getItems().iterator().next().name;
Toast.makeText(this, cluster.getSize() + " (including " + firstName + ")", Toast.LENGTH_SHORT).show();

// Zoom in the cluster. Need to create LatLngBounds and including all the cluster items
// inside of bounds, then animate to center of the bounds.

// Create the builder to collect all essential cluster items for the bounds.
LatLngBounds.Builder builder = LatLngBounds.builder();
for (ClusterItem item : cluster.getItems()) {
builder.include(item.getPosition());
}
// Get the LatLngBounds
final LatLngBounds bounds = builder.build();

// Animate camera to the bounds
try {
getMap().animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 100));
} catch (Exception e) {
e.printStackTrace();
}

return true;
}

@Override
public void onClusterInfoWindowClick(Cluster<Person> cluster) {
// Does nothing, but you could go to a list of the users.
}

@Override
public boolean onClusterItemClick(Person item) {
// Does nothing, but you could go into the user's profile page, for example.
return false;
}

@Override
public void onClusterItemInfoWindowClick(Person item) {
// Does nothing, but you could go into the user's profile page, for example.
}

@Override
protected void startDemo(boolean isRestore) {
if (!isRestore) {
getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(51.503186, -0.126446), 9.5f));
}

// This line is extremely important to initialise the advanced markers. Without it, advanced markers will not work.
MapsInitializer.initialize(getApplicationContext(), MapsInitializer.Renderer.LATEST, this);

mClusterManager = new ClusterManager<>(this, getMap());
mClusterManager.setRenderer(new AdvancedMarkerRenderer());
getMap().setOnCameraIdleListener(mClusterManager);
getMap().setOnMarkerClickListener(mClusterManager);
getMap().setOnInfoWindowClickListener(mClusterManager);
mClusterManager.setOnClusterClickListener(this);
mClusterManager.setOnClusterInfoWindowClickListener(this);
mClusterManager.setOnClusterItemClickListener(this);
mClusterManager.setOnClusterItemInfoWindowClickListener(this);

addItems();
mClusterManager.cluster();
}

private void addItems() {
// http://www.flickr.com/photos/sdasmarchives/5036248203/
mClusterManager.addItem(new Person(position(), "Walter", R.drawable.walter));

// http://www.flickr.com/photos/usnationalarchives/4726917149/
mClusterManager.addItem(new Person(position(), "Gran", R.drawable.gran));

// http://www.flickr.com/photos/nypl/3111525394/
mClusterManager.addItem(new Person(position(), "Ruth", R.drawable.ruth));

// http://www.flickr.com/photos/smithsonian/2887433330/
mClusterManager.addItem(new Person(position(), "Stefan", R.drawable.stefan));

// http://www.flickr.com/photos/library_of_congress/2179915182/
mClusterManager.addItem(new Person(position(), "Mechanic", R.drawable.mechanic));

// http://www.flickr.com/photos/nationalmediamuseum/7893552556/
mClusterManager.addItem(new Person(position(), "Yeats", R.drawable.yeats));

// http://www.flickr.com/photos/sdasmarchives/5036231225/
mClusterManager.addItem(new Person(position(), "John", R.drawable.john));

// http://www.flickr.com/photos/anmm_thecommons/7694202096/
mClusterManager.addItem(new Person(position(), "Trevor the Turtle", R.drawable.turtle));

// http://www.flickr.com/photos/usnationalarchives/4726892651/
mClusterManager.addItem(new Person(position(), "Teach", R.drawable.teacher));
}

private LatLng position() {
return new LatLng(random(51.6723432, 51.38494009999999), random(0.148271, -0.3514683));
}

private double random(double min, double max) {
return mRandom.nextDouble() * (max - min) + min;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ protected void onCreate(Bundle savedInstanceState) {
mListView = findViewById(R.id.list);

addDemo("Clustering", ClusteringDemoActivity.class);
addDemo("Advanced Markers Clustering Example", CustomAdvancedMarkerClusteringDemoActivity.class);
addDemo("Clustering: Custom Look", CustomMarkerClusteringDemoActivity.class);
addDemo("Clustering: 2K markers", BigClusteringDemoActivity.class);
addDemo("Clustering: 20K only visible markers", VisibleClusteringDemoActivity.class);
Expand Down
3 changes: 3 additions & 0 deletions demo/src/main/res/layout/map.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
~ limitations under the License.
-->

<!-- Note that the item mapId is required for Advanced Markers to work -->
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="@+id/map"
map:mapId="mapId"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.google.android.gms.maps.SupportMapFragment" />
3 changes: 2 additions & 1 deletion library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ android {
}

dependencies {
implementation 'com.google.android.gms:play-services-maps:18.1.0'
// We are adding api() for the Maps SDK for Android, so it propagates to the app-level modules.
api 'com.google.android.gms:play-services-maps:18.2.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.core:core-ktx:1.12.0'
Expand Down
Loading

0 comments on commit c4cd47c

Please sign in to comment.