Google Maps Android Api v2: Guía De Mapas

Si andas buscando como usar la api de Google Maps v2 en Android, este tutorial es para ti.

Verás la forma de implementar las características de esta tecnología para crear apps similares a Easy Taxi, Uber, Trivago, etc.

Usar mapas en aplicaciones Android permite aumentar la utilidad de un servicio o característica que proporcione valor agregado y facilidades al usuario que requiera asociar ubicaciones a sus necesidades.

Si sigues leyendo, aprenderás a:

  1. Descargar y configurar los Google Play Services
  2. Obtener huella SHA-1
  3. Crear Nuevo Proyecto En Google Console Developers
  4. Instalar Google Play Services En Emulador
  5. Agregar api key a la app
  6. Añadir un mapa a tu aplicación
  7. Template MapActivity en Android Studio
  8. Gestos
  9. Controles UI
  10. Markers
  11. Tipos de mapas
  12. Mover la cámara
  13. Formas
  14. Manejo de eventos

Descargar Código De Google Maps En Android

Para seguir las lecciones, usaré una app de ejemplo llamada «Google Maps En Android». Puedes desbloquear el link de descarga convirtiéndote en suscriptor de Hermosa Programación:

[sociallocker id=»7121″]Descargar Gratis[/sociallocker]

Así que crea un nuevo proyecto en Android Studio con ese nombre y ve probando las enseñanzas del artículo.

1. Descargar Y Configurar Google Play Services

Google Play Services es un conjunto de librerías de Google que brindan a los desarrolladores las funcionalidades de las aplicaciones de Google como Gmail, Analytics, Google Fit, etc. Al igual que Google Maps.

Para agregar la dependencia en nuestro proyecto primero debes instalar el complemento disponible en el SDK.

Dentro de Android Studio dirígete a Tools > Android > SDK Manager:

Tools > Android > SDK Manager

En las opciones que verás, selecciona la pestaña SDK Tools, localiza el paquete Google Play Services y marca su respectivo checkbox.

Descargar Google Play Services

Al presionar OK debes confirmar el inicio de la instalación:

Confirmar instalación de Google Play Services

El siguiente paso es aceptar la licencia:

Aceptar licencia de SDK

Espera a que termine la descarga y pegado de los archivos:

Instalación de Google Play Services

Añadir dependencia a build.gradle

1. Ve al menú File > Project Structure…

2. Selecciona el módulo app, sitúate en la pestaña Dependencies y agrega una nueva Library Dependency.

Agregar nueva library dependency

3. Busca la librería indicando el texto descriptivo «com.google.android.gms» y selecciona aquella que tenga el complemento «play-services.maps:v» sin más.

Choose library dependency gms maps

4. Al confirmar, tus dependencias se actualizarán y la app será sincronizada.

Esta es la forma de agregación por interfaz. Pero si ya has editado manualmente tu archivo build.gradle con anterioridad solo usa el siguiente comando compile:

dependencies {
    ...
    compile 'com.google.android.gms:play-services-maps:8.4.0'
}

2. Obtener El Fingerprint SHA-1 De Tu App

La siguiente instancia es crear un nuevo proyecto en la consola para desarrolladores de Google, la cual permitirá habilitar el servicio web en los servidores de Google para proveer datos desde la api de Google Maps.

Pero cuando llegues allí y vayas a crear las credenciales para tu app Android, se te pedirá una clave que determina la existencia real de esta.

Su nombre es fingerprint (huella dactilar «digital») SHA-1.

Haz lo siguiente para obtenerla:

1. Localiza el archivo con la keystore usada para una app en modo debug. La documentación específica que dicho archivo se denomina debug.keystore y se encuentra en:

  • OS X y Linux: ~/.android/
  • Windows Vista y Windows 7: C:Usersnombre_usuario.android

En mi caso abro la terminal de Android Studio y navego hasta el directorio con el comando cd de la siguiente forma:

Abrir directorio .android

Me ubicó en la unidad C: y luego navego con:

 cd "C:/Users/Hermosa Programación/.android."

Usa las comillas para el nombre del directorio, esto te evitará problemas por espacios y caracteres especiales.

2. Usa la utilidad keytool para extraer la huella digital SHA-1 con el siguiente comando:

keytool -list -v -keystore "debug.keystore" -alias androiddebugkey -storepass android -keypass android

En mi caso la consola me arroja el siguiente print:

Obtener SHA-1 para Google Maps en Android Studio

Deja la consola abierta o salva el texto para el siguiente paso.

3. Crear Nuevo Proyecto En Google Console Developers

Para usar una api de Google es necesario crear un nuevo proyecto en Google Console Developers. Obviamente esto requiere que tengas una cuenta Google antes de manipular tu espacio.

La consola para desarrolladores de Google es una plataforma que te da acceso a la manipulación de proyectos web que implementarán una api. Desde allí podrás ver las apis propiedades y estadísticas de uso de cada proyecto.

Veamos cómo hacerlo.

1. Ingresa a tu Google Console Developers.

2. Selecciona el menú desplegable en la parte derecha de la toolbar que dice Selecciona un proyecto y selecciona Crear proyecto…

Crear proyecto en la consola para desarrolladores de Google

3. En el diálogo de creación asigna el nombre Google Maps En Android y confirma.

Crear nuevo proyecto en Google Developers Console

Si quieres puedes presionar Editar para cambiar el identificador de proyecto, por si tienes alguna convención de nombrado, ya que luego no será posible.

Al terminar la creación tendrás una notificación en la toolbar y el nuevo proyecto será seleccionado para administración.

Proyecto creado en consola Google

4. En la lista de apis populares busca la sección de APIs de Google Maps y selecciona la opción Google Maps Android API.

Google Maps Android

5. En la visión general del servicio presiona Habilitar.

Habilitar api de Google Maps en Android

Cuando el servicio esté activado se te advertirá que es necesario una clave de api para que tu app Android pueda hacer peticiones al servicio de Google Maps. Presiona Ir a las credenciales para obtener una.

Obtener clave de API para Google Maps

6. En el siguiente asistente selecciona Google Maps Android API como api destino y en la segunda Android. Confirma este par con el botón ¿Qué credenciales necesito?

Configurar clave de api

7. Usa el nombre Clave Google Maps En Android para la clave de API y restringe el alcance del servicio a la aplicación que vas a usar, añadiendo el nombre del paquete y huella digital SHA-1.

Agregar nombre de paqueta y huella digital SHA-1

El paquete puedes obtenerlo de tu AndroidManifest.xml y la huella digital solo es la copia que guardaste en la sección anterior. En mi caso quedaría así:

Crear clave API

Lo siguiente es confirmar la creación de la clave.

8. Finalmente la consola te entregará una clave de API para usar en tu proyecto Android Studio.

Esta es tu clave de API

Al presionar Listo podrás ver la sección Credenciales con tu nueva clave.

Lista de credenciales Google Console Developers

4. Instalar Google Play Services En Emulador

Si vas a usar tu móvil físico como fuente de pruebas de tu app no tendrás problemas para ejecutar tu app ya que muy rara vez los servicios de Google no estarán instalados.

Sin embargo los emuladores requieren una característica extra.

Google Play Services En AVDs de Android Studio

Para crear un AVD habilitado para GPS haz lo siguiente:

1. Abre el AVD Manager a través del siguiente icono:

AVD Manager En Android Studio

2. En el administrador que salió presiona el botón Create Virtual Device…

Create Virtual Device

3. Ahora te saldrá una lista con varios modelos de hardware para tu emulador. Dependiendo del tipo de dispositivo (TV, Wear, Phone o Tablet) que necesites así mismo elige la configuración que más te convenga.

En esta ocasión usaremos un Nexus 5X. Luego confirma con Next.

Select hardware device

4. Ahora es el turno de la imagen del sistema. Ubícate en la pestaña Recommended y elige la imagen con la versión que más te convenga.

Solo fíjate que el valor de la columna Target exprese la existencia de las apis de Google (with Google APIs). Con esto te aseguras de tener Google Play Services en tu AVD.

Si tu equipo corre es de arquitectura x86 y corre a 64 bits, entonces fíjate en elegir una imagen con el valor de su columna ABI en x86_64, de lo contrario usa x86.

Select System Image Android Marshmallow

Confirma presionando Next.

5. Finalmente verifica las características de tu emulador y cambia aquellas de las que te retractes. O aprovecha para presionar Show Advance Settings y configurar propiedades de hardware como memoria, almacenamiento, núcleos, etc.

Verify configuration avd en android studio

Confirma con Finish y espera que aparezca la lista de AVDs con tu nuevo emulador.

Lista de avds

Google Play Services en Genymotion

Para los que usamos Genymotion, existen varias construcciones de Google play Services que pueden ser flasheadas en el emulador que tengamos. Los siguientes son recursos muy buenos:

Como ejemplo te mostraré los pasos que seguí para instalar GPS en una imagen de Genymotion de un Nexus 5X con Android 6.0 (API 23).

Sigue estos pasos:

1. Descargar el adaptador Genymotion ARM Translation y arrastrarlo hacia el emulador en ejecución. Espera que se carguen los datos:

Drag and drop en genymotion

Cuando se complete, confirma con OK el flasheo del paquete.

Confirmar flasheo de ARM Translation

Reinicia el emulador de inmediato.

2. Descarga las Google APIs para Android 6.0 y realiza el mismo procedimiento. Arrastra y suelta. Confirmar y reinicia el emulador.

3. Ve al menú de aplicaciones de Android, selecciona Google y agrega una cuenta.

App Google en Android

4. Ahora descarga la compilación de Google Apps Benzo y aplica el mismo procedimiento de flasheo.

5. Con ello ya puedes ejecutar tu app con Google Play Services. Incluso ir a la Play Store para descargar las demás apps de Google.

Google Apps en PlayStore

5. Agregar Clave De API A la Aplicación

El funcionamiento de Google Maps en Android requiere que especifiques como meta información la clave asociada de Google Maps en la construcción de tu app.

Veamos.

1. Abre tu archivo AndroidManifest.xml y agrega un hijo <meta-data> al nodo <application> con la siguiente descripción:

<meta-data
    android:name="com.google.android.geo.API_KEY"
    android:value="TU_CLAVE_DE_API" />

Solo copia y pega la clave que obtuvimos en el paso anterior

<meta-data
    android:name="com.google.android.geo.API_KEY"
    android:value="AIzaSyC60P9Z53FX2kyqwJrvk2e_KhdtK6-cUPc" />

Con ello la app queda autorizada para realizar peticiones al servicio de Google Maps creado.

6. Añadir Un Mapa A La Aplicación

Ingredientes:

  1. Añadir MapFragment o SupportMapFragment
  2. Realizar transacción de fragmentos con Fragment Manager
  3. Implementar OnMapReadyCallback en la actividad y sobrescribe onMapReady()
  4. Llamar a getMapAsync()

Para desplegar datos de Google Maps API usaremos clase MapFragment en versiones mayores o igual a 12. Para el soporte de antecesores usa SupportMapFragment.

Este fragmento administra totalmente la creación, actualización y destrucción de los mapas en la vista.

Por otro lado, las respuestas web serán manejadas por OnMapReadyCallback. Esta escucha implementa el modelo asíncrono de los servicios de Google, para avisarte en qué momento el mapa está listo y te entrega una referencia del mismo que puedes manipular.

Preparación:

1. Ve al menú File > New > Fragment > Fragment (Blank) y crea un fragmento llamado FirstMapFragment.

No le agregues layout ni interfaz de comunicación.

Nuevo fragmento en Android Studio

Incluir un layout no es necesario ya que MapFragment tiene una forma de creación interna.

La interfaz de comunicación no será necesaria porque en este ejemplo no enviaremos eventos desde el fragmento hacia la actividad.

Cuando tengas la nueva clase creada, hazla heredar de SupportMapFragment. Luego sobrescribe onCreateView() para que la vista sea inflada desde la superclase con super.

Con ello tendrás:

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.google.android.gms.maps.SupportMapFragment;


/**
 * Muestra el mapa
 */
public class FirstMapFragment extends SupportMapFragment {

    public FirstMapFragment() {
    }

    public static FirstMapFragment newInstance() {
        return new FirstMapFragment();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View root = super.onCreateView(inflater, container, savedInstanceState);

        return root;
    }

}

Otra manera: Añadir un nodo <fragment>

Otra forma alternativa para añadir un fragmento de mapas es usar en el layout de la actividad una etiqueta <fragment> cuya referencia sea MapFragment o SupportMapFragment.

En nuestro caso se añade el siguiente contenido a activity_first_map.xml:

<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/map"
    android:name="com.google.android.gms.maps.SupportMapFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FirstMapActivity" />

Hazlo de esta forma si el manejo del contenido del mapa es sencillo. Pero si deseas sobrescribir propiedades, comportamientos e información, será mucho más útil tener la clase del fragmento desligada del layout.

2. Añade un fragmento dinámicamente con el Fragment Manager.

Solo obtén una instancia relacionada al contexto con getSupportFragmentManager() y realiza una transacción add() en onCreate() de la actividad.

public class FirstMapActivity extends AppCompatActivity {

    private FirstMapFragment mFirstMapFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first_map);

        mFirstMapFragment = FirstMapFragment.newInstance();
        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.map_container, mFirstMapFragment)
                .commit();
    }
}

3. Implementa en FirstMapActivity la interfaz OnMapReadyCallback y sobrescribe su controlador onMapReady().

Recuerda que puedes usar Alt + Insert en Android Studio para generar el cuerpo de los métodos obligatorios a implementar. Solo selecciona la opción Implement Methods…

Android Studio: Generate Implement Methods...

El código de la actividad debe quedarte así:

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;

public class FirstMapActivity extends AppCompatActivity
        implements OnMapReadyCallback {

    private FirstMapFragment mFirstMapFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first_map);

        mFirstMapFragment = FirstMapFragment.newInstance();
        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.map_container, mFirstMapFragment)
                .commit();

    }

    @Override
    public void onMapReady(GoogleMap googleMap) {

    }
}

Ejecuta y verás el mapa.

Mapa de Google en App Android

4. Aunque el mapa está cargando, onMapReady() no está siendo llamado debido a la ausencia de asociación.

Para ello, usa el método de asociación getMapAsync() para registrar tu actividad como escucha.

onCreate() es un buen lugar para hacerlo:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_first_map);

    mFirstMapFragment = FirstMapFragment.newInstance();
    getSupportFragmentManager()
            .beginTransaction()
            .add(R.id.map_container, mFirstMapFragment)
            .commit();

    // Registrar escucha onMapReadyCallback
    mFirstMapFragment.getMapAsync(this);
}

Con esta acción onMapReady() te proporcionará la instancia del mapa cuando el servicio web de Google Maps responda con el contenido.

A propósito. Este controlador recibe un parámetro GoogleMap.

Se puede decir que esta clase es la médula de toda la API, ya que representa el mapa como tal y te permitirá manejar los gráficos en el mapa, transformar la cámara, escuchar eventos, tomar instantáneas, etc.

Por ejemplo…

Añadir un marcador que apunte a la ciudad Cali en Colombia. Y mover la cámara sobre este punto

Solución:

Un marcador es un icono que apunta a una ubicación sobre el mapa (lo verás en más detalle en la siguiente sección). Para crear uno usa el método addMarker() asociando la longitud y latitud de la ubicación.

El movimiento de cámara se realiza con moveCamera() involucrando de nuevo la ubicación.

Así que dentro de onMapReady() añade el siguiente código para probar el funcionamiento de la escucha. No te preocupes si no entiendes por el momento:

@Override
public void onMapReady(GoogleMap googleMap) {
    LatLng cali = new LatLng(3.4383, -76.5161);
    googleMap.addMarker(new MarkerOptions()
            .position(cali)
            .title("Cali la Sucursal del cielo"));

    CameraPosition cameraPosition = CameraPosition.builder()
            .target(cali)
            .zoom(10)
            .build();
    
    googleMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
}

Si ejecutas tendrás:

Mapa en App Android con Marker y movimiento de cámara

Guarda la instancia GoogleMap obtenida en onMapReady() en un campo global, que brinde futuros accesos.

7. Template Google Maps Activity En Android Studio

Android Studio trae consigo una plantilla para agilizar el uso de mapas en una actividad.

Para usarla sigue estos pasos:

1. Ubícate en tu paquete java y presiona Click derecho. Luego sigue la ruta New > Google > Google Maps Activity.

Google Maps Activity en Android Studio

2. Configura el nombre, layout, titulo, jerarquía y paquete de la actividad en el asistente que Android Studio despliega.

Configure Activity Maps en Android Studio

3. Al confirmar con Finish, se creará la actividad con su layout y además se agregará la meta información necesario al AndroidManifest.xml.

Adicional a eso, extraerá tu clave de api en un archivo de recursos strings con el cualificador debug llamado google_maps_api.xml.

<resources>
    <!--
    TODO: Before you run your application, you need a Google Maps API key.

    To get one, follow this link, follow the directions and press "Create" at the end:

    https://console.developers.google.com/flows/enableapi?apiid=maps_android_backend&keyType=CLIENT_SIDE_ANDROID&r=0F:6B:C8:91:57:37:DE:8D:5C:73:39:C6:09:CE:CC:C3:84:BB:D5:83%3Bcom.herprogramacion.templategooglemapsactivity

    You can also add your credentials to an existing key, using this line:
    0F:6B:C8:91:57:37:DE:8D:5C:73:39:C6:09:CE:CC:C3:84:BB:D5:83;com.herprogramacion.templategooglemapsactivity

    Alternatively, follow the directions here:
    https://developers.google.com/maps/documentation/android/start#get-key

    Once you have your key (it starts with "AIza"), replace the "google_maps_key"
    string in this file.
    -->
    <string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">YOUR_KEY_HERE</string>
</resources>

Solo reemplaza el contenido YOUR_KEY_HERE por la clave entregada en Google console developers.

Puedes manejar la clave de api en otro archivo de recursos con el cualificador release para cuando liberes tu app a la Play Store.

8. Gestos Móviles En El Mapa

Existen varios gestos que el usuario puede usar para cambiar la posición de la cámara en un mapa de Google Maps API. Con ello me refiero a desplazar el mapa, acercar/alejar, rotar y variar la inclinación.

Toma como base la siguiente ilustración de gestos móviles:

Gestos En Google Maps Android API

Zoom:

  • Double tap para acercar
  • Press and Tap para alejar
  • Press and drag para acercar/alejar
  • Spread para acercar
  • Pinch para alejar

Desplazamiento:

  • Drag para desplazar continuamente el mapa de forma omnidireccional
  • Flick para desplazar en pequeñas porciones el mapa de forma omnidireccional

Inclinación:

  • Drag x 2 dedos de forma vertical. Hacia arriba inclina hasta 90° y hacia abajo declina a 0°

Rotación:

  • Rotate para rotar el mapa a favor o en contra de las manecillas del reloj.

Multi-touch en Genymotion

Usa los siguientes comandos en Genymotion para simular gestos multi-touch:

Comandos Genymotion para Multi-touch

Multi-touch en AVDs

La versión 2.0 de Android Studio trajo consigo un soporte para gestos multi-touch bastante sencilla.

Los comandos son:

  • Double tap: Doble click
  • Drag/Flitch : Click izquierdo sostenido
  • Pinch/Spread : Ctrl+ click izquierdo sostenido de adentro hacia a fuera o viceversaGesto Pinch/Spread en AVD Android Studio
  • Rotate: Ctrl + click izquierdo sostenido siguiendo una circunferenciaRotate en AVD Android Studio
  • Press and tap: Ctrl + click derecho sostenido con movimiento vertical.Press and Tap En AVD Android Studio

Habilitar/Deshabilitar gestos de UI

Si deseas restringir el acceso de gestos hacia el usuario usa la clase UiSettings.

Esta configura las características de interfaz de usuario de la clase GoogleMap.

Para obtener su instancia del mapa cargado usa getUiSettings() y ten en cuenta los siguientes métodos relacionados a los gestos:

  • setZoomGesturesEnabled(boolean): Modifica la disponibilidad de los gestos de zoom. Usa false para deshabilitarlos.
  • setScrollGesturesEnabled(boolean): Realiza exactamente que el método anterior pero para los gestos de desplazamiento.
  • setTiltGesturesEnabled(boolean): Igual pero para la inclinación.
  • setRotateGesturesEnabled(boolean): La disponibilidad en gestos de rotación.
  • setAllGesturesEnabled(boolean): Modifica la disponibilidad de todos los gestos al mismo tiempo.

Por ejemplo…

Desactivar todos los gestos de desplazamiento e inclinación y mover la cámara a Estados Unidos

@Override
public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;

    UiSettings uiSettings = mMap.getUiSettings();
    uiSettings.setScrollGesturesEnabled(false);
    uiSettings.setTiltGesturesEnabled(false);

    mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
            new LatLng(40.3839, -100.9565), 2));

}

9. Controles de UI Sobre El Mapa

Los controles del mapa son views de acceso rápido para una facilitar acción o gesto. Podemos ver algunos de ellos en la siguiente imagen:

Controles de UI en Google Maps Android

El propósito de cada uno es:

  • Controles de zoom: Dos botones en la parte inferior derecha para acercar y alejar. Estos vienen deshabilitados por defecto.
  • Barra de tareas del mapa: Dos accesos que se muestran al tocar un marcador en la parte inferior derecha. Su función es dirigirte a la app de Google Maps.
  • Botón «Mi ubicación»: Botón ubicado en la parte superior derecha que se habilita cuando activas la capa la capa «My Location». Al ser presionado anima la cámara a la ubicación actual (punto azul) que marque tu dispositivo si es que hay una.
  • Brújula: Icono en la parte superior izquierda que aparece cuando la orientación e inclinación no son . Si la presionas la cámara se moverá a su posición estándar.

Si quieres modificar la disponibilidad de cada elemento, usa la instancia UiSettings del mapa y llama a uno de los siguientes métodos:

Controlador Método
Zoom setZoomControlsEnabled(boolen)
Brujula setCompassEnabled(boolean)
Botón «Mi ubicación» setMyLocationEnabled(boolean), setMyLocationButtonEnabled(boolean)
Barra de herramientas del mapa setMapToolbarEnabled(boolean)

Añadir botón de ubicación

1. El botón de ubicación requiere permisos de ubicación en nuestra app. Así que añade la siguiente línea a tu AndroidManifest.xml

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

En caso de que tu target sea Android Marshmallow hacia adelante debes preguntarle al usuario por los permisos en tiempo de ejecución.

2. Usa setMyLocationEnabled() con el valor de true.

public class ControlsActivity extends AppCompatActivity implements OnMapReadyCallback {

    private static final int LOCATION_REQUEST_CODE = 1;
    private GoogleMap mMap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_controls);

        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);

    }


    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;

        // Controles UI
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED) {
            mMap.setMyLocationEnabled(true);
        } else {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.ACCESS_FINE_LOCATION)) {
                // Mostrar diálogo explicativo
            } else {
                // Solicitar permiso
                ActivityCompat.requestPermissions(
                        this,
                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                        LOCATION_REQUEST_CODE);
            }
        }

        mMap.getUiSettings().setZoomControlsEnabled(true);

        // Marcadores
        mMap.addMarker(new MarkerOptions().position(new LatLng(0, 0)));


    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode == LOCATION_REQUEST_CODE) {
            // ¿Permisos asignados?
            if (permissions.length > 0 &&
                    permissions[0].equals(Manifest.permission.ACCESS_FINE_LOCATION) &&
                    grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                mMap.setMyLocationEnabled(true);
            } else {
                Toast.makeText(this, "Error de permisos", Toast.LENGTH_LONG).show();
            }

        }
    }
}

10. Markers O Marcadores en Google Maps

Como viste en la sección anterior, un Marker es un objeto interactivo que marca una ubicación específica en la superficie del mapa, para representar un lugar, establecimientos, objetos, etc.

Estructura de un Marker en Google Maps Android

Cuando un marker es presionado, lo normal es que aparezca una ventana de información (Info Window) que muestra información relacionada al marker y que la cámara se posicione en la ubicación del marker.

Aunque la posición donde se encuentre puedes fijarla con coordenadas, también es posible dejar que el usuario los arrastre y suelte en ubicaciones personalizadas.

A continuación veremos los casos más populares sobre su tratamiento.

Agregar un marker

Para añadir un marcador se usa el método addMarker().

public final Marker addMarker (MarkerOptions options)

Este método recibe un objeto MarkerOptions. Este actúa como un fabricador de markers basado en las propiedades que tú especifiques.

Su retorno es de tipo Marker, la clase que representa como tal el marker en pantalla.

Ejemplo…

Añadir un marker en japón (36.2048, 138.2529). Usar la etiqueta «Japón» y la descripción «Primer ministro: Shinzō Abe»

Solución:

Crea un MarkerOptions y usa:

  • position()
  • title()
  • snippet()

Luego añade la definición con addMarker() en onMapReady();

@Override
public void onMapReady(GoogleMap googleMap) {
    // Coordenadas Japón
    LatLng latLng = new LatLng(36.2048, 138.2529);

    MarkerOptions markerOptions =
            new MarkerOptions()
                    .position(latLng)
                    .title("Japón")
                    .snippet("Primer ministro: Shinzō Abe");

    Marker marker = googleMap.addMarker(markerOptions);
}

Al ejecutar y presionar el marker tendrás:

Marker con title y snippet

Personalizar un marker

La sección de Markers en Google Maps API nos muestra las propiedades posibles que puedes manipular para que tus marcadores cambien su aspecto. Veamos una tabla resumen de estas:

Propiedad Descripción Método en MarkerOptions
Posición Coordenadas de latitud y longitud para mover el marker en grados decimales.
Se representa por la clase LatLng.
position()
Anchor Punto del icono del marcador que representará la ubicación. Dicho punto se representa por coordenadas de textura (u,v)
El punto A de la ilustración anterior muestra la ubicación por defecto.
anchor()
Alpha Grado de opacidad del marcador.
0 es transparente y 1 es color total.
alpha()
Título Texto principal ubicado en la ventana de información title()
Snippet Text secundario ubicado por debajo del título snippet()
Icono Bitmap que representa al marker. Puedes cambiarlo por un recurso propio o usar el icono por defecto. icon()
Arrastre Atributo booleano que define si un marker puede ser arrastrado por el usuario.
Usa true para habilitarlo, o false (por defecto) en caso contrario.
draggable()
Visibilidad Determina si el marker es visible (true) o invisible (false) visible()
Geometría Determina si el icono del marker se transformará (true) con respecto a la posición de la cámara o se dibujará exactamente en la misma posición sin importar qué (false). flat()
Rotación Especifica la rotación del marcador en orden de las manecillas del reloj. rotation()

Apliquemos la tabla anterior para algunos ejemplos:

Ejemplo: Cambiar color del marker a Cyan.

Pensamiento 1. Si observas la tabla, el método a usar es icon().

public MarkerOptions icon (BitmapDescriptor icon)

Este recibe un parámetro BitmapDescriptor.

Esta clase define una imagen para los mapas en la API. La construcción de instancias se facilita a través de la clase fabricadora BitmapDescriptorFactory.

Pensamiento 2. Para cambiar el color del icono actual usaremos el método de clase BitmapDescriptorFactory.defaultMarker():

public static BitmapDescriptor defaultMarker (float hue)

Donde hue debe tener por preferencia un valor de color establecido en las siguientes constantes:

  • float HUE_AZURE
  • float HUE_BLUE
  • float HUE_CYAN
  • float HUE_GREEN
  • float HUE_MAGENTA
  • float HUE_ORANGE
  • float HUE_RED
  • float HUE_ROSE
  • float HUE_VIOLET
  • float HUE_YELLOW

Deducción: Usar HUE_CYAN con el método defaultMarker().

@Override
public void onMapReady(GoogleMap googleMap) {    

    LatLng japon2 = new LatLng(36.2048, 138.2529);
    googleMap.addMarker(new MarkerOptions()
            .position(japon2)
            .title("Marcador CYAN")
            .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_CYAN)));
    googleMap.moveCamera(CameraUpdateFactory.newLatLng(japon2));

}

Resultado

Cambiar color de marker a CYAN

Ejemplo: Cambiar icono del marker

1. Descarga o crea un recurso gráfico para tu marker.

Buscar imágenes de markers en Google

2. Ve al proyecto en Android Studio y añade el recurso en diferentes densidades en tu carpeta drawable.

3. Ahora desde onMapReady() crea el marcador y usa en las opciones el método icon().

Para producir la imagen puedes ayudarte de los siguientes métodos de clase:

  • fromAsset(String assetName): Genera un BitmapDescriptor a partir de una imagen en la carpeta assets
  • fromBitmap(Bitmap image): Aquí la generación es a partir de un objeto Bitmap
  • fromFile(String fileName): Se genera desde el nombre de la imagen en el almacenamiento interno
  • fromPath(String absolutePath): Se genera desde la ruta absoluta de la imagen en el almacenamiento
  • fromResource(int resourceId): Se genera desde los recursos del proyecto

Debido a que incluimos la imagen en drawable, usarás fromResource() con el id del recurso.

@Override
public void onMapReady(GoogleMap googleMap) {   

    LatLng position = new LatLng(10, 10);
    googleMap.addMarker(new MarkerOptions()
            .position(position)
            .title("Marcador con icono personalizado")
            .icon(BitmapDescriptorFactory.fromResource(R.drawable.marker_icon_pointer)));
    googleMap.moveCamera(CameraUpdateFactory.newLatLng(position));

}

Mi recurso se llama marker_icon_pointer.png y produce el siguiente resultado:

Icono de marker personalizada en Google Maps

Ejemplo: Hacer draggable un marker

1. Viendo la tabla resumida de propiedades sabemos que el método draggable() con true permitirá que el usuario arrastre el marcador a su preferencia.

Con esto el código quedaría así:

@Override
public void onMapReady(GoogleMap googleMap) {

    LatLng position = new LatLng(15, 15);
    googleMap.addMarker(new MarkerOptions()
            .position(position)
            .title("Marcador draggable")
            .draggable(true));
    googleMap.moveCamera(CameraUpdateFactory.newLatLng(position));

}

¿Cómo arrastro el marker?

Solo mantén presionado el marker por un instante y luego muévelo a cualquier posición al soltar la presión.

Funcionamiento de marker draggable

11. Manejar Eventos De Markers

La API de Google Maps provee escuchas de eventos sobre la clase GoogleMap para procesar los siguientes gestos en un marcador:

Acción del usuario Escucha responsable Controladores
Clickear el marker OnMarkerClickLister onMarkerClick(Marker marker)
Clickear la Info Window OnInfoWindowClickListener onInfoWindowClick(Marker marker)
Arrastrar el marker OnMarkerDragListener onMarkerDrag(Marker marker)
onMarkerDragEnd(Marker marker)
onMarkerDragStart(Marker marker)

Practiquemos su uso con algunos ejemplos…

Eventos al clickear un marcador

Iniciar una actividad nueva cuando se presione un marcador. Dicha actividad debe mostrar la longitud y latitud.

Solución

1. Implementa la escucha OnMarkerClickListener sobre FirstMapActivity y crea un marker global llamado markerPais.

public class MarkersActivity extends AppCompatActivity
        implements OnMapReadyCallback, GoogleMap.OnMarkerClickListener {

    private Marker markerPais;
    ...
}

2. Dentro de onMapReady() crea un marker que apunte a tu país y asignalo a markerPais.

@Override
public void onMapReady(GoogleMap googleMap) {

    // Markers
    LatLng colombia = new LatLng(4.6,-74.08);
    markerPais = googleMap.addMarker(new MarkerOptions()
            .position(colombia)
            .title("Colombia")
    );

    // Cámara
    googleMap.moveCamera(CameraUpdateFactory.newLatLng(colombia));

    // Eventos    

}

3. Usa setOnMarkerClickListener() para asignar la actividad como escucha de clicks a través del operador this.

// Eventos
googleMap.setOnMarkerClickListener(this);

4. Sobrescribe el método onMarkerClick() dentro de la actividad, para iniciar otra actividad llamada MarkerDetailActivity.

Cuando crees la nueva actividad, abre su layout (activity_marker_detail.xml) y ubica el siguiente diseño:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.herprogramacion.googlemapsenandroid.MarkerDetailActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="(%$1s, %$2s)"
        android:textSize="24sp"
        android:id="@+id/tv_latlng"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />
</RelativeLayout>

Ahora desde onMarkerClick() en FirstMapActivity, extrae la latitud y longitud del marker entrante. Luego envíalas como extras en el nuevo Intent implícito de creación para MarkerDetailActivity.

@Override
public boolean onMarkerClick(Marker marker) {
    if (marker.equals(markerPais)) {
        Intent intent = new Intent(this, MarkerDetailActivity.class);
        intent.putExtra(EXTRA_LATITUD, marker.getPosition().latitude);
        intent.putExtra(EXTRA_LONGITUD, marker.getPosition().longitude);

        startActivity(intent);
    }
    return false;
}

Aspectos a tener en cuenta del código anterior:

  • Usa una sentencia de condiciones (if o switch) para determinar si el marker entrante tiene la misma referencia del que deseas.
  • Si debes crear marcadores en tiempo real, usa ids. Las colecciones de la clase Map son una buena solución en este caso.
  • El método getPosition() de Marker permite obtener la posición con la que se creó. latitude y longitude son los valores de las coordenadas respectivamente.

5. Obtén los datos desde MarkerDetailActivity y setealos en el TextView de la vista.

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

public class MarkerDetailActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_marker_detail);

        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        // Extraer lat. y lng.
        Intent intent = getIntent();
        String latlng = String.format(
                getString(R.string.marker_detail_latlng),
                intent.getDoubleExtra(FirstMapActivity.EXTRA_LATITUD, 0),
                intent.getDoubleExtra(FirstMapActivity.EXTRA_LONGITUD, 0));

        // Poblar
        TextView coordenadas = (TextView) findViewById(R.id.tv_latlng);
        coordenadas.setText(latlng);
    }

    @Override
    public boolean onSupportNavigateUp() {
        onBackPressed();
        return super.onSupportNavigateUp();
    }
}

Si ejecutas tendrás un flujo similar a este:

Iniciar actividad al clickear marker

El fragmento de mapas que tenemos creado también puede implementar las escuchas de click y procesar los eventos.

Eventos al arrastrar un Marker

Mostrar un Toast cuando el usuario comience o termine de arrastrar un marcador en Ecuador. Mientras siga siendo arrastrado, cambiar el título de la toolbar por las coordenadas actuales.

Solución

1. Implementa OnMarkerDragListener sobre FirstMapActivity .

2. Al igual que en el ejemplo pasado, dentro de onMapReady() relaciona el objeto GoogleMap con la escucha. Para ello usa setOnMarkerDragListener().

Recuerda usar el método draggable() con true para dejar arrastrar el marcador.

LatLng ecuador = new LatLng(-0.217, -78.51);
markerEcuador = googleMap.addMarker(new MarkerOptions()
        .position(ecuador)
        .title("Ecuador")
        .draggable(true)
);

// Cámara
googleMap.moveCamera(CameraUpdateFactory.newLatLng(ecuador));

// Eventos
googleMap.setOnMarkerDragListener(this);

3. Sobrescribe los controladores onMarkerDrag(), onMarkerDragStart() y onMarkerDragEnd().

onMarkerDrag() se ejecuta cuando se comienza el arrastre, así que aquí crea un Toast con el mensaje "START".

@Override
public void onMarkerDragStart(Marker marker) {
    if (marker.equals(markerEcuador)) {
        Toast.makeText(this, "START", Toast.LENGTH_SHORT).show();
    }
}

onMarkerDrag() se consecutivamente llamado al arrastrar el marker. Por lo que aquí actualiza el título de la toolbar con las coordenadas.

@Override
public void onMarkerDrag(Marker marker) {
    if (marker.equals(markerEcuador)) {
        String newTitle = String.format(Locale.getDefault(),
                getString(R.string.marker_detail_latlng),
                marker.getPosition().latitude,
                marker.getPosition().longitude);

        setTitle(newTitle);
    }
}

R.string.marker_detail_latlng es una string formateada para mostra el par latitud-longitud:

<string name="marker_detail_latlng">(%1$.2f, %2$.2f)</string>

onMarkerDragEnd() se llama al soltar el marker. Ejecuta un Toast con el mensaje "END":

@Override
public void onMarkerDragEnd(Marker marker) {
    if (marker.equals(markerEcuador)) {
        Toast.makeText(this, "END", Toast.LENGTH_SHORT).show();
    }
}

Ejecuta la app y prueba la visibilidad de las señales anteriores:

Eventos de drag en marker

Eventos sobre la Info Window

Iniciar un diálogo con información extendida al presionar la ventana de información de un marker que apunte en Argentina.

Solución

1. Implementa sobre la actividad la escucha OnInfoWindowClickListener y crea un marker global llamado markerArgentina.

2. En onMapReady() crea el marker para Argentina y registra la escucha con setOnInfoWindowClickListener():

LatLng argentina = new LatLng(-34.6, -58.4);
markerArgentina = googleMap.addMarker(
        new MarkerOptions()
                .position(argentina)
                .title("Argentina")
);

// Cámara
googleMap.moveCamera(CameraUpdateFactory.newLatLng(argentina));

// Eventos
googleMap.setOnMarkerClickListener(this);
googleMap.setOnMarkerDragListener(this);
googleMap.setOnInfoWindowClickListener(this);

3. Sobrescribe el método onInfoWindowClick() con la creación de un nuevo diálogo.

Esto requiere que crees una nueva clase que extienda de DialogFragment. La idea es generar un diálogo de alerta con título y mensaje.

Llama la clase ArgentinaDialogFragment y pega el siguiente código:

import android.app.Dialog;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;

public class ArgentinaDialogFragment extends DialogFragment {

    public static final String ARGUMENTO_TITLE = "TITLE";
    public static final String ARGUMENTO_FULL_SNIPPET = "FULL_SNIPPET";

    private String title;
    private String fullSnippet;

    public ArgentinaDialogFragment() {
    }

    public static ArgentinaDialogFragment newInstance(String title, String fullSnippet) {
        ArgentinaDialogFragment fragment = new ArgentinaDialogFragment();
        Bundle b = new Bundle();
        b.putString(ARGUMENTO_TITLE, title);
        b.putString(ARGUMENTO_FULL_SNIPPET, fullSnippet);
        fragment.setArguments(b);
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle args = getArguments();

        title = args.getString(ARGUMENTO_TITLE);
        fullSnippet = args.getString(ARGUMENTO_FULL_SNIPPET);
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        Dialog dialog = new AlertDialog.Builder(getActivity())
                .setTitle(title)
                .setMessage(fullSnippet)
                .create();

        return dialog;
    }
}

Ahora desde onInfoWindowClick() crea una nueva instancia del diálogo. Añade el título del marker y asigna un recurso string con datos de Argentina:

@Override
public void onInfoWindowClick(Marker marker) {
    if (marker.equals(markerArgentina)) {

        ArgentinaDialogFragment.newInstance(marker.getTitle(),
                getString(R.string.argentina_full_snippet))
                .show(getSupportFragmentManager(), null);
    }
}

donde R.string.argentina_full_snippet tiene el siguiente extracto de wikipedia en strings.xml:

<string name="argentina_full_snippet">
La República Argentina, conocida simplemente como Argentina, 
es un país soberano de América del Sur, ubicado en el extremo sur y sudeste 
de dicho subcontinente. Adopta la forma de gobierno republicana, representativa y federal. 
El Estado nacional convive federativamente con veinticuatro entidades 
estatales autónomas, de las cuales veintitrés son provincias 
que preservan todo el poder no delegado constitucionalmente 
a la Nación y una es la Ciudad Autónoma de Buenos Aires, 
designada por ley como capital federal.
</string>

Resultado:

Iniciar diálogo de detalle al clickear InfoWindow de marker

Borrar Marker

La clase Marker nos proporciona el método remove() para borrar el marker del objeto GoogleMap. Tan solo llámalo desde la instancia a eliminar y listo:

markerArgentina.remove();

No mostrar la InfoWindow al presionar un marker

En la documentación de la escucha de clicks en los markers nos recomiendan que si deseamos evitar que se muestre la info window, solo debemos hacer retornar el método onMarkerClick() en true.

Por ejemplo, el marker de Colombia al ser presionado nos envía a otra actividad, sin embargo la ventana de información se muestra.

La modificación es sencilla:

@Override
public boolean onMarkerClick(Marker marker) {
    if (marker.equals(markerColombia)) {
        Intent intent = new Intent(this, MarkerDetailActivity.class);
        intent.putExtra(EXTRA_LATITUD, marker.getPosition().latitude);
        intent.putExtra(EXTRA_LONGITUD, marker.getPosition().longitude);

        startActivity(intent);
        
        return true;
    }

    return false;
}

Ahora, esta modificación no posiciona la cámara en el marker como normalmente vemos.

Para solucionar este incidente, usa tu instancia global del mapa con el método animateCamera().

Puedes añadir una escucha CancellableCallback para ejecutar la actividad solo cuando termine la animación (más adelante veremos más detalles).

@Override
public boolean onMarkerClick(final Marker marker) {
    if (marker.equals(markerColombia)) {

        map.animateCamera(CameraUpdateFactory.newLatLng(marker.getPosition()), new GoogleMap.CancelableCallback() {
            @Override
            public void onFinish() {
                Intent intent = new Intent(MarkersActivity.this, MarkerDetailActivity.class);
                intent.putExtra(EXTRA_LATITUD, marker.getPosition().latitude);
                intent.putExtra(EXTRA_LONGITUD, marker.getPosition().longitude);
                startActivity(intent);
            }

            @Override
            public void onCancel() {

            }
        });

        return true;

    }

    return false;
}

11. Tipos De Mapas En La API

Hasta ahora se ha visto una sola representación gráfica de los mapas que has visto en ejemplos.

Sin embargo hay más diseños que varían el detalle y estructura.

Resumiéndolos serían:

Tipo Contenido Constante asociada
Normal Carreteras y objetos naturales como vegetación o ríos MAP_TYPE_NORMAL
Híbrido Datos de carreteras junto a capturas satelitales MAP_TYPE_HYBRID
Satélite Capturas de satélites MAP_TYPE_SATELLITE
Tierra Detalles de relieve en el terreno y datos sobre carreteras MAP_TYPE_TERRAIN
Ninguno Una cuadrícula vacía MAP_TYPE_NONE

Para asignar el tipo de mapa programáticamente usa el método setMapType() con alguna de las constantes anteriores como parámetro.

Ejemplo: Cambiar tipo de mapa

Proporciona un Spinner al usuario para que cambie el tipo de mapa entre las 5 opciones disponibles

Solución

1. El layout de FirstMapActivity debe modificarse para ubicar un spinner en la parte superior y por debajo tener el mapa.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="@dimen/activity_horizontal_margin">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Tipo de mapa"
            android:textAppearance="@style/TextAppearance.AppCompat.Caption"
            android:textColor="@color/colorPrimary" />

        <Spinner
            android:id="@+id/map_type_selector"
            android:layout_width="match_parent"
            android:layout_height="?attr/listPreferredItemHeight"
            android:entries="@array/map_types_list" />
    </LinearLayout>

    <fragment
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

Recuerda que el atributo android:entries recibe un recurso <string-array> con la lista de las opciones que tendrá el spinner. Ya sabemos que estas deben ser los 5 tipos de mapas declaradas así:

strings.xml

<string-array name="map_types_list">
    <item>None</item>
    <item>Normal</item>
    <item>Satellite</item>
    <item>Hybrid</item>
    <item>Terrain</item>
</string-array>

2. Ahora ve a la actividad y realiza lo siguiente:

  • Declara una instancia global para el map
  • Obtén en onCreate() el Spinner map_type_selector y busca el fragmento con el Fragment Manager
  • Implementa sobre la actividad OnItemSelectedListener y relacionala con el Spinner.
  • Implementa OnMapReadyCallback
  • Llama a getMapAsync() y guarda la referencia GoogleMap .

Con ello tendrás un código como el siguiente:

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Spinner;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;

public class FirstMapActivity extends AppCompatActivity
        implements OnMapReadyCallback, AdapterView.OnItemSelectedListener {

    private SupportMapFragment mMapFragment;
    private GoogleMap mMap;
    private Spinner mMapTypeSelector;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_map_types);

        mMapTypeSelector = (Spinner) findViewById(R.id.map_type_selector);
        mMapTypeSelector.setOnItemSelectedListener(this);

        mMapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mMapFragment.getMapAsync(this);
    }

    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {

    }
}

3. Lo siguiente es usar el método setMapType() dentro de onItemSelected().

Como sabes que el orden de los ítems del spinner no variará, entonces puedes crear un arreglo de enteros para relacionar las constantes de tipo a la posición seleccionada.

private int mMapTypes[] = {
        GoogleMap.MAP_TYPE_NONE,
        GoogleMap.MAP_TYPE_NORMAL,
        GoogleMap.MAP_TYPE_SATELLITE,
        GoogleMap.MAP_TYPE_HYBRID,
        GoogleMap.MAP_TYPE_TERRAIN
};

Con esta variable podemos usar el tercer parámetro position de onItemSelected() de la siguiente forma:

@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
    mMap.setMapType(mMapTypes[position]);
}

Ejecuta la app y prueba los tipos:

Tipo de mapa Terrain en Android Tipo de mapa Satellite en Android Tipo de mapa None en Android

12. Mover La Cámara Del Mapa

La cámara es el punto de vista que refleja una cantidad de espacio o volumen en los mapas de la API de Google Maps.

En ejemplos anteriores vimos métodos para cambiar la posición y realizar zoom. La idea ahora es ver más características y utilidades que complementen tu conocimiento.

Las transformaciones de la cámara se representan con CameraUpdate y para producir instancias de esta usa CameraUpdateFactory.

Cambiar nivel de zoom

Los siguientes son métodos de clase de CameraUpdateFactory que modifican el zoom.

Método Descripción
zoomIn() Aumenta el zoom en 1.0
zoomOut() Disminuye el zoom en 1.0
zoomTo(float zoom) Modifica el zoom desde el valor actual hasta el proporcionado
zoomBy(float amount) Aumenta o disminuye (número negativo) el zoom en las unidades que desees.

Por ejemplo…

Aumentemos el zoom en 20 unidades al iniciar el mapa:

@Override
public void onMapReady(GoogleMap googleMap) {
    googleMap.moveCamera(CameraUpdateFactory.zoomBy(20));
}

Cambiar objetivo de la cámara

En las secciones anteriores movimos varias veces la cámara con nuevas coordenadas de latitud y longitud.

Pero veamos un resumen de los métodos que tenemos para hacerlo:

Método Descripción
newLatLng(LatLng latLng) Genera una instancia CameraUpdate que ubica el objetivo de la cámara en las nuevas coordenadas
newLatLngZoom(LatLng latLng, float zoom) Hace lo mismo que newLatLng() y adicionalmente le combina un nivel de zoom.

newLatLng() ya lo vimos grandes cantidades de veces, así que hagamos un ejemplo con su variante.

Cambiar la ubicación hacia Nicaragua y aumentar 7 niveles de zoom.

La solución sería la siguiente:

@Override
public void onMapReady(GoogleMap googleMap) {
    LatLng nicaragua = new LatLng(13, -85);
    googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(nicaragua, 7));
}

Nicaragua en Google Maps Android

Desplazar la cámara

También es posible crear instancias de CameraUpdate para mover en n pixeles las coordenadas de longitud y latitud, con respecto a la ubicación actual.

Esta transformación se logra con CameraUpdateFactory.scrollBy(float x, float y) donde,

  • Los valores positivos en x desplazan la cámara hacia la derecha. Los negativos hacia la izquierda.
  • Los valores positivos en y desplazan la cámara hacia abajo. Los negativos hacia arriba.

Por ejemplo…

Un desplazamiento de 100 pixeles a la derecha y 200 hacia arriba se da por.

mMap.moveCamera(CameraUpdateFactory.scrollBy(100,-200));

Personalizar la posición de la cámara

Si deseas cambiar todas las propiedades relacionadas a la posición de la cámara usa la clase CameraPosition.

Puedes crear una instancia con el operador new de forma normal o usar CameraPosition.Builder para facilitar el proceso.

La edición debes realizarla basado en los siguientes atributos:

  • float bearing: Orientación vertical medida en grados partiendo desde el norte en sentidos de las manecillas del reloj
  • LatLng target: La ubicación donde está apuntando la cámara
  • float tilt: Separación en ángulos desde la orientación de la cámara hasta el nadir.
  • float zoom: Nivel del zoom con respecto a la cámara

Cuando ya tengas preparado tu objeto, entonces usa el método CameraUpdateFactory.newCameraPosition() con el fin de crear un nuevo elemento CameraUpdate basado en tus argumentos.

Por ejemplo…

Ubicar la cámara apuntando a España, 7 niveles de zoom, alineada verticalmente 90° respecto al norte y con una inclinación de 90°

El código sería:

LatLng españa = new LatLng(40.416667, -3.75);
CameraPosition cameraPosition = new CameraPosition.Builder()
        .target(españa)
        .zoom(7)
        .bearing(90)
        .tilt(90)
        .build();
googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));

El resultado:

Mapa en app Android con modificación en Bearing y Tilt

Mover la vista de la cámara

Actualizar la posición de la cámara en el mapa se logra a través de dos métodos de GoogleMap: moveCamera() y animateCamera().

Ambos aplican el objeto CameraUpdate para actualizar el objetivo actual de la cámara en la vista. La diferencia es que animateCamera() anima el movimiento.

¿Cómo usar moveCamera()?

Esto ya lo viste varias veces. Solo produces un nuevo CameraUpdate con CameraUpdateFactory y lo pasas como parámetro.

LatLng cali = new LatLng(3.444, -76.511);
googleMap.moveCamera(CameraUpdateFactory.newLatLng(cali));

¿Cómo usar animateCamera()?

Exactamente igual que el anterior:

googleMap.animateCamera(CameraUpdateFactory.newLatLng(cali));

Sin embargo este método tiene una sobrecarga muy útil para controlar la duración de la animación y una escucha relacionada.

void animateCamera (CameraUpdate update, int durationMs, GoogleMap.CancelableCallback callback)

donde,

  • update: Nuevo transformación de posición de la cámara
  • durationMs: La cantidad de milisegundos que durará la animación
  • callback: Escucha para determinar cuándo termina la animación o cuando se cancela prematuramente.

La instancia de CancelableCallback debe tener definido dos métodos:

  • onFinish(): Se llama si la animación termino sin percances.
  • onCancel(): Se llama si la animación terminó prematuramente.

Por ejemplo…

Animar el cambio de posición de la cámara hacia las coordenadas (80,80) al presionar el action button «Inicio». Al terminar la animación desplegar un Toast con un mensaje de confirmación.

Solución…

1. Crea un nuevo action button programáticamente en la actividad con onPrepareOptionsMenu(). Usa el título «Inicio»:

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
    menu.add("Inicio").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
    return super.onPrepareOptionsMenu(menu);
}

2. Procesa el evento de este elemento en onOptionsItemSelected():

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    CharSequence title = item.getTitle();

    if (title != null && title.equals("Inicio")) {
        // Animar cámara
    }
    return super.onOptionsItemSelected(item);
}

3. Establece un nuevo LatLng en (80,80) para usar newLatLng() en animateCamera() como primer parámetro.

La duración ponla 2000 ms y crea una instancia anónima de CancelableCallback en el tercer parámetro.

Sobrescribe el controlador onFinish() para crear un Toast que indique que la animación terminó:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    CharSequence title = item.getTitle();

    if (title != null && title.equals("Inicio")) {
        LatLng zero = new LatLng(80, 80);
        mMap.animateCamera(
                CameraUpdateFactory.newLatLng(zero), // update
                2000, // durationMs
                new GoogleMap.CancelableCallback() { // callback
                    @Override
                    public void onFinish() {
                        Toast.makeText(CameraActivity.this, "Animación finalizada",
                                Toast.LENGTH_LONG).show();
                    }

                    @Override
                    public void onCancel() {

                    }
                });
    }
    return super.onOptionsItemSelected(item);
}

Al ejecutar tendrás:

animateCamera() en Google Maps API Android

13. Crear Formas Con Google Maps API

Existen tres clases para crear formas en tus mapas:

  • Polyline: Conjunto de líneas interconectadas. Útiles al marcar rutas.Polyline en Mapa Android
  • Polygon: Figura que permite encerrar áreas del mapa. Útiles para mostrar localidades o lugares de incidenciaPolygon en Google Maps API Android
  • Circle: Figura circular dibujada sobre el mapa.Circle en Google Maps API Android

Veamos como producir cada forma…

Crear polilíneas

Para crear una polilínea solo necesitas saber la latitud y longitud donde comienza y termina un segmento de línea.

Ingredientes:

  • La clase PolylineOptions
  • La clase LatLng
  • La clase Polyline
  • El método GoogleMap.addPolyline()

Preparación:

1. La clase PolylineOptions facilita la creación de instancias Polyline. Solo añade vértices terminales LatLng con el método add().

Ten en cuenta el orden de la secuencia para ir creando los segmentos rectos.

// Ejemplo: Delimitar a Sudamérica con un rectángulo
PolylineOptions sudamericaRect = new PolylineOptions()
        .add(new LatLng(12.897489, -82.441406)) // P1
        .add(new LatLng(12.897489, -32.167969)) // P2
        .add(new LatLng(-55.37911, -32.167969)) // P3
        .add(new LatLng(-55.37911, -82.441406)) // P4
        .add(new LatLng(12.897489, -82.441406)) // P1
        .color(Color.parseColor("#f44336"));    // Rojo 500

2. Usa el método addPolyline() para añadir la instancia PolylineOptions previamente creada.

// Instancia Polyline para posteriores usos
Polyline polyline = mMap.addPolyline(sudamericaRect);

3. (Opcional) Mueve la cámara para visualizar la polilínea:

// Mover cámara
mMap.animateCamera(CameraUpdateFactory.newLatLng(new LatLng(-20.96144, -61.347656)));

El resultado de la unión de los puntos dados es el siguiente:

Polyline limitando a Sudamérica en Google Maps

Crear polígonos

Un polígono se representa en el mapa como una figura plana limitada por al menos 3 vértices y ángulos. Aunque su método de creación es casi igual que Polyline, este tiene un color de relleno.

Ingredientes:

  • La clase PolygonOptions
  • La clase LatLng
  • La clase Polygon
  • El método GoogleMap.addPolygon()

Preparación:

1. Investiga, obtén o produce los vértices del polígono y crea un objeto LatLng por cada uno:

// Ejemplo: Encerrar a Cuba con un polígono de bajo detalle
LatLng p1 = new LatLng(21.88661065803621, -85.01541511562505);
LatLng p2 = new LatLng(22.927294359193038, -83.76297370937505);
LatLng p3 = new LatLng(23.26620799401109, -82.35672370937505);
LatLng p4 = new LatLng(23.387267854439315, -80.79666511562505);
LatLng p5 = new LatLng(22.496957602618004, -77.98416511562505);
LatLng p6 = new LatLng(20.20512046753661, -74.16092292812505);
LatLng p7 = new LatLng(19.70944706110937, -77.65457527187505);

2. Usa la clase PolygonOptions para predefinir instancias de Polygon.

Solo añade vértices LatLgn con su método add() y ten en cuenta que la API está diseñada para autocompletar el polígono y producir una figurada cerrada.

Polygon cubaPolygon = mMap.addPolygon(new PolygonOptions()
        .add(p1, p2, p3, p4, p5, p6, p7, p1)
        .strokeColor(Color.parseColor("#AB47BC"))
        .fillColor(Color.parseColor("#7B1FA2")));

3. Usa la el objeto Polygon retornado por addPolygon() para posteriores usos y cambios. Por ejemplo, con Polygon.setPoints() puedes modificar los vértices de nuevo.

4. (Opcional) Actualiza la cámara

mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
        new LatLng(21.5034305704608, -78.95096199062505), 5));

El ejemplo anterior nos muestra el polígono sobre Cuba:

Poligono limitando a Cuba en Mapa de Google Android

Crear círculos

Usa la clase Circle para representar un círculo en la superficie del mapa con relleno. Su construcción se basa en la definición de un centro y el radio.

Ingredientes:

  • La clase CircleOptions
  • La clase LatLng
  • La clase Circle
  • El método GoogleMap.addCircle()

Preparación:

1. Define el centro del círculo con un objeto LatLng y su radio con un flotante en metros.

// Ejemplo: Crear círculo con radio de 40m 
// y centro (3.4003755294523828, -76.54801384952702) 
LatLng center = new LatLng(3.4003755294523828, -76.54801384952702);
int radius = 40;

2. Crea un objeto CircleOptions para configurar el círculo. Ten en cuenta los siguientes métodos:

  • center(LatLng center): Asigna el valor del centro
  • radius(double radius): Asigna el radio
  • strokeColor(int color): Asigna el color de la línea de trazo. Usa la clase de utilidades Color para crear colores o usar constantes como Color.RED.
  • strokeWidth(float width): Asigna el grueso de la línea de trazo
  • fillColor(int color): Asigna el color del relleno del círculo.

Estos te permitirán personalizar la forma como se ve en la continuación del ejemplo:

CircleOptions circleOptions = new CircleOptions()
        .center(center)
        .radius(radius)
        .strokeColor(Color.parseColor("#0D47A1"))
        .strokeWidth(4)
        .fillColor(Color.argb(32, 33, 150, 243));

3. Añade el círculo con GoogleMap.addCircle(). Si deseas modificar en el futuro el círculo, entonces toma la referencia de la instancia Circle que retorna este método.

// Añadir círculo
Circle circle = mMap.addCircle(circleOptions);

4. (Opcional) Actualiza el objetivo de la cámara:

mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(center, 17));

El resultado será la delimitación del Parque del Refugio en mi ciudad Cali.

Parque del Refugio en Cali Google Maps Android

14. Manejar Eventos Sobre El Mapa

¿Qué tipos de eventos recibe un objeto GoogleMap y que escucha uso para procesarlos?

Evento Escucha
Click OnMapClickListener
Click prolongado OnMapLongClickListener
Cambio de cámara OnCameraChangeListener
Mapas de interiores OnIndoorStateChangeListener
Marcadores Ver sección de marcadores
Ventanas de información Ver sección de marcadores

Veamos algunos ejemplos…

Procesar eventos de Click

Como se ve en la tabla anterior debes usar OnMapClickListener para procesar el evento de la vista (Actividad o Fragmento). Para usarla veremos los siguientes pasos.

Ingredientes:

  • Activity o Fragment
  • OnMapClickListener y su controlador onMapClick()
  • GoogleMap.setOnMapClickListener()
  • LatLng
  • Projection

Preparación:

1. Implementa la interfaz OnMapClickListener sobre tu vista.

En nuestro caso usaremos FirstMapActivity. Así que solo usamos implements y sobrescribimos onMapClick().

public class EventsActivity extends AppCompatActivity
        implements OnMapReadyCallback,
        GoogleMap.OnMapClickListener {

    private GoogleMap mMap;

    // Cuerpo


    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;

      
    }

    @Override
    public void onMapClick(LatLng latLng) {
       
    }
}

2. Asigna la escucha con setOnMapClickListener() en onMapReady()

@Override
public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;

    // Eventos
    mMap.setOnMapClickListener(this);
}

3. Procesa onMapClick() y realiza las acciones correspondientes con su parámetro LatLng.

Aquí nuestro ejemplo imprime un mensaje con las coordenadas.

@Override
public void onMapClick(LatLng latLng) {
    String format = String.format(Locale.getDefault(),
            "Lat/Lng = (%f,%f)", latLng.latitude, latLng.longitude);
    Toast.makeText(this, format, Toast.LENGTH_LONG).show();
}

4. (Opcional) Traduce las coordenadas a pixeles de la pantalla con la clase Projection y su método toScreenLocation().

Como variante, concatenamos los pixeles reales al mensaje a imprimir.

@Override
public void onMapClick(LatLng latLng) {
    String formatLatLng = String.format(Locale.getDefault(),
            "Lat/Lng = (%f,%f)", latLng.latitude, latLng.longitude);

    Point screentPoint = mMap.getProjection().toScreenLocation(latLng);

    String formatScreenPoint = String.format(Locale.getDefault(),
            "nPoint = (%d,%d)", screentPoint.x, screentPoint.y);

    Toast.makeText(this, formatLatLng + formatScreenPoint, Toast.LENGTH_LONG).show();
}

El resultado sería:

OnMapClickListener en Google Maps API Android

Procesar eventos de click prolongado

El click prolongado se refiere al gesto Press, que se da cuando el usuario presiona un punto de la pantalla por un tiempo extendido. Y como dice la tabla se requiere OnMapLongClickListener para leer esta acción.

Ingredientes:

  • Activity o Fragment
  • OnMapLongClickListener y su controlador onMapLongClick()
  • GoogleMap.setOnMapLongClickListener()
  • LatLng
  • Projection

Preperación:

1. Implementa OnMapLongClickListener sobre el Fragmento u Actividad.

public class EventsActivity extends AppCompatActivity
        implements OnMapReadyCallback
        GoogleMap.OnMapLongClickListener {

    private GoogleMap mMap;

    // Cuerpo


    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;
    }

    @Override
    public void onMapLongClick(LatLng latLng) {
       
    }
}

2. Relaciona el mapa con la escucha a través de setOnMapLongClickListener().

El ejemplo necesita los gestos deshabilitados para el propósito del ejercicio.

@Override
public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;

    // Configuración UI
    mMap.getUiSettings().setAllGesturesEnabled(false);

    // Eventos
    mMap.setOnMapLongClickListener(this);
}

3. Incluye las acciones necesarias en onMapLongClick(LatLng) para procesar la ubicación.

Añadiremos un marcador en la posición donde se hizo click prolongado. Si su coordenada x en la pantalla es menor o igual que el ancho total de esta, entonces cambiaremos su color a Amarillo. De lo contrario será Naranja.

@Override
public void onMapLongClick(LatLng latLng) {

    // Añadir marker en la posición
    Marker marker = mMap.addMarker(new MarkerOptions().position(latLng));

    // Obtener pixeles reales
    Point point = mMap.getProjection().toScreenLocation(latLng);

    // Determinar el ancho total de la pantalla
    DisplayMetrics display = new DisplayMetrics();
    getWindowManager().getDefaultDisplay().getMetrics(display);
    int width = display.widthPixels;

    float hue;

    // ¿La coordenada de pantalla es menor o igual que la mitad del ancho?
    if (point.x <= width / 2) {
        hue = BitmapDescriptorFactory.HUE_YELLOW;

    } else {
        hue = BitmapDescriptorFactory.HUE_ORANGE;
    }

    // Cambiar color del marker según el grupo
    marker.setIcon(BitmapDescriptorFactory.defaultMarker(hue));
}

Comienza a presionar puntos y tendrás marcadores separados:

OnMapLongClicksListener en Google Maps API Android

Conclusión

¿Cómo te fue con este inicio en Google APIs?

Espero que hayas probado una buena cantidad de características de Google Maps.

Todos los temas explicados en este tutorial satisfacen aplicaciones que deseen destacar ubicaciones, mostrar datos importantes sobre lugares, encerrar áreas, crear marcadores personalizados para diferenciar entidades, etc.

Por lo que puedes complementar muy bien con otros elementos UI de Android para solucionar necesidades de tus usuarios.

Si deseas mostrar ubicaciones con información más detallada, rastrear de forma precisa un elemento, calcular distancias y tiempos de viaje o trazar caminos por direcciones transitables, entonces investiga sobre los Web Services de Google Maps.

Únete Al Discord De Develou

Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!