1. Avant de commencer
Cet atelier de programmation vous explique comment intégrer le SDK Maps pour Android à votre application et utiliser ses principales fonctionnalités. Pour cela, nous allons créer une application affichant une carte des magasins de vélos à San Francisco, Californie, États-Unis.
Prerequisites
- Connaissances de base en développement avec Kotlin et Android
Objectifs de l'atelier
- Activer et utiliser le SDK Maps pour Android afin d'ajouter Google Maps à une application Android
- Ajouter, personnaliser et regrouper des marqueurs
- Tracer des polylignes et des polygones sur la carte
- Contrôler le focus de la caméra via le programmatique
Prérequis
- SDK Maps pour Android
- Un compte Google pour lequel la facturation est activée
- Android Studio 2020.3.1 ou version ultérieure
- Les services Google Play installés dans Android Studio
- Un appareil ou un émulateur Android qui exécute la plate-forme des API Google sous Android version 4.2.2 ou ultérieure (pour connaître la procédure d'installation, consultez Exécuter des applications sur l'émulateur Android).
2. Configuration
Pour cette étape , vous devez activer le SDK Maps pour Android.
Configurer Google Maps Platform
Si vous ne disposez pas encore d'un compte Google Cloud Platform et d'un projet pour lequel la facturation est activée, consultez le guide Premiers pas avec Google Maps Platform pour savoir comment créer un compte de facturation et un projet.
- Dans Cloud Console, cliquez sur le menu déroulant des projets, puis sélectionnez celui que vous souhaitez utiliser pour cet atelier de programmation.
- Activez les API et les SDK Google Maps Platform requis pour cet atelier de programmation dans Google Cloud Marketplace. Pour ce faire, suivez les étapes indiquées dans cette vidéo ou dans cette documentation.
- Générez une clé API sur la page Identifiants de Cloud Console. Vous pouvez suivre la procédure décrite dans cette vidéo ou dans cette documentation. Toutes les requêtes envoyées à Google Maps Platform nécessitent une clé API.
3. Démarrage rapide
Voici un code de démarrage qui vous permettra de commencer rapidement cet atelier de programmation. Vous pouvez tout à fait passer directement au résultat, mais si vous souhaitez voir toutes les étapes et les réaliser de votre côté, poursuivez votre lecture.
- Clonez le dépôt si vous avez installé
git
.
git clone https://github.com/googlecodelabs/maps-platform-101-android.git
Vous pouvez également cliquer sur le bouton suivant pour télécharger le code source.
- Une fois le code obtenu, dans Android Studio, ouvrez le projet qui se trouve dans le répertoire
starter
.
4. Ajouter Google Maps
Dans cette section, vous allez ajouter Google Maps pour charger la carte lorsque vous lancez l'application.
Ajouter votre clé API
La clé API que vous avez créée précédemment doit être fournie à l'application afin que le SDK Maps pour Android puisse associer ces deux éléments.
- Pour ce faire, ouvrez le fichier nommé
local.properties
dans le répertoire racine de votre projet (soit au même niveau quegradle.properties
etsettings.gradle
). - Dans ce fichier, définissez une nouvelle clé
GOOGLE_MAPS_API_KEY
avec pour valeur la clé API que vous avez créée.
local.properties
GOOGLE_MAPS_API_KEY=YOUR_KEY_HERE
Notez que local.properties
est répertorié dans le fichier .gitignore
du dépôt Git. En effet, votre clé API est considérée comme une information sensible et ne doit pas être vérifiée dans un contrôle du code source, si possible.
- Pour exposer ensuite votre API afin de l'utiliser dans l'ensemble de votre application, incluez le plug-in Secrets Gradle pour Android dans le fichier
build.gradle
de votre application (situé dans le répertoireapp/
) et ajoutez la ligne suivante dans le blocplugins
:
build.gradle au niveau de l'application
plugins {
// ...
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
}
Vous devrez également modifier le fichier build.gradle
au niveau du projet pour inclure le chemin de classe suivant:
build.gradle au niveau du projet
buildscript {
dependencies {
// ...
classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:1.3.0"
}
}
Avec ce plug-in, les clés que vous avez définies dans votre fichier local.properties
seront disponibles en tant que variables de compilation dans le fichier manifeste Android et en tant que variables dans la classe BuildConfig
générée par Gradle au moment de la compilation. Ainsi, vous n'avez plus besoin du code récurrent permettant de lire les propriétés de local.properties
afin d'étendre l'accès à l'ensemble de l'application.
Ajouter la dépendance Google Maps
- Maintenant que l'application a accès à votre clé API, vous devez ajouter le SDK Maps pour Android en tant que dépendance dans le fichier
build.gradle
de votre application.
Dans le projet initial proposé dans cet atelier de programmation, cette dépendance a déjà été ajoutée.
build.gradle
dependencies {
// Dependency to include Maps SDK for Android
implementation 'com.google.android.gms:play-services-maps:17.0.0'
}
- Ajoutez ensuite une nouvelle balise
meta-data
dansAndroidManifest.xml
pour transmettre la clé API que vous avez créée précédemment. Pour ce faire, ouvrez ce fichier dans Android Studio et ajoutez la balisemeta-data
suivante dans l'objetapplication
de votre fichierAndroidManifest.xml
, situé dansapp/src/main
.
AndroidManifest.xml
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${GOOGLE_MAPS_API_KEY}" />
- Créez ensuite un fichier de mise en page appelé
activity_main.xml
dans le répertoireapp/src/main/res/layout/
et définissez-le comme suit :
activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<fragment
class="com.google.android.gms.maps.SupportMapFragment"
android:id="@+id/map_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
Cette mise en page comporte un seul FrameLayout
contenant un SupportMapFragment
, qui inclut l'objet GoogleMaps
sous-jacent que vous utiliserez dans les étapes suivantes.
- Enfin, mettez à jour la classe
MainActivity
située dansapp/src/main/java/com/google/codelabs/buildyourfirstmap
en ajoutant le code suivant pour ignorer la méthodeonCreate
, ce qui vous permettra de définir son contenu avec la mise en page que vous venez de créer.
MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
- Exécutez à présent l'application. La carte devrait se charger sur l'écran de votre appareil.
5. Personnalisation de cartes dans Google Cloud (facultatif)
Vous pouvez personnaliser le style de votre carte à l'aide de la fonctionnalité Personnalisation de cartes dans Google Cloud.
Créer un ID de carte
Si vous n'avez pas encore créé d'ID de carte associé à un style de carte, consultez le guide ID de carte pour effectuer les étapes suivantes :
- Créer un ID de carte
- Associer un ID de carte à un style de carte
Ajouter l'ID de carte dans votre application
Pour utiliser l'identifiant de carte que vous avez créé, modifiez le fichier activity_main.xml
et transmettez votre identifiant de carte dans l'attribut map:mapId
de SupportMapFragment
.
activity_main.xml
<fragment xmlns:map="http://schemas.android.com/apk/res-auto"
class="com.google.android.gms.maps.SupportMapFragment"
<!-- ... -->
map:mapId="YOUR_MAP_ID" />
Une fois ces étapes terminées, exécutez l'application pour afficher votre carte dans le style que vous avez sélectionné.
6. Ajouter des repères
Dans cette tâche, vous allez ajouter les repères qui représentent les points d'intérêt que vous souhaitez mettre en évidence sur la carte. Pour commencer, vous devez extraire la liste des lieux fournis dans le projet initial, puis les ajouter à la carte. Dans cet exemple, il s'agit de magasins de vélos.
Obtenir une référence à GoogleMap
Tout d'abord, vous devez obtenir une référence à l'objet GoogleMap
afin de pouvoir utiliser ses méthodes. Pour cela, ajoutez le code suivant dans votre méthode MainActivity.onCreate()
, juste après l'appel de setContentView()
:
MainActivity.onCreate()
val mapFragment = supportFragmentManager.findFragmentById(
R.id.map_fragment
) as? SupportMapFragment
mapFragment?.getMapAsync { googleMap ->
addMarkers(googleMap)
}
L'implémentation commence par rechercher le SupportMapFragment
que vous avez ajouté à l'étape précédente en utilisant la méthode findFragmentById()
sur l'objet SupportFragmentManager
. Une fois qu'elle a obtenu une référence, elle appelle getMapAsync()
, puis transmet un lambda, où l'objet GoogleMap
est transmis. Dans ce lambda, la méthode addMarkers()
est appelée. Nous la définirons bientôt.
Classe fournie : PlacesReader
Dans le projet initial, la classe PlacesReader
a déjà été fournie. Elle lit une liste de 49 lieux stockés dans un fichier JSON appelé places.json
et les renvoie en tant que List<Place>
. Il s'agit de magasins de vélos à San Francisco, en Californie, États-Unis.
Si vous souhaitez en savoir plus sur l'implémentation de la classe PlacesReader
, vous pouvez y accéder sur GitHub ou l'ouvrir dans Android Studio.
PlacesReader
package com.google.codelabs.buildyourfirstmap.place
import android.content.Context
import com.google.codelabs.buildyourfirstmap.R
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.io.InputStream
import java.io.InputStreamReader
/**
* Reads a list of place JSON objects from the file places.json
*/
class PlacesReader(private val context: Context) {
// GSON object responsible for converting from JSON to a Place object
private val gson = Gson()
// InputStream representing places.json
private val inputStream: InputStream
get() = context.resources.openRawResource(R.raw.places)
/**
* Reads the list of place JSON objects in the file places.json
* and returns a list of Place objects
*/
fun read(): List<Place> {
val itemType = object : TypeToken<List<PlaceResponse>>() {}.type
val reader = InputStreamReader(inputStream)
return gson.fromJson<List<PlaceResponse>>(reader, itemType).map {
it.toPlace()
}
}
Charger les lieux
Pour charger la liste des magasins de vélos, ajoutez une propriété appelée places
dans MainActivity
, puis définissez-la comme suit :
MainActivity.places
private val places: List<Place> by lazy {
PlacesReader(this).read()
}
Ce code appelle la méthode read()
sur PlacesReader
, qui renvoie un List<Place>
. Chaque Place
comprend une propriété name
(le nom du lieu) et une propriété latLng
(les coordonnées du lieu).
Place
data class Place(
val name: String,
val latLng: LatLng,
val address: LatLng,
val rating: Float
)
Ajouter des repères à la carte
Maintenant que les lieux ont été chargés dans la mémoire, l'étape suivante consiste à les représenter sur la carte.
- Dans
MainActivity
, créez une méthode appeléeaddMarkers()
et définissez-la comme suit :
MainActivity.addMarkers()
/**
* Adds marker representations of the places list on the provided GoogleMap object
*/
private fun addMarkers(googleMap: GoogleMap) {
places.forEach { place ->
val marker = googleMap.addMarker(
MarkerOptions()
.title(place.name)
.position(place.latLng)
)
}
}
Cette méthode itère la liste de places
, puis appelle la méthode addMarker()
sur l'objet GoogleMap
fourni. Le repère est créé en instanciant un objet MarkerOptions
, qui vous permet de le personnaliser. Dans ce cas, le titre et la position du repère sont indiqués. Ils représentent respectivement le nom les coordonnées du magasin de vélos.
- Exécutez l'application et affichez San Francisco pour voir les repères que vous venez d'ajouter.
7. Personnaliser les repères
Il existe plusieurs options de personnalisation vous permettant de mettre en valeur les repères que vous venez d'ajouter et de fournir des informations pertinentes aux utilisateurs. Dans cette tâche, vous allez découvrir quelques-unes de ces options en personnalisant l'image des repères, ainsi que la fenêtre d'informations qui s'affiche lorsque l'utilisateur appuie sur l'un d'eux.
Ajouter une fenêtre d'informations
Lorsque vous appuyez sur un repère, la fenêtre d'informations affiche par défaut le titre et l'extrait associés (s'ils sont définis). Avec cette option, vous pouvez la personnaliser pour fournir des renseignements supplémentaires tels que l'adresse et la note.
Créer marker_info_contents.xml
Commencez par créer un fichier de mise en page appelé marker_info_contents.xml
.
- Pour ce faire, effectuez un clic droit sur le dossier
app/src/main/res/layout
dans la vue du projet dans Android Studio, puis sélectionnez New (Nouveau) > Layout Resource File (Fichier de ressource de mise en page).
- Dans la boîte de dialogue, saisissez
marker_info_contents
dans le champ File name (Nom du fichier) etLinearLayout
dans le champRoot element
(Élément racine), puis cliquez sur OK.
Ce fichier de mise en page est ensuite gonflé pour représenter le contenu dans la fenêtre d'informations.
- Copiez le contenu dans l'extrait de code suivant, qui ajoute trois
TextViews
dans un groupe d'affichage verticalLinearLayout
et écrase le code par défaut dans le fichier.
marker_info_contents.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:padding="8dp">
<TextView
android:id="@+id/text_view_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold"
tools:text="Title"/>
<TextView
android:id="@+id/text_view_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:textSize="16sp"
tools:text="123 Main Street"/>
<TextView
android:id="@+id/text_view_rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:textSize="16sp"
tools:text="Rating: 3"/>
</LinearLayout>
Créer une implémentation d'InfoWindowAdapter
Après avoir créé le fichier de mise en page pour la fenêtre d'informations personnalisée, l'étape suivante consiste à implémenter l'interface GoogleMap.InfoWindowAdapter. Cette interface contient deux méthodes : getInfoWindow()
et getInfoContents()
. Elles renvoient toutes deux un objet View
facultatif, dans lequel la première méthode est utilisée pour personnaliser la fenêtre elle-même et la seconde pour personnaliser son contenu. Dans cet atelier, vous allez implémenter les deux méthodes et personnaliser le résultat de getInfoContents()
en renvoyant la valeur nulle dans getInfoWindow()
, ce qui indique que la fenêtre par défaut doit être utilisée.
- Créez un fichier Kotlin nommé
MarkerInfoWindowAdapter
dans le même package queMainActivity
. Pour cela, effectuez un clic droit sur le dossierapp/src/main/java/com/google/codelabs/buildyourfirstmap
dans la vue du projet dans Android Studio, puis sélectionnez New (Nouveau) > Kotlin File/Class (Ficher/Classe Kotlin).
- Dans la boîte de dialogue, saisissez
MarkerInfoWindowAdapter
et laissez l'option File (Fichier) en surbrillance.
- Une fois le fichier créé, copiez dans celui-ci le contenu de l'extrait de code suivant.
MarkerInfoWindowAdapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.model.Marker
import com.google.codelabs.buildyourfirstmap.place.Place
class MarkerInfoWindowAdapter(
private val context: Context
) : GoogleMap.InfoWindowAdapter {
override fun getInfoContents(marker: Marker?): View? {
// 1. Get tag
val place = marker?.tag as? Place ?: return null
// 2. Inflate view and set title, address, and rating
val view = LayoutInflater.from(context).inflate(
R.layout.marker_info_contents, null
)
view.findViewById<TextView>(
R.id.text_view_title
).text = place.name
view.findViewById<TextView>(
R.id.text_view_address
).text = place.address
view.findViewById<TextView>(
R.id.text_view_rating
).text = "Rating: %.2f".format(place.rating)
return view
}
override fun getInfoWindow(marker: Marker?): View? {
// Return null to indicate that the
// default window (white bubble) should be used
return null
}
}
Dans le contenu de la méthode getInfoContents()
, le repère fourni dans la méthode est casté en type Place
. S'il n'est pas possible de le caster, la méthode renvoie une valeur nulle. Vous n'avez pas encore défini la propriété de la balise sur le repère (Marker
), mais vous le ferez à l'étape suivante.
Ensuite, la mise en page marker_info_contents.xml
est gonflée, puis le texte sur les TextViews
correspondants est défini sur la balise Place
.
Mettre à jour MainActivity
Pour assembler tous les composants que vous avez créés jusqu'à présent, vous devez ajouter deux lignes dans votre classe MainActivity
.
Tout d'abord, pour transmettre l'InfoWindowAdapter
personnalisé, MarkerInfoWindowAdapter
, dans l'appel de la méthode getMapAsync
, appelez la méthode setInfoWindowAdapter()
sur l'objet GoogleMap
et créez une instance de MarkerInfoWindowAdapter
.
- Pour ce faire, ajoutez le code suivant après l'appel de la méthode
addMarkers()
dans le lambdagetMapAsync()
.
MainActivity.onCreate()
// Set custom info window adapter
googleMap.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))
Enfin, vous devez définir chaque "Place" en tant que propriété de balise à chaque "Marker" ajouté à la carte.
- Pour ce faire, modifiez l'appel à
places.forEach{}
dans la fonctionaddMarkers()
comme suit :
MainActivity.addMarkers()
places.forEach { place ->
val marker = googleMap.addMarker(
MarkerOptions()
.title(place.name)
.position(place.latLng)
.icon(bicycleIcon)
)
// Set place as the tag on the marker object so it can be referenced within
// MarkerInfoWindowAdapter
marker.tag = place
}
Ajouter une image de repère personnalisée
Personnaliser l'image du repère permet de communiquer de manière conviviale le type de lieu qu'il représente sur votre carte. Dans cette étape, vous allez afficher des vélos à la place des repères rouges par défaut pour représenter chaque magasin sur la carte. Le projet initial inclut dans app/src/res/drawable
l'icône Vélo ic_directions_bike_black_24dp.xml
que vous utiliserez.
Définir le bitmap personnalisé sur le repère
L'icône Vélo étant à votre disposition sous forme de drawable vectoriel, l'étape suivante consiste à le définir pour chaque repère sur la carte. Pour cela, MarkerOptions
comporte une méthode icon
, qui prend un BitmapDescriptor
.
Tout d'abord, vous devez convertir le drawable vectoriel que vous venez d'ajouter dans un BitmapDescriptor
. Un fichier nommé BitMapHelper
inclus dans le projet initial contient une fonction d'assistance appelée vectorToBitmap()
conçue pour réaliser cette tâche.
BitmapHelper
package com.google.codelabs.buildyourfirstmap
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.util.Log
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.DrawableCompat
import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.BitmapDescriptorFactory
object BitmapHelper {
/**
* Demonstrates converting a [Drawable] to a [BitmapDescriptor],
* for use as a marker icon. Taken from ApiDemos on GitHub:
* https://github.com/googlemaps/android-samples/blob/main/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/MarkerDemoActivity.kt
*/
fun vectorToBitmap(
context: Context,
@DrawableRes id: Int,
@ColorInt color: Int
): BitmapDescriptor {
val vectorDrawable = ResourcesCompat.getDrawable(context.resources, id, null)
if (vectorDrawable == null) {
Log.e("BitmapHelper", "Resource not found")
return BitmapDescriptorFactory.defaultMarker()
}
val bitmap = Bitmap.createBitmap(
vectorDrawable.intrinsicWidth,
vectorDrawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
vectorDrawable.setBounds(0, 0, canvas.width, canvas.height)
DrawableCompat.setTint(vectorDrawable, color)
vectorDrawable.draw(canvas)
return BitmapDescriptorFactory.fromBitmap(bitmap)
}
}
Cette méthode utilise un Context
, un ID de ressource drawable et une couleur sous forme d'un entier, et crée une représentation BitmapDescriptor
du drawable vectoriel.
Déclarer une nouvelle propriété bicycleIcon
et lui attribuer la définition MainActivity.bicycleicon avec la méthode d'assistance
private val bicycleIcon: BitmapDescriptor by lazy {
val color = ContextCompat.getColor(this, R.color.colorPrimary)
BitmapHelper.vectorToBitmap(this, R.drawable.ic_directions_bike_black_24dp, color)
}
Cette propriété utilise la couleur prédéfinie colorPrimary
de votre application pour teinter l'icône Vélo et la renvoyer en tant que BitmapDescriptor
.
- Avec cette propriété, vous pouvez maintenant appeler la méthode
icon
deMarkerOptions
dans la méthodeaddMarkers()
pour terminer de personnaliser l'icône. La propriété du repère devrait alors ressembler à ceci :
MainActivity.addMarkers()
val marker = googleMap.addMarker(
MarkerOptions()
.title(place.name)
.position(place.latLng)
.icon(bicycleIcon)
)
- Lancez l'application pour voir les nouveaux repères.
8. Regrouper les repères
Selon le niveau de zoom sur la carte, vous remarquez peut-être que les repères que vous avez ajoutés se chevauchent. Les interactions deviennent alors difficiles, et les repèrent gênent la vue ce qui nuit à l'utilisabilité de votre application.
Pour améliorer l'expérience utilisateur, dès lors que vous disposez d'un ensemble de données volumineux et compact, regrouper les repères est une bonne pratique. Avec cette méthode, lorsque vous faites un zoom avant ou arrière sur la carte, les repères proches les uns des autres sont regroupés comme suit :
Pour implémenter le regroupement des repères, vous avez besoin de la bibliothèque d'utilitaires du SDK Maps pour Android.
Bibliothèque d'utilitaires du SDK Maps pour Android
La bibliothèque d'utilitaires a été créée pour élargir les possibilités offertes par le SDK Maps pour Android. Elle propose des fonctionnalités avancées telles que le regroupement de repères, les cartes de densité, la prise en charge de KML et de GeoJson, l'encodage et le décodage de polylignes, et quelques fonctions d'assistance pour la géométrie sphérique.
Mettre à jour build.gradle
Étant donné que la bibliothèque d'utilitaires n'est pas fournie dans le même package que le SDK Maps pour Android, vous devez ajouter une dépendance à votre fichier build.gradle
.
- Pour cela, modifiez la section
dependencies
de votre fichierapp/build.gradle
.
build.gradle
implementation 'com.google.maps.android:android-maps-utils:1.1.0'
- Une fois que vous avez ajouté cette ligne, vous devez synchroniser le projet pour récupérer les nouvelles dépendances.
Implémenter le regroupement
Pour regrouper les repères dans votre application, suivez ces trois étapes :
- Implémentez l'interface
ClusterItem
. - Créez une sous-classe de la classe
DefaultClusterRenderer
. - Créez un
ClusterManager
et ajoutez des éléments.
Implémenter l'interface ClusterItem
Tous les objets représentant un repère regroupable sur la carte doivent implémenter l'interface ClusterItem
. Dans votre cas, cela signifie que le modèle Place
doit être conforme à ClusterItem
. Ouvrez le fichier Place.kt
et modifiez-le comme suit :
Place
data class Place(
val name: String,
val latLng: LatLng,
val address: String,
val rating: Float
) : ClusterItem {
override fun getPosition(): LatLng =
latLng
override fun getTitle(): String =
name
override fun getSnippet(): String =
address
}
L'objet ClusterItem définit les trois méthodes suivantes :
getPosition()
, qui représente les coordonnéesLatLng
du lieugetTitle()
, qui représente le nom du lieugetSnippet()
, qui représente l'adresse du lieu
Créer une sous-classe de la classe DefaultClusterRenderer
La classe chargée d'implémenter le regroupement, ClusterManager
, utilise en interne une classe ClusterRenderer
pour regrouper les repères lorsque vous déplacez et zoomez la carte. Par défaut, elle utilise le moteur de rendu DefaultClusterRenderer
qui implémente ClusterRenderer
. Dans les scénarios simples, cette classe est suffisante. Toutefois, dans votre cas, étant donné que les repères doivent être personnalisés, vous devez étendre cette classe et y ajouter les personnalisations nécessaires.
Créez le fichier Kotlin PlaceRenderer.kt
dans le package com.google.codelabs.buildyourfirstmap.place
, puis définissez-le comme suit :
PlaceRenderer
package com.google.codelabs.buildyourfirstmap.place
import android.content.Context
import androidx.core.content.ContextCompat
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.Marker
import com.google.android.gms.maps.model.MarkerOptions
import com.google.codelabs.buildyourfirstmap.BitmapHelper
import com.google.codelabs.buildyourfirstmap.R
import com.google.maps.android.clustering.ClusterManager
import com.google.maps.android.clustering.view.DefaultClusterRenderer
/**
* A custom cluster renderer for Place objects.
*/
class PlaceRenderer(
private val context: Context,
map: GoogleMap,
clusterManager: ClusterManager<Place>
) : DefaultClusterRenderer<Place>(context, map, clusterManager) {
/**
* The icon to use for each cluster item
*/
private val bicycleIcon: BitmapDescriptor by lazy {
val color = ContextCompat.getColor(context,
R.color.colorPrimary
)
BitmapHelper.vectorToBitmap(
context,
R.drawable.ic_directions_bike_black_24dp,
color
)
}
/**
* Method called before the cluster item (the marker) is rendered.
* This is where marker options should be set.
*/
override fun onBeforeClusterItemRendered(
item: Place,
markerOptions: MarkerOptions
) {
markerOptions.title(item.name)
.position(item.latLng)
.icon(bicycleIcon)
}
/**
* Method called right after the cluster item (the marker) is rendered.
* This is where properties for the Marker object should be set.
*/
override fun onClusterItemRendered(clusterItem: Place, marker: Marker) {
marker.tag = clusterItem
}
}
Cette classe ignore ces deux fonctions :
onBeforeClusterItemRendered()
, qui est appelée avant le rendu du regroupement sur la carte. C'est là que vous pouvez apporter des personnalisations viaMarkerOptions
. Dans ce cas, elle définit le titre, la position et l'icône du repère.onClusterItemRenderer()
, qui est appelée juste après le rendu du repère sur la carte. C'est là que vous pouvez accéder à l'objetMarker
créé. Dans ce cas, elle définit la propriété de balise du repère.
Créer un ClusterManager et ajouter des éléments
Enfin, afin que le regroupement fonctionne, vous devez modifier MainActivity
pour instancier un ClusterManager
et lui fournir les dépendances nécessaires. ClusterManager
ajoute les repères (les objets ClusterItem
) en interne. Ainsi, au lieu d'être ajoutés directement sur la carte, les repères sont positionnés par ClusterManager
. En outre, ClusterManager
appelle également setInfoWindowAdapter()
en interne. Par conséquent, la définition d'une fenêtre d'informations personnalisée doit être effectuée sur l'objet MarkerManager.Collection
ClusterManger
.
- Pour commencer, modifiez le contenu du lambda de l'appel de
getMapAsync()
dansMainActivity.onCreate()
. Vous pouvez maintenant mettre l'appel deaddMarkers()
et desetInfoWindowAdapter()
en commentaire, puis appeler une méthode nomméeaddClusteredMarkers()
, que vous définirez ensuite.
MainActivity.onCreate()
mapFragment?.getMapAsync { googleMap ->
//addMarkers(googleMap)
addClusteredMarkers(googleMap)
// Set custom info window adapter.
// googleMap.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))
}
- Ensuite, dans
MainActivity
, définissezaddClusteredMarkers()
.
MainActivity.addClusteredMarkers()
/**
* Adds markers to the map with clustering support.
*/
private fun addClusteredMarkers(googleMap: GoogleMap) {
// Create the ClusterManager class and set the custom renderer.
val clusterManager = ClusterManager<Place>(this, googleMap)
clusterManager.renderer =
PlaceRenderer(
this,
googleMap,
clusterManager
)
// Set custom info window adapter
clusterManager.markerCollection.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))
// Add the places to the ClusterManager.
clusterManager.addItems(places)
clusterManager.cluster()
// Set ClusterManager as the OnCameraIdleListener so that it
// can re-cluster when zooming in and out.
googleMap.setOnCameraIdleListener {
clusterManager.onCameraIdle()
}
}
Cette méthode instancie un ClusterManager
, lui transmet le moteur de rendu personnalisé PlacesRenderer
, ajoute tous les lieux, puis appelle la méthode cluster()
. De plus, comme ClusterManager
utilise la méthode setInfoWindowAdapter()
sur l'objet de carte, la définition de la fenêtre d'informations personnalisée doit être effectuée sur l'objet ClusterManager.markerCollection
. Enfin, comme vous souhaitez modifier le regroupement lorsque l'utilisateur déplace et zoome la carte, OnCameraIdleListener
est fourni à googleMap
pour appeler clusterManager.onCameraIdle()
quand l'appareil photo est inactif.
- Lancez l'application pour voir les magasins regroupés.
9. Dessiner sur la carte
Vous avez déjà exploré une méthode pour dessiner sur la carte (en ajoutant des repères), mais le SDK Maps pour Android propose de nombreuses autres façons d'afficher des informations utiles.
Par exemple, si vous souhaitez afficher des itinéraires et des zones sur la carte, vous pouvez utiliser des polylignes et des polygones. Si vous souhaitez appliquer une image à la surface du sol, vous pouvez également utiliser des superpositions au sol.
Dans cette tâche, vous allez apprendre à dessiner des formes. Plus précisément, vous allez tracer un cercle à afficher autour d'un repère lorsque l'utilisateur appuie dessus.
Ajouter un écouteur de clics
Généralement, pour ajouter un écouteur de clics à un repère, il faut le transmettre directement à l'objet GoogleMap
via setOnMarkerClickListener()
. Toutefois, comme vous utilisez le regroupement, l'écouteur de clics doit être fourni à ClusterManager
.
- Dans la méthode
addClusteredMarkers()
deMainActivity
, ajoutez la ligne suivante juste après l'appel decluster()
.
MainActivity.addClusteredMarkers()
// Show polygon
clusterManager.setOnClusterItemClickListener { item ->
addCircle(googleMap, item)
return@setOnClusterItemClickListener false
}
Cette méthode ajoute un écouteur et appelle la méthode addCircle()
, que vous définirez ensuite. Enfin, elle renvoie false
pour indiquer qu'elle n'a pas utilisé cet événement.
- Ensuite, vous devez définir la propriété
circle
et la méthodeaddCircle()
dansMainActivity
.
MainActivity.addCircle()
private var circle: Circle? = null
/**
* Adds a [Circle] around the provided [item]
*/
private fun addCircle(googleMap: GoogleMap, item: Place) {
circle?.remove()
circle = googleMap.addCircle(
CircleOptions()
.center(item.latLng)
.radius(1000.0)
.fillColor(ContextCompat.getColor(this, R.color.colorPrimaryTranslucent))
.strokeColor(ContextCompat.getColor(this, R.color.colorPrimary))
)
}
La propriété circle
est définie pour supprimer le cercle précédent et en ajouter un chaque fois qu'un nouveau repère est sélectionné. Notez que l'API qui permet d'ajouter un cercle est très semblable à celle qui permet d'ajouter un repère.
- Exécutez l'application pour voir les modifications.
10. Contrôler la caméra
Pour votre dernière tâche, vous découvrirez certaines commandes de la caméra qui vous permettront de centrer la vue sur une région spécifique.
Caméra et vue
Vous l'avez peut-être remarqué, lorsque vous exécutez l'application, la caméra affiche le continent africain. Vous devez prendre le temps de déplacer la carte et de zoomer sur San Francisco pour trouver les repères que vous avez ajoutés. Même si cela peut être un bon moyen d'explorer le monde en s'amusant, vous préférerez sans doute afficher directement les repères.
Pour cela, vous pouvez régler la position de la caméra via le programmatique afin de centrer la vue à l'endroit qui vous intéresse.
- Ajoutez le code suivant à l'appel de
getMapAsync()
pour ajuster la vue de la caméra afin qu'elle soit initialisée sur San Francisco au lancement de l'application.
MainActivity.onCreate()
mapFragment?.getMapAsync { googleMap ->
// Ensure all places are visible in the map.
googleMap.setOnMapLoadedCallback {
val bounds = LatLngBounds.builder()
places.forEach { bounds.include(it.latLng) }
googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds.build(), 20))
}
}
D'abord, setOnMapLoadedCallback()
est appelé pour que la caméra ne soit mise à jour qu'une fois la carte chargée. Cette étape est nécessaire, car les propriétés de la carte (telles que les dimensions) doivent être calculées avant que la mise à jour de la caméra soit appelée.
Dans le lambda, un nouvel objet LatLngBounds
est construit. Il définit une région rectangulaire sur la carte. Cet objet est compilé de manière incrémentale en incluant toutes les valeurs LatLng
de "place", afin que la région englobe tous les magasins. Ensuite, la méthode moveCamera()
est appelée sur GoogleMap
, et un CameraUpdate
lui est fourni via CameraUpdateFactory.newLatLngBounds(bounds.build(), 20)
.
- Exécutez l'application et notez que la caméra est désormais initialisée sur San Francisco.
Écouter les changements au niveau de la caméra
En plus de modifier la position de la caméra, vous pouvez également écouter les modifications lorsque l'utilisateur se déplace sur la carte. Cela peut être utile si vous voulez modifier l'UI quand la caméra bouge.
Pour vous exercer, vous pouvez modifier le code afin de rendre les repères translucides lorsque la caméra se déplace.
- Dans la méthode
addClusteredMarkers()
, ajoutez les lignes suivantes en bas de la méthode :
MainActivity.addClusteredMarkers()
// When the camera starts moving, change the alpha value of the marker to translucent.
googleMap.setOnCameraMoveStartedListener {
clusterManager.markerCollection.markers.forEach { it.alpha = 0.3f }
clusterManager.clusterMarkerCollection.markers.forEach { it.alpha = 0.3f }
}
Cela ajoute un OnCameraMoveStartedListener
pour que, lorsque la caméra se déplace, toutes les valeurs alpha des repères et des regroupements de repères soient modifiées en 0.3f
, ce qui les rend translucides.
- Enfin, pour opacifier à nouveau les repères lorsque la caméra ne bouge plus, modifiez le contenu de
setOnCameraIdleListener
dans la méthodeaddClusteredMarkers()
comme suit :
MainActivity.addClusteredMarkers()
googleMap.setOnCameraIdleListener {
// When the camera stops moving, change the alpha value back to opaque.
clusterManager.markerCollection.markers.forEach { it.alpha = 1.0f }
clusterManager.clusterMarkerCollection.markers.forEach { it.alpha = 1.0f }
// Call clusterManager.onCameraIdle() when the camera stops moving so that reclustering
// can be performed when the camera stops moving.
clusterManager.onCameraIdle()
}
- Exécutez l'application pour voir le résultat.
11. Maps KTX
Pour les applications Kotlin utilisant un ou plusieurs SDK Android Google Maps Platform, l'extension Kotlin ou les bibliothèques KTX sont disponibles. Vous pouvez ainsi profiter des fonctionnalités du langage Kotlin comme les coroutines, les propriétés/fonctions d'extension, etc. Chaque SDK Google Maps possède une bibliothèque KTX correspondante, comme indiqué ci-dessous :
Dans cette tâche, vous allez utiliser les bibliothèques Maps KTX et Maps Utils KTX avec votre application, puis refactoriser les tâches précédentes afin d'utiliser les fonctionnalités linguistiques spécifiques à Kotlin dans votre application.
- Inclure les dépendances KTX dans le fichier build.gradle au niveau de l'application
Étant donné que l'application utilise à la fois le SDK Maps pour Android et la bibliothèque d'utilitaires du SDK Maps pour Android, vous devez inclure les bibliothèques KTX correspondantes pour ces bibliothèques. Vous allez également utiliser une fonctionnalité qui se trouve dans la bibliothèque KXX du cycle de vie AndroidX dans cette tâche. Incluez donc cette dépendance également dans votre fichier build.gradle
au niveau de l'application.
build.gradle
dependencies {
// ...
// Maps SDK for Android KTX Library
implementation 'com.google.maps.android:maps-ktx:3.0.0'
// Maps SDK for Android Utility Library KTX Library
implementation 'com.google.maps.android:maps-utils-ktx:3.0.0'
// Lifecycle Runtime KTX Library
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
}
- Utiliser les fonctions d'extension GoogleMap.addMarker() et GoogleMap.addCircle()
La bibliothèque Maps KTX fournit une alternative à l'API de style DSL pour les éléments GoogleMap.addMarker(MarkerOptions)
et GoogleMap.addCircle(CircleOptions)
utilisés aux étapes précédentes. Pour utiliser les API mentionnées ci-dessus, la création d'une classe contenant des options pour un repère ou un cercle est nécessaire, tandis qu'avec les alternatives KTX, vous pouvez définir les options du repère ou du cercle dans le lambda que vous fournissez.
Pour utiliser ces API, mettez à jour les méthodes MainActivity.addMarkers(GoogleMap)
et MainActivity.addCircle(GoogleMap)
:
MainActivity.addMarkers(GoogleMap)
/**
* Adds markers to the map. These markers won't be clustered.
*/
private fun addMarkers(googleMap: GoogleMap) {
places.forEach { place ->
val marker = googleMap.addMarker {
title(place.name)
position(place.latLng)
icon(bicycleIcon)
}
// Set place as the tag on the marker object so it can be referenced within
// MarkerInfoWindowAdapter
marker.tag = place
}
}
MainActivity.addCircle(GoogleMap)
/**
* Adds a [Circle] around the provided [item]
*/
private fun addCircle(googleMap: GoogleMap, item: Place) {
circle?.remove()
circle = googleMap.addCircle {
center(item.latLng)
radius(1000.0)
fillColor(ContextCompat.getColor(this@MainActivity, R.color.colorPrimaryTranslucent))
strokeColor(ContextCompat.getColor(this@MainActivity, R.color.colorPrimary))
}
}
Réécrire les méthodes ci-dessus de cette manière est beaucoup plus concis, ce qui est rendu possible à l'aide du littéral de fonction avec Kotlin de Kotlin.
- Utiliser SupportMapFragment.awaitMap() et GoogleMap.awaitMapLoad() pour les fonctions de suspension d'extensions
La bibliothèque Maps KTX fournit également des extensions de fonction de suspension à utiliser dans les coroutines. Plus précisément, il existe des alternatives aux fonctions de suspension pour SupportMapFragment.getMapAsync(OnMapReadyCallback)
et GoogleMap.setOnMapLoadedCallback(OnMapLoadedCallback)
. L'utilisation de ces API alternatives ne nécessite pas de transmettre des rappels, ce qui vous permet de recevoir la réponse de ces méthodes de manière synchrone et synchrone.
Étant donné que ces méthodes suspendent des fonctions, elles doivent être utilisées dans une coroutine. La bibliothèque Cycle de vie d'exécution du pipeline KTX propose une extension pour fournir des champs d'application des coroutines adaptées au cycle de vie, afin que les coroutines soient exécutées et arrêtées lors de l'événement de cycle de vie approprié.
En combinant ces concepts, mettez à jour la méthode MainActivity.onCreate(Bundle)
:
MainActivity.onCreate(Bundle)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mapFragment =
supportFragmentManager.findFragmentById(R.id.map_fragment) as SupportMapFragment
lifecycleScope.launchWhenCreated {
// Get map
val googleMap = mapFragment.awaitMap()
// Wait for map to finish loading
googleMap.awaitMapLoad()
// Ensure all places are visible in the map
val bounds = LatLngBounds.builder()
places.forEach { bounds.include(it.latLng) }
googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds.build(), 20))
addClusteredMarkers(googleMap)
}
}
La portée de la coroutine lifecycleScope.launchWhenCreated
exécute le bloc lorsque l'activité est au moins dans l'état créé. Notez également que les appels pour récupérer l'objet GoogleMap
et pour attendre que le chargement de la carte soit terminé, ont été remplacés par SupportMapFragment.awaitMap()
et GoogleMap.awaitMapLoad()
, respectivement. La refactorisation à l'aide de ces fonctions de suspension vous permet d'écrire le code basé sur le rappel équivalent de manière séquentielle.
- Vous allez devoir recompiler l'application avec vos modifications refactoriséess.
12. Félicitations
Félicitations ! Vous avez terminé un programme bien rempli. Nous espérons que vous connaissez mieux les principales fonctionnalités du SDK Maps pour Android.
Learn more
- SDK Places pour Android : explorez le vaste ensemble de données de lieux pour découvrir des établissements autour de vous.
- android-maps-ktx : cette bibliothèque Open Source s'intègre au SDK Maps pour Android et à la bibliothèque d'utilitaires du SDK Maps pour Android, tout en étant compatible avec Kotlin.
- android-place-ktx : bibliothèque Open Source s'intégrant au SDK Places pour Android, compatible avec Kotlin
- android-samples : exemples de code sur GitHub illustrant toutes les fonctionnalités abordées dans cet atelier de programmation, et bien d'autres.
- Autres ateliers de programmation Kotlin pour créer des applications Android avec Google Maps Platform