Añadiendo mapas en Mis Lugares

Ejercicio: Añadiendo Google Maps en Mis Lugares

1.   Realiza el ejercicio “Obtención de una clave Google Maps”, pero esta vez indica como nombre del proyecto Mis Lugares.

2.   Añade la librería Google Maps y configura AndroidManifest.xml. Para ello sigue los puntos 2 al 6 del ejercicio “Un ejemplo simple con Google Maps”.

3.   Crea un nuevo layout que se llame mapa.xml con el siguiente código:

<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <fragment android:id="@+id/mapa"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              class="com.google.android.gms.maps.SupportMapFragment"/>
</LinearLayout> 

4.  Crea una nueva clase para la actividad que mostrará el mapa:

public class MapaActivity extends FragmentActivity
                          implements OnMapReadyCallback {
   private GoogleMap mapa;
   private RepositorioLugares lugares;

   @Override public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.mapa);
      lugares = ((Aplicacion) getApplication()).lugares;
      SupportMapFragment mapFragment = (SupportMapFragment)
                getSupportFragmentManager().findFragmentById(R.id.mapa);
      mapFragment.getMapAsync(this);
   }

   @Override public void onMapReady(GoogleMap googleMap) {
      mapa = googleMap;
      mapa.setMapType(GoogleMap.MAP_TYPE_NORMAL);
      if (ActivityCompat.checkSelfPermission(this,
              android.Manifest.permission.ACCESS_FINE_LOCATION) ==
              PackageManager.PERMISSION_GRANTED) {
         mapa.setMyLocationEnabled(true);
         mapa.getUiSettings().setZoomControlsEnabled(true);
         mapa.getUiSettings().setCompassEnabled(true);
      }
      if (lugares.tamaño() > 0) {
         GeoPunto p = lugares.elemento(0).getPosicion();
         mapa.moveCamera(CameraUpdateFactory.newLatLngZoom(
                    new LatLng(p.getLatitud(), p.getLongitud()), 12));
      }
      for (int n=0; n<lugares.tamaño(); n++) {
         Lugar lugar = lugares.elemento(n);
         GeoPunto p = lugar.getPosicion();
         if (p != null && p.getLatitud() != 0) {

            Bitmap iGrande = BitmapFactory.decodeResource(
                            getResources(), lugar.getTipo().getRecurso());
            Bitmap icono = Bitmap.createScaledBitmap(iGrande,
                  iGrande.getWidth() / 7, iGrande.getHeight() / 7, false);
            mapa.addMarker(new MarkerOptions()
                  .position(new LatLng(p.getLatitud(), p.getLongitud()))
                  .title(lugar.getNombre()).snippet(lugar.getDireccion())
                  .icon(BitmapDescriptorFactory.fromBitmap(icono)));
         }
      }
   }
} 
class MapaActivity: FragmentActivity(), OnMapReadyCallback {

   lateinit var mapa: GoogleMap
   val lugares by lazy { (application as Aplicacion).lugares }

   public override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.mapa)
      val mapFragment = supportFragmentManager.findFragmentById(R.id.mapa)
                                                     as SupportMapFragment
      mapFragment.getMapAsync(this)
   }

   override fun onMapReady(googleMap: GoogleMap) {  
      mapa = googleMap
      mapa.setMapType(GoogleMap.MAP_TYPE_NORMAL)
      if (ActivityCompat.checkSelfPermission(this,
          android.Manifest.permission.ACCESS_FINE_LOCATION)
                                   == PackageManager.PERMISSION_GRANTED) {
         mapa.setMyLocationEnabled(true)
         mapa.getUiSettings().setZoomControlsEnabled(true)
         mapa.getUiSettings().setCompassEnabled(true)
      }
      if (lugares.tamaño() > 0) {
         val p = lugares.elemento(0).posicion
         mapa.moveCamera(CameraUpdateFactory.newLatLngZoom(
               LatLng(p.latitud, p.longitud), 12F))
      }
      for (n in 0 until lugares.tamaño()) {
         val lugar = lugares.elemento(n)
         val p = lugar.posicion
         if (p != null && p.latitud != 0.0) {
            val iGrande = BitmapFactory.decodeResource(
               getResources(), lugar.tipoLugar.recurso)
            val icono = Bitmap.createScaledBitmap(iGrande,
               iGrande.getWidth() / 7, iGrande.getHeight() / 7, false)
            mapa.addMarker(
               MarkerOptions()
                  .position(LatLng(p.latitud, p.longitud))
                  .title(lugar.nombre).snippet(lugar.direccion)
                  .icon(BitmapDescriptorFactory.fromBitmap(icono)))
         }
      }
   }
} 
El código utilizado es similar al utilizado en el ejercicio anterior. Una diferencia está en que el centro del mapa se sitúa (moveCamera) en el primer lugar de listaLugares siempre que tenga algún elemento. Luego se introduce un bucle donde añadiremos un marcador para cada lugar. Queremos utilizar los iconos utilizados en la aplicación. El problema es que su tamaño es excesivo. Para resolverlo los leemos como Drawables de los recursos, los convertimos en Bitmap y los escalamos dividiendo su anchura y altura entre siete.

NOTA: Desde un punto de vista de eficiencia, lo ideal sería añadir los nuevos recursos reescalados.

5.   Registra esta actividad añadiendo la siguiente línea en AndroidManifest.xml dentro de la etiqueta <application>:

   <activity android:name="presentacion.MapaActivity" /> 

6.   Vamos a añadir en la actividad principal una nueva opción en el ActionBar para visualizar el mapa. Para ello edita el fichero res/menu/menu_main.xml y añade el siguiente ítem de menú:

<item android:title="Mapa"
      android:id="@+id/menu_mapa"
      android:icon="@android:drawable/ic_menu_myplaces"
      android:orderInCategory="100"
      app:showAsAction="always"/>  

7.   Abre la clase MainActivity y añade dentro del método onOptionsItemSelected() el siguiente código:

if (id==R.id.menu_mapa) {
   Intent intent = new Intent(this, MapaActivity.class);
   startActivity(intent);
} 
R.id.menu_mapa -> {
   startActivity(Intent(this, MapaActivity::class.java))
   true;
} 

8.   Ejecuta la aplicación en un dispositivo real y selecciona la opción que acabas de introducir. El resultado ha de ser similar al siguiente:

NOTA: Si aparece el error: NoClassDefFoundError: Lorg/ apache/ http/ ProtocolVersion consulta.

9.   Si cambias de orientación el terminal, la actividad Mapa se reinicializará y el mapa volverá a la posición inicial. Si quieres evitarlo, bloquea la orientación de esta actividad. Para ello añade el siguiente atributo en la definición de la actividad dentro de AndroidManifest.xml.

Ejercicio: Añadiendo un escuchador en Google Maps

Si en el ejercicio anterior pulsas sobre un marcador, verás como se abre una ventana de información (InfoWindow). Queremos conseguir que cuando se pulse sobre esta ventana se abra la actividad que nos muestra información detallada.

1.   Vamos a introducir un escuchador que recoja el evento correspondiente cuando se pulse sobre esta ventana. Para ello, añade al final del método onMapReady() de la actividad MapaActivity la siguiente línea:

mapa.setOnInfoWindowClickListener(this);

2.   Aparecerá un error justo en la línea que acabas de introducir. Si sitúas el cursor de texto sobre el error, aparecerá una bombilla roja con opciones para resolver el error. Selecciona “Make ‘MapaActivity’ implement ‘OnInfoWindowClickListener’”. Observa como en la definición de la clase se añade esta interfaz:

public class MapaActivity extends FragmentActivity implements 
                 OnMapReadyCallback, GoogleMap.OnInfoWindowClickListener { 
class MapaActivity: FragmentActivity(), OnMapReadyCallback, 
                                     GoogleMap.OnInfoWindowClickListener { 

3.   Ahora aparecerá un nuevo error sobre MapaActivity dado que no implementa esta interface. Aparecerá la bombilla roja con opciones para resolver el error. Selecciona “Implemented methods” y luego el único método de la interfaz. Completa este código, tal y como se muestra a continuación:

@Override public void onInfoWindowClick(Marker marker) {
    for (int id=0; id<lugares.tamanyo(); id++){
        if (lugares.elemento(id).getNombre()
                .equals(marker.getTitle())){
            Intent intent = new Intent(this, VistaLugarActivity.class);
            intent.putExtra("pos", id);
            startActivity(intent);
            break;
        }
    }
} 
override fun onInfoWindowClick(marker: Marker) {
   for (id in 0 until lugares.tamanyo()) {
      if (lugares.elemento(id).nombre == marker.title) {
         val intent = Intent(this, VistaLugarActivity::class.java)
         intent.putExtra("pos", id)
         startActivity(intent)
         break
      }
   }
} 

Se llamará a este método cuando se pulse sobre cualquier ventana de información. Para averiguar el marcador al que corresponde, se pasa el objeto Marker que se ha pulsado. En el marcador hemos introducido alguna información sobre el lugar (como el nombre); sin embargo, lo que necesitamos es el id del lugar. No resulta sencillo introducir este id en un objeto Marker. Para resolverlo hemos introducido un bucle donde se busca un lugar cuyo nombre coincida con el título de marcador. Cuando se encuentre una coincidencia, se creará una intención para lanzar la actividad correspondiente.

NOTA: Lo más correcto para resolverlo sería crear un descendiente de Marker que añada este id. Sin embargo, la clase Marker se ha marcado como final, por lo que no es posible crear descendientes.

Preguntas de repaso: GoogleMaps