- Crear el proyecto y el repositorio para la gestión y el control de cambios.
- Darle forma al código para que sea fácil de mantener, robusto y versátil.
- Conseguir la key del Google Maps Android API.
- Incluir referencias y librerías (tuve que actualizar eclipse y varios plugins y eso también me llevó un rato largo, google-play-services_lib.jar, android-support-v4.jar)
- Insertar el mapa como fragment (lo que me llevó a pasar de utilizar Activity a usar FragmentActivity)
1º Requisitos
2º Análisis
3º Creación del proyecto y repositorio
4º Inserción de mapa
5º Localizar y apuntar a La Meca
6º Detalles de usabilidad
7º Crear y firmar la applicación y subirla a +Google Play
En la primera parte llegué hasta el punto 4 y hoy me gustaría terminar y publicar una versión BETA de ésta aplicación. así que empecemos.
Localización
En la actividad que maneja el mapa necesitamos un objeto tipo GoogleMap para manejarlo par alo cual hay que hacer lo siguiente:
GoogleMap mapa = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap();
Tendremos que importar además com.google.android.gms.maps.GoogleMap y com.google.android.gms.maps.SupportMapFragment, cosa que no debería darnos problemas si tenemos bien enlazadas las librerías del API y con ésto obtenemos una instancia del mapa que hemos declarado en la vista así:
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/map"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
class="com.google.android.gms.maps.SupportMapFragment"/>
Se pueden hacer muchas cosas con ese mapa y recomiendo leer éste segundo artículo de sgoliver sobre mapas para ver como jugar con los estilos del mapa y con la cámara, habrá cosas de ese artículo que utilizaré más adelante… Pero lo que ahora quiero es centrar el mapa en mi posición.
Haciendo mapa.setMyLocationEnabled(true); nos aparecerá el famoso círculo azul que indica nuestra posición y el radio aproximado de precisión pero el mapa seguirá centrado en el punto 0,0 y con el zoom por defecto…
Después de darle muchas vueltas a todo el tema de la localización y haber evolucionado mucho el código de mi MapActivity creo que es mejor que lo exponga y lo desgrane aquí que ir explicando los pasos que he ido siguiendo porque he dado muchas vueltas sin resultados. Es cierto que el código es bastante sucio pero esto es porque he intentado con multitud de comentarios que el código sea auto-explicativo:
package com.jgc.lameca;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.LocationSource;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.PolylineOptions;
import android.graphics.Color;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
public class MapActivity extends BaseActivity implements LocationListener, LocationSource, SensorEventListener {
// El mapa
private GoogleMap mMap;
// Para escuchar los cambios de localización del usuario,
// si se mueve o si la localización es más precisa
private OnLocationChangedListener mListener;
// Para gestionar nosotros la localización y no el propio mapa y poder
// manejar eventos como onLocationChanged que de otra manera no podríamos gestionar
private LocationManager locationManager;
private boolean haveLocation = false;
// Sensores para orientar el mapa de acuerdo a la brújula
private SensorManager mSensorManager;
private float[] mGravity;
private float[] mGeomagnetic;
// para controlar el intervalo de ejecución del evento de los sensores
private long lastTimeCompassUpdated = 0;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.map_layout);
// Instanciamos locationManager
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
// Establecemos la precisión
Criteria locationCriteria = new Criteria();
locationCriteria.setAccuracy(Criteria.ACCURACY_FINE);
// Y establecemos que se escuche al mejor proveedor de localización
// (o redes móviles o GPS o WiFi, dependerá del estado del dispositivo
// y así nos despreocupamos nosotros)
locationManager.requestLocationUpdates(locationManager.getBestProvider(locationCriteria, true), 1L, 2F, (LocationListener) this);
initializeMap();
}
private void initializeMap() {
// Confirmamos que no se ha inicializado el mapa todavía
if (mMap == null)
{
// obtenemos el mapa de la vista
mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap();
// Registramos o establecemos ésta clase, MapActivity, como LocationSource
// del mapa para utilizar nuestro locationManager y el Listener ;)
mMap.setLocationSource(this);
// Inicializamos el mapa...
if (mMap != null)
{
setUpMap();
}
}
}
private void setUpMap()
{
// Nos localiza...
mMap.setMyLocationEnabled(true);
// Quitamos el botón de "mi posición"
mMap.getUiSettings().setMyLocationButtonEnabled(false);
// Pinta una marca en La Meca
addLaMecaMarkOnMap();
centerMapOnLaMeca();
}
private void addLaMecaMarkOnMap(){
mMap.addMarker(new MarkerOptions()
.position(new LatLng(Constants.laMecaLatitude, Constants.laMecaLongitude))
.title("La Meca")
.icon(BitmapDescriptorFactory.fromResource(R.drawable.icono_meca)));
}
public void centerMapOnLaMeca(){
CameraPosition newCameraPosition = new CameraPosition.Builder()
.target(new LatLng(Constants.laMecaLatitude, Constants.laMecaLatitude))
.zoom(1)
.bearing(0)
.tilt(0)
.build();
mMap.animateCamera(CameraUpdateFactory.newCameraPosition(newCameraPosition));
}
private void initialiceSensors() {
mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
// Vamos a usar el acelerómetro y el sensor magnético
Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
mSensorManager.registerListener(this, gsensor, SensorManager.SENSOR_DELAY_NORMAL);
mSensorManager.registerListener(this, msensor, SensorManager.SENSOR_DELAY_NORMAL);
}
private void drawLineBetweenOurLocationAndLaMeca(){
// Debe ejecutarse cuando sepamos nuestra localización
// es decir, onLocationChanged
Location myLocation = mMap.getMyLocation();
LatLng myLatLng = new LatLng(myLocation.getLatitude(), myLocation.getLongitude());
LatLng laMecaLatLng = new LatLng(Constants.laMecaLatitude, Constants.laMecaLongitude);
// creamos un polyline (linea poligonal) con dos puntos
PolylineOptions rectOptions = new PolylineOptions()
.add(myLatLng) // nuestra posición
.add(laMecaLatLng); // la posición de la meca
// pintamos esa línea de color verde
rectOptions.color(Color.GREEN);
// Y la añadimos al mapa
mMap.addPolyline(rectOptions);
}
@Override
public void onPause()
{
if(locationManager != null)
{
locationManager.removeUpdates((LocationListener) this);
}
super.onPause();
}
@Override
public void onResume()
{
super.onResume();
initializeMap();
if(locationManager != null)
{
mMap.setMyLocationEnabled(true);
}
}
public void activate(OnLocationChangedListener listener)
{
mListener = listener;
}
public void deactivate()
{
mListener = null;
}
public void onLocationChanged(Location location)
{
if (!haveLocation){
initialiceSensors();
haveLocation = true;
}
mMap.clear();
if( mListener != null )
{
addLaMecaMarkOnMap();
mListener.onLocationChanged( location );
CameraPosition newCameraPosition = new CameraPosition.Builder()
.target(new LatLng(location.getLatitude(), location.getLongitude())) // centra el mapa en nuestra posición
.zoom(19) // establece el zoom
.bearing(0)
.tilt(0)
.build();
//Move the camera to the user's location once it's available!
mMap.animateCamera(CameraUpdateFactory.newCameraPosition(newCameraPosition));
drawLineBetweenOurLocationAndLaMeca();
}
}
public void onProviderDisabled(String provider)
{
// cuando se deshabilita un probeedor de localización... prefiero no hacer nada
// Toast.makeText(this, "provider disabled", Toast.LENGTH_SHORT).show();
}
public void onProviderEnabled(String provider)
{
// cuando se habilita un probeedor de localización... prefiero no hacer nada
// Toast.makeText(this, "provider enabled", Toast.LENGTH_SHORT).show();
}
public void onStatusChanged(String provider, int status, Bundle extras)
{
// Cuando cambia el estado de la localización... prefiero no hacer nada
// Toast.makeText(this, "status changed", Toast.LENGTH_SHORT).show();
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// cuando cambia la precisión...
}
public void onSensorChanged(SensorEvent event) {
// quiero dejar un margen de 3 segundos al principio
// para la animación hasta el punto donde se encuentra el usuario...
if (lastTimeCompassUpdated == 0){
lastTimeCompassUpdated = System.currentTimeMillis()+3000;
return;
}
else if (System.currentTimeMillis()>lastTimeCompassUpdated ){
if (mMap!=null){
int matrix_size = 16;
float[] R = new float[matrix_size];
float[] outR = new float[matrix_size];
if (event.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE)
return;
switch (event.sensor.getType()) {
case Sensor.TYPE_MAGNETIC_FIELD:
mGeomagnetic = event.values.clone();
break;
case Sensor.TYPE_ACCELEROMETER:
mGravity = event.values.clone();
break;
}
if (mGeomagnetic != null && mGravity != null) {
if (SensorManager.getRotationMatrix(R, null, mGravity, mGeomagnetic)) {
SensorManager.getOrientation(R, outR);
}
CameraPosition oldPosition = mMap.getCameraPosition();
// Quiero que sólo se actualice si hay una variación de mas de un grado
//if (Math.abs(Math.abs(oldPosition.bearing) - Math.abs(Math.toDegrees(outR[0])))>1){
CameraPosition newCameraPosition = new CameraPosition.Builder()
.target(oldPosition.target) // Sets the center of the map to Mountain View
.zoom(oldPosition.zoom) // Sets the zoom
.bearing((float) Math.toDegrees(outR[0]))
.tilt(oldPosition.tilt)
.build(); // Creates a CameraPosition from the builder
mMap.moveCamera(CameraUpdateFactory.newCameraPosition(newCameraPosition));
}
}
}
}
}
import android.location.LocationManager;
import android.os.Bundle;
import android.widget.Button;
public class EntryPointActivity extends BaseActivity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onResume() {
setMapButtonVisibility();
super.onResume();
}
private void setMapButtonVisibility() {
LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
boolean network_enabled=locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
boolean gps_enabled=locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
Button mapButton = ((Button)this.findViewById(R.id.ep_map_button));
if (!(network_enabled || gps_enabled)){
mapButton.setEnabled(false);
}
else {
mapButton.setEnabled(true);
}
}
}
Lo último que nos queda por hacer antes de poder firmar y subir a Google Play es pulir detalles de interfaz de usuario y de usabilidad… vamos con ello:
Interfaz de Usuario
Que no se crea nadie que me he complicado demasiado, he buscado un mensaje que invita a la oración, lo he traducido al árabe y he incluido los dos textos, he buscado alguna imagen bonita de la Kaaba y la he centrado y colocado como background del layout principal, también he centrado los botones y buscado una imagen pequeña de la Kaaba para utilizarla como icono de la aplicación. Así ha quedado el xml (main.xml) de EntryPoint:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/centered"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:text="@string/intro_arabe" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:text="@string/intro" />
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="1dip"
android:orientation="vertical" >
<Button
android:id="@+id/ep_gps_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="25dp"
android:onClick="onClickFeature"
android:text="@string/lanzar_configuracion_gps" />
<Button
android:id="@+id/ep_map_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/ep_gps_button"
android:layout_centerHorizontal="true"
android:layout_marginBottom="14dp"
android:onClick="onClickFeature"
android:text="@string/lanzar_mapa" />
</RelativeLayout>
</LinearLayout>
Respecto al centrado de la imagen, he creado un fichero en /res/drawable llamado centered.xml así:
<?xml version=”1.0″ encoding=”utf-8″?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:gravity="center"
android:src="@drawable/Kaaba_mirror_edit_jj" />
¡Hasta la próxima entrada!
Ayer subí la aplicación un poco deprisa y corriendo porque se me hacía tarde y hoy al probarla bajada desde Play Store me he dado cuenta de que no se veían los mapas, esto era por que no había utilizado una clave para el apk firmado. Desde la linea de comandos (cmd.exe) he hecho ésto:
C:Program FilesJavajdk1.7.0_03bin> keytool.exe -list -v -keystore D:WORKruta…Keystorekeystore
Y eso me ha devuelto, entre otras cosas el SHA1 que he puesto en la zona correspondiente a la API de Google Maps (https://code.google.com/apis), también hay mucho escrito al respecto. Lo he probado y ahora si se ven los mapas. Ya estña disponible en Play Store aquí:
https://play.google.com/store/apps/details?id=com.jgc.lameca
— Actualizado 25/12/2012 —
amigo el api v2 funciona para el android2.3 ? he estado batallando para correrlo y me ha dado muchos problemas
Hola amigo Anónimo,Si que funciona, sin más detalles de tu problema se me ocurre que te pueden estar pasando varias cosas:1º ¿Tienes bien la key del API? (es un problema frecuente)2º ¿Tu actividad hereda de FragmentActivity en vez de heredar de Activity? (a mí me pasó y me tiré un par de horas sin saber que pasaba hasta que leí en algún sitio éste cambio en el API v2)3º ¿Tienes declaradas todas las actividades en el Manifest? ( a veces se puede olvidar…)Échale un vistazo a éste post de stackoverflow y me cuentas si te sirve (deja un nombre o mándame un mail):http://stackoverflow.com/questions/13733581/android-2-3-and-google-maps-api-v2
Este comentario ha sido eliminado por el autor.
buenas noches, un muy buen post que me ha ayudado muchisimo a comprender, pero quisiera experimentar con solo obtener mi ubicacion, (sin ofender no obtener la Meca), solo estoy provando, pero la camara no me dirige a mi posicion en automatico ¿que debo hacer?
Para obtener tu ubicación, mira el código de MapActivity… Te lo explico brevemente aquí mismo: // Para escuchar los cambios de localización del usuario, // si se mueve o si la localización es más precisa private OnLocationChangedListener mListener; // Para gestionar nosotros la localización y no el propio mapa y poder // manejar eventos como onLocationChanged private LocationManager locationManager;Mira InitializeMap y después, en SetupMap() (lo siento, no me acuerdo del código, lo he ido leyendo…) puedes leer esto:// Nos localiza… mMap.setMyLocationEnabled(true);Luego la respuesta corta a tu pregunta es:"mMap.setMyLocationEnabled(true);"La repsuesta larga es todo lo anterior mas que debes hacer que tu clase mapa implemente LocationListener y LocationSource (hay peros, pero lo mejor por el control que te da sería implementar ambos).Espero haberte ayudado, ya me contarás, suerte,Juan
muchas gracias y me funciono perfectamente, solo que use en donde va el mapa en lugar de activity use android.support.v4.app.FragmentActivity pero quiero hacer el intent y no me resulta en otra actividad, la herede como tu pero nada me dice que la aplicacion se forzo a cerrarse algo asi, mmm algun consejo para untilizar el intent con mapas? te lo agardeceria
Muy bueno el post, yo tambien estoy viendo el tema de la API v2 para usarla en una app, creo que tiene varias mejoras realmente interesante con respecto a la v1 😉 Saludos
Hola devon25, tu mensaje me lía, supongo que principalmente por la falta de comas entre otras cosas, a ver…Me dices que te funcionó perfectamente, supongo que te refieres a lo de que el mapa "te localice". Veo que te peleaste con la librería de soporte de las FragmentActivity, ¡bien hecho!Para que se lance una activity ésta debe estar declarada en AndroidManifest.xml si no lo está la aplicación fallará y se cerrará "inesperadamente"… (aunque en LogCat, si lo analizas bien, lo suele poner, algo parecido a "la actividad no está declarada")Para lanzar un Intent:private void lanzarMapa() { Intent i = new Intent(this, MapActivity.class); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(i); }
gracias de echo me ha servido de gran ayuda lo que me has dado, solo tengo dos preguntas ¿puedo pintar el mapa sin agregar en mi actividad el fragmentactivity?, por ejemplo public class mapactivity extends fragmentactivity{}y hacerlo como su con solopublic class mapactivity extends activity{}y dos el intent no me sale porque tengo el mapa trabajando y quiero realizar otra actividad, entonces cuando quiero realizar esa actividad me saca y dice que hay un error, puedo detener el mapa y hacer otras actividades?espero puedas ayudarme gracias
Excelente de mucha ayuda me sirvió mucho
Y para poner que el location manager no lo escoga de acuerdo al mejor proveedor sino que se ejecute con el gps? como podria hacer?
Hola muchísimas gracias por el arpote … ¿puedes ponerlo en un github o de alguna forma para descargarlo fácilmente? Es que me estoy haciendo un lio con la clases …
Te pongo aqui porque es al hilo de lo anterior …¿Es obligatoria esta clase EntryPointActivity? No sé podría poner: LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); boolean network_enabled=locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); boolean gps_enabled=locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);En la clase principal antes del boton del mapa?
Hola, he probado tu código y me da error en la línea (dentro de initialize map) que pone:mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.mapaD)).getMap(); me pone que es valor nulo (Caused by: java.lang.NullPointerException)Creo que puede ser un error de cómo tengo mi xml para el layaout … te lo pongo porque si es de eso o sino espero que me digas por donde puede estarPor cierto … la clase la hago herededa de (extends ) android.support.v4.app.FragmentActivity ya que BaseActivity es una clase tuya que no he visto escrita en el artículo, ¿me equivoco?De todas formas está muy bien el aporte, gracias por ayudar a patanes como yo !!!
No tienes el proyecto de la aplicaciones en algun repositorio ?te lo agradeceria saludos
Hola…me estoy iniciando en esto de googleMaps, pero estoy completamente colgado. Me explico…Estoy creando una aplicación para móviles en el que las distintas pantallas que tengo diponibles según las opciones, son todas fragments, por lo que mi UI se basa en hacer transiciones entre los mismos. Mi problema está en que uno es un mapa de google. Cuando uso la opción para visualizarlo no hay problema, este surge cuando giro la pantalla o cuando vuelvo para atrás y quiero cargarlo otra vez, se cuelga todo diciendo que está duplicado y no puede inflate.Alguien sabe como puedo evitar este error o darme alguna pauta para hacer una correcta transición entre fragments de forma que al usar el Maps no salga este errro??Decir que esto solo pasa con el fragment del mapa todos los demás funcionan correctametneMuchísimas gracias y un saludo.
ya resolviste tu problema?? tengo tu respuesta martin