Google Maps es una de las aplicaciones más impresionantes que Google pone a nuestra disposición. Su integración en las aplicaciones de Android es un deseo común de muchos desarrolladores, pero afortunadamente no es tan difícil.
En este tutorial, aprenderá cómo hacerlo y el ejemplo le mostrará conceptos importantes, desde la configuración básica hasta casos de uso avanzado.
Vamos a construir una “calculadora de distancias” utilizando Google Maps . El enfoque es totalmente visual. Elegimos dos puntos en el Mapa: el primero moviendo un Marcador, el segundo tocando la pantalla.
La aplicación dibuja una línea roja entre estos puntos, calcula su distancia y recupera las direcciones de las calles correspondientes a través de la geocodificación. Al final, un diálogo de alerta notifica al usuario de los resultados.
El caso que estamos tratando es interesante ya que involucra varias funcionalidades más allá de las bases de Google Maps en Android: marcadores, dibujo en el mapa, cálculo de distancia por objetos de Ubicación, manejo de eventos en Mapa y geocodificación.
El código a escribir no lo es tanto porque las clases de Java en el SDK de Google Play Services nos facilitan el trabajo.
Empecemos.
Google Play Services y Maps: preparando el proyecto
Para uniformar la interacción entre las aplicaciones y sus propios servicios (incluidos Maps, Drive, Google Plus, etc.), Google proporcionó Google Play Services, un paquete de software integrado en el sistema operativo Android y actualizado constantemente.
Una aplicación que utiliza la API de Google Maps requiere la integración del SDK de Google Play Services en el proyecto.
El primer paso es instalar la biblioteca de Google Play Services en nuestra aplicación. El Android SDK Manager puede ayudarnos. Este tutorial se basa en Android Studio, la herramienta de desarrollo oficial.
En la GUI del Administrador de SDK de Android (podemos abrirlo seleccionando el elemento «Administrador de SDK» en el submenú Herramientas> Android), tenemos que descargar dos elementos: «Servicios de Google Play» y «Repositorio de Google».
Al final de la descarga, puede integrarlo en el proyecto utilizando las directivas de Gradle :
dependencias { … … compila 'com.google.android.gms:play-services:7.3.0' }
Indiqué 7.3.0 como número de versión según mi instalación. Obviamente, tiene que ser personalizado.
Sin embargo, este es el archivo completo de compilación de Gradle:
aplicar complemento: 'com.android.application' androide { compileSdkVersión 21 buildToolsVersión "22.0.1" configuración por defecto { applicationId "com.androidbegin.googlemapstutorial" minSdk Versión 14 targetSdkVersión 21 código de versión 1 nombre de la versión "1.0" } tipos de compilación { lanzamiento { minifyEnabled falso proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencias { compile fileTree (dir: 'libs', incluya: ['*.jar']) compila 'com.android.support:appcompat-v7:22.1.1' compila 'com.google.android.gms:play-services:7.3.0' }
Creación de un proyecto API de Google
Después de instalar la biblioteca, debe crear un proyecto de Google en Google Developers Console ( https://console.developers.google.com ). Para acceder solo necesitas una cuenta de Google: tus credenciales de Gmail serán suficientes.
Esto le permite crear una clave API que se requiere para permitir que su aplicación descargue información del servicio de Google Maps.
En primer lugar, necesita un proyecto API de Google. Después de iniciar sesión correctamente, seleccione un proyecto existente o cree uno nuevo haciendo clic en el botón «Crear proyecto» .
La ventana de diálogo que aparece le pedirá un nombre de proyecto y una ID de proyecto. Puedes elegir ambos.
En la barra lateral de la izquierda, seleccione » API y autenticación » > API. Busque «Google Maps Android API v2» y habilítelos.
Ahora puedes crear tu clave de acceso .
Seleccione » API y autenticación » > Credenciales y haga clic en el botón «Crear una nueva clave». Necesitamos una clave de acceso público, pero es importante recordar que tiene que ser una «clave de Android».
Se le pedirá un código que es el resultado de la concatenación de dos partes separadas por un punto y coma: un código SHA-1 y el nombre del paquete de su aplicación.
El código SHA-1 es la huella digital de su aplicación. Puede obtenerlo usando key-tool, pero el comando es diferente en cada sistema operativo.
Para ventanas:
keytool -list -v -keystore “%USERPROFILE%.androiddebug.keystore” -alias androiddebugkey -storepass android -keypass android
Para Linux o Mac:
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
Estos comandos generan una clave de depuración. Tienes que buscar una clave SHA1 en la cantidad de salida.
En mi proyecto, la huella digital SHA1 y el nombre del paquete de la aplicación se ven así:
17:5F:A3:CA:67:94:E1:AA:E4:02:11:82:9B:3D:8A:53:3A:B2:8D:12;com.androidbegin.googlemapstutorial
Después de la creación, puede ver una clave API como esta:
AIzaSy62NbSRLQScJa_5sAWBYM_fGA0G4eAYDirector general
La clave se copiará y pegará en el archivo de manifiesto de su proyecto.
La configuración: el archivo AndroidManifest.xml
En primer lugar, tenemos que configurar la aplicación.
<?versión xml="1.0" codificación="utf-8"?> <manifiesto xmlns_android="http://schemas.android.com/apk/res/android" paquete="com.androidbegin.googlemapstutorial" > <usos-permiso android_name="android.permiso.INTERNET" /> <usos-permiso android_name="android.permission.ACCESS_NETWORK_STATE" /> <usos-permiso android_name="android.permission.WRITE_EXTERNAL_STORAGE" /> <usos-permiso android_name="com.google.android.providers.gsf.permission.READ_GSERVICES" /> <usos-permiso android_name="android.permission.ACCESS_COARSE_LOCATION" /> <usos-permiso android_name="android.permission.ACCESS_FINE_LOCATION" /> <aplicación Android: permitir copia de seguridad = "verdadero" android_icono="@dibujable/ic_launcher" android_etiqueta="@cadena/nombre_aplicación" android_tema="@estilo/AppTheme" > <metadatos android_nombre="com.google.android.gms.versión" android_value="@integer/google_play_services_version" /> <metadatos android_nombre="com.google.android.maps.v2.API_KEY" android_valor="@string/google_maps_key" /> <actividad android_name=".MapsActivity" android_label="@cadena/título_actividad_mapas" > <intent-filter> <acción android_name="android.intent.action.MAIN" /> <categoría android_name="android.intent.category.LAUNCHER" /> </intent-filter> </actividad> </aplicación> </manifiesto>
El AndroidManifest.xml contiene elementos fundamentales para que la clase GoogleMap que utilicemos pueda obtener información de servidores remotos.
Los permisos requeridos son:
- android.permiso.INTERNET;
- android.permiso.ACCESS_NETWORK_STATE;
- android.permiso.WRITE_EXTERNAL_STORAGE.
Permiten que la aplicación acceda a la red y guarde datos para la memoria caché en el almacenamiento externo.
Otros dos permisos no son indispensables, pero pueden ser útiles si la aplicación accede a la ubicación del usuario:
ACCESS_COARSE_LOCATION y ACCESS_FINE_LOCATION.
Luego, agregamos dos nodos de metadatos como elementos secundarios del elemento <application>. El primero establece la CLAVE API que creamos en nuestro Proyecto de Google. El segundo declara la versión de Google Play Services.
Los valores dentro de ellos se refieren a archivos en dos subcarpetas /res/values:
google_maps_api.xml:
<recursos> <string name="google_maps_key">AIzaSy62NbSRLQScJa_5sAWBYM_fGA0G4eAYCEo</string> </recursos>
y versión.xml:
<recursos> <integer name="google_play_services_version">7327000</integer> </recursos>
La actividad
El único elemento que necesitamos en el diseño es un fragmento que contenga el Mapa que se proporcionará desde la clase SupportMapFragment:
<fragmento xmlns_android="http://schemas.android.com/apk/res/android" android_layout_width="match_parent" android_layout_height="match_parent" android_id="@+id/mapa" android_name="com.google.android.gms.maps.SupportMapFragment" />
El código de la clase Activity realiza tres tareas principales:
- configuración de la UI y verificación de la disponibilidad de un Mapa;
- la configuración del Mapa incluía el Marcador y el oyente que maneja sus eventos de arrastrar y soltar;
- Map click listener: activa la funcionalidad de cálculo de distancia.
clase pública MapsActivity extiende FragmentActivity { // el objeto del mapa de Google privado GoogleMap mMap; // Los objetos LatLng almacenan un par de coordenadas terrestres (latitud y longitud) // STARTING_MARKER_POSITION primeros valores son las coordenadas del Coliseo en Roma (Italia) privado estático LatLng STARTING_MARKER_POSITION = new LatLng (41.892950, 12.494456); /*distanceFrom indica el punto de partida para calcular la distancia. Se inicializa con STARTING_MARKER_POSITION */ distancia privada LatLngFrom= STARTING_MARKER_POSITION; // la línea se dibujará en el evento de clic polilínea privada line=null; // Un Geocodificador puede transformar un par de latitud/longitud en una dirección de calle y viceversa. // Lo usaremos en el oyente Geocoder estático privado geocoder=null; privado GoogleMap.OnMapClickListener clickListener=nuevo GoogleMap.OnMapClickListener() { @Anular onMapClick public void (pos. LatLng final) { // este método se llama cuando el usuario toca el mapa // si ya aparece una línea, se elimina si (línea! = nulo) línea.remove(); // se crea una nueva línea línea = mMap.addPolyline(nueva PolylineOptions() .add(distanciaDesde, pos) .width(5) // ancho de la línea .color(Color.ROJO)); // color de linea // llamar al objeto convertidor para la invocación de geocodificación y el cálculo de distancia nuevo AddressConverter().execute(distanciaDesde, pos); } }; @Anular Vacío protegido onCreate (Paquete de estado de instancia guardado) { super.onCreate(estadoDeInstanciaGuardado); // establecemos el diseño para la Actividad setContentView(R.layout.actividad_mapas); // el geocodificador es instanciado por primera vez geocoder=nuevo Geocodificador(esto); // si no hay un mapa, se creará configurarMapaSiEsNecesario(); } @Anular vacío protegido en el currículum () { super.onReanudar(); // la disponibilidad de GoogleMap se verificará antes de que la Actividad comience a interactuar con el usuario configurarMapaSiEsNecesario(); } setUpMapIfNeeded privado vacío () { // se crea el mapa solo que no se ha inicializado si (mMap == nulo) { // el mapa se encuentra en el diseño mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap(); // si existe un mapa, procedemos con la inicialización if (mMapa!= nulo) { configurarMapa(); } } } // Ahora es el momento de configurar el mapa. Podemos agregar marcadores, formas, controladores de eventos, etc. setUpMap vacío privado () { // la cámara se posicionará según las nuevas coordenadas mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(STARTING_MARKER_POSITION, 16)); // elegimos el tipo de mapa: Satélite en este caso mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE); // markerOptions describe el marcador que queremos colocar Opciones de marcador Opciones de marcador=nuevas Opciones de marcador() .posición(STARTING_MARKER_POSITION) .arrastrable(verdadero); // el marcador tiene que ser arrastrable ya que lo moveremos // el marcador se representa en el mapa mMap.addMarker(marcadorOpciones); // definimos el objeto a invocar cuando se arrastra el marcador mMap.setOnMarkerDragListener(nuevo GoogleMap.OnMarkerDragListener() { @Anular vacío público enMarkerDragStart(Marker arg0) { // este método se llama cuando comienza el arrastre // la operación que necesitamos es la cancelación de una línea preexistente si (línea! = nulo) línea.remove(); } @Anular public void onMarkerDragEnd (posición final del marcador) { // obtenemos la posición final del marcador distanciaDesde=pos.getPosition(); } @Anular vacío público enMarkerDrag (Marcador arg0) { // operaciones realizadas durante el movimiento. Nada que hacer } }); // se establece la devolución de llamada a invocar mMap.setOnMapClickListener(clickListener); } // queremos saber que direccion corresponde a esta ubicacion // usamos AsyncTask para realizar operaciones más lentas en un subproceso separado clase privada AddressConverter extiende AsyncTask<LatLng,Void,String> { // La ventana ProgressDialog que mostraremos durante el cálculo privado ProgressDialog progreso = nulo; // este método se llama antes de que comience el trabajo en segundo plano. Funciona en el hilo principal. @Anular vacío protegido onPreExecute() { // Se muestra ProgressDialog progreso= ProgressDialog.show(MapsActivity.this,"Calculadora de distancia","Estamos calculando la distancia...", verdadero, falso); } // este método funciona en un hilo separado // realiza operaciones de geocodificación para recuperar la dirección de los puntos y calcula la distancia en metros entre ellos @Anular Cadena protegida doInBackground(LatLng... parámetros) { float[] distancia=nuevo float[1]; intentar { // la clase Location contiene lo que necesitamos para calcular distancias Location.distanceBetween(parámetros[0].latitud,parámetros[0].longitud,parámetros[1].latitud,parámetros[1].longitud,distancia); // operaciones de geocodificación List<Address> fromResult=geocoder.getFromLocation(parámetros[0].latitud,parámetros[0].longitud,1); List<Address> toResult=geocoder.getFromLocation(parámetros[1].latitud,parámetros[1].longitud,1); // el mensaje informa al usuario sobre la distancia desde el marcador hasta el punto seleccionado con el clic // si tenemos ambas direcciones, las usamos para redactar el mensaje, de lo contrario mostramos solo la distancia if (fromResult.size()>0 && toResult.size()>0) { return "La distancia entre " + getAddressDescription(fromResult.get(0)) + " y " + getAddressDescription(toResult.get(0)) + " es " + Math.round(distancia[0]) + " metros"; } más return "La distancia es " + Math.round(distancia[0]) + "metros"; } captura (IOException e) { return "La distancia es " + Math.round(distancia[0]) + "metros"; } } @Anular vacío protegido en PostExecute (mensaje de cadena) { si (¡progreso! = nulo) progreso.descartar(); // Se instancia el constructor de la ventana AlertDialog.Builder builder=new AlertDialog.Builder(MapsActivity.this); constructor.setTitle("Distancia"); constructor.setMessage(mensaje); // aparece el cuadro de diálogo Alerta constructor.mostrar(); } } // este método solo formatea el mensaje con direcciones Cadena privada getAddressDescription(Dirección a) { String city=a.getLocality(); Cadena dirección=a.getAddressLine(0); return "'"+dirección+"' ("+ciudad+")"; } }
Los métodos onCreate y onResume establecen el diseño e inicializan un objeto Geocoder que usaremos para convertir coordenadas geográficas en direcciones de calles.
Almacenamos una referencia a un objeto GoogleMap como miembro privado de la Actividad. Esta referencia se recupera en la llamada al método setUpMapIfNeeded de onResume .
La parte más importante de la configuración se realiza en setUpMap . Después de llamarlo, sabemos que hay un objeto de GoogleMap disponible. Movemos la cámara que indica qué parte del mundo debe mostrar nuestro Mapa.
La posición desde la que comenzamos se describe en un objeto LatLng , al que se hace referencia en el código como STARTING_MARKER_POSITION.
El programador puede elegir el tipo de mapa. Preferimos un mapa satelital que muestre detalles de la ciudad. STARTING_MARKER_POSITION se establece en las coordenadas del Coliseo, uno de los monumentos más importantes de la antigua Roma.
Para crear un marcador, tenemos que instanciar un objeto MarkerOptions . Al menos, tiene que obtener la posición del marcador como un objeto LatLng. Es importante que el marcador se pueda arrastrar para nuestros propósitos.
OnMarkerDragListener requiere la anulación de tres métodos:
- onMarkerDragStart: se llama cuando comienza el movimiento de arrastre. Solo necesitamos borrar una línea si se acaba de ejecutar un cálculo;
- onMarkerDrag: llamado repetidamente durante el movimiento. No lo necesitamos;
- onMarkerDragEnd: notifica el final del movimiento de arrastre. Almacenamos la posición final del marcador en la variable distanceFrom.
La clase anidada: operaciones de geocodificación
Hay una clase anidada dentro de MapsActivity, AddressConverter . Extiende AsyncTask ya que lo usamos para realizar operaciones de geoconducción. El Geocodificador necesita comunicarse con servidores externos y esto puede causar algún retraso.
La definición de AsyncTask contiene un método abstracto, doInBackground , que ejecuta el código en un encabezado separado para no afectar el rendimiento de la interfaz de usuario.
Por otro lado, los métodos onPreExecute y onPostExecute funcionan en el subproceso principal para que puedan actualizar la interfaz de usuario.
El método onPreExecute muestra un ProgressDialog que desaparecerá al finalizar el trabajo en segundo plano.
En doInBackground, calculamos la distancia entre los puntos y realizamos operaciones de geocodificación utilizando el objeto Geocoder. Luego, preparamos la cadena para el mensaje que se mostrará en el AlertDialog.
El método onPostExecute descarta el ProgressDialog ya que el trabajo en segundo plano ha finalizado y muestra el AlertDialog que contiene los resultados del cálculo para el usuario.