terça-feira, 25 de novembro de 2014

Android - Trabalhando com Google Maps

Como marcar pontos em um mapa com a API do Google Maps? Como traçar rotas? Como customizar marcadores?

Sem mais enrolação, vamos ao passo-a-passo!

1) Este tutorial é uma continuação do tutorial Android - Iniciando Com Google Maps encontrado AQUI. Se você tem dúvidas de como incluir a API do Google Maps em sua aplicação, como adquirir a assinatura digital de sua aplicação, etc, volte e siga a primeira parte deste tutorial AQUI. Se você já incluiu o mapa em sua aplicação e apenas tem dúvidas de como utilizá-lo, siga para o passo 2) deste tutorial.

2) Como estamos continuando o tutorial anterior vale ressaltar nossa Activity está vazia e que nosso arquivo de layout contém apenas o fragmento referente ao mapa:

 
<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"  
   tools:context="com.jonathan.projetomapas.ProjetoMapasActivity" >  
   
   <fragment  
     android:id="@+id/mapa"  
     android:layout_width="match_parent"  
     android:layout_height="match_parent"  
     android:name="com.google.android.gms.maps.MapFragment"/>  
   
 </RelativeLayout>  


Sendo assim, o próximo passo é vincular o nosso componente de tela referente ao mapa com um objeto em nossa Activity para que seja possível manipulá-lo conforme desejamos. Para isso, edite sua Activity criando um objeto do tipo "com.google.android.gms.maps.GoogleMap" e recuperando um "GoogleMap" de nosso fragmento que incluímos em nosso arquivo de layout anteriormente. Para melhor visualização e manutenção do código, eu criei um método para inicializar os componentes da tela e o invoquei no método "onCreate" de nossa Activity. Segue código de como ficou nossa Activity:

 
package com.jonathan.projetomapas;  
   
import android.app.Activity;  
import android.os.Bundle;  
import com.google.android.gms.maps.GoogleMap;  
import com.google.android.gms.maps.MapFragment;  
   
   
public class ProjetoMapasActivity extends Activity {  
        
    private GoogleMap mapa;  
        
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
         super.onCreate(savedInstanceState);  
         setContentView(R.layout.activity_projeto_mapas);  
       
         inicializarComponentes();  
    }  
     
    private void inicializarComponentes(){  
         mapa = ((MapFragment) getFragmentManager().findFragmentById(R.id.mapa)).getMap();  
    }  
}  
   



3) Adicionando marcadores.
Adicionar marcadores em seu mapa é uma tarefa simples, você precisa apenas de uma latitude e longitude do local que você quer marcar em seu mapa. Para efeito de aprendizado irei abrir o Google Maps e digitar um endereço qualquer para recuperar sua longitude e latitude. Nesse caso utilizarei o endereço da Google do Brasil que fica localizada na Av. Brigadeiro Faria Lima, 3477, São Paulo. Sua latitude e longitude é -23.586328 e -46.682012 respectivamente. Agora que temos a localização, criaremos um objeto do tipo "com.google.android.gms.maps.model.LatLng" com as coordenadas que recuperamos:

 
private final LatLng cordGoogleSp = new LatLng(-23.586328, -46.682012);  


Feito isso, no método "inicializarComponentes", logo após inicializar o nosso atributo "mapa", adicionaremos este marcador com a seguinte linha de código:

 
mapa.addMarker(new MarkerOptions().position(cordGoogleSp)); 
 



Marcador adicionado! Simples, não?

4) Zoom e posicionamento em animação.
Nós adicionamos o marcador porém não demos o zoom necessário no mapa para visualizarmos onde realmente está posicionado o nosso marcador e também não deixamos o nosso marcador centralizado na tela. Faremos isso agora de forma "animada". Utilizaremos o método "animateCamera", porém se você quiser realizar a mesma tarefa de forma não animada, utilize o método "moveCamera". O método "animateCamera" que utilizaremos, recebe 3 parâmetros: Um objeto do tipo CameraUpdate, um inteiro representado a duração da animação em milissegundos e um Objeto do tipo CancelableCallback responsável por tratar os evento disparado quando tal animação é cancelada.
Como iremos atualizar o posicionamento da "câmera" de nosso mapa, criaremos um objeto do tipo "com.google.android.gms.maps.model.CameraPosition" da seguinte forma:

 
CameraPosition update = new CameraPosition(cordGoogleSp, 15, 0, 0); 
 

Onde o primeiro parâmetro é a coordenada que será colocada no centro de nossa tela do mapa, o segundo parâmetro é o nível de zoom que daremos no mapa, o terceiro é o "tilt" que seria um efeito de rotação na vertical de até 90º e o quarto é o "bearing" que seria um efeito de rotação na horizontal em até 360º. No nosso caso, assumimos que a coordenada é a mesma que utilizamos anteriormente onde adicionamos o marcador e o zoom será de nível 15. Não utilizaremos Tilt nem Bearing.
Feito isso agora podemos utilizar o método animateCamera. Usaremos a classe "com.google.android.gms.maps.CameraUpdateFactory" para criar um objeto CameraUpdate a partir de nosso CameraPosition.

 
mapa.animateCamera(CameraUpdateFactory.newCameraPosition(update), 5000, null); 
 

Definimos que ao abrir nosso mapa em nossa aplicação, o mesmo será direcionado para as coordenadas estabelecidas e dará um zoom de nível 15. Isso de forma "animada" com duração de 3 segundos! Segue como ficou o código:

 
package com.jonathan.projetomapas;  
   
import android.app.Activity;  
import android.os.Bundle;  
   
import com.google.android.gms.maps.CameraUpdateFactory;  
import com.google.android.gms.maps.GoogleMap;  
import com.google.android.gms.maps.MapFragment;  
import com.google.android.gms.maps.model.CameraPosition;  
import com.google.android.gms.maps.model.LatLng;  
import com.google.android.gms.maps.model.MarkerOptions;  
   
   
public class ProjetoMapasActivity extends Activity {  
        
     private GoogleMap mapa;  
     private final LatLng cordGoogleSp = new LatLng(-23.586328, -46.682012);  
        
     @Override  
     protected void onCreate(Bundle savedInstanceState) {  
          super.onCreate(savedInstanceState);  
          setContentView(R.layout.activity_projeto_mapas);  
       
          inicializarComponentes();  
     }  
     
     private void inicializarComponentes(){  
          mapa = ((MapFragment) getFragmentManager().findFragmentById(R.id.mapa)).getMap();  
          mapa.addMarker(new MarkerOptions().position(cordGoogleSp));  
          
          CameraPosition update = new CameraPosition(cordGoogleSp, 15, 0, 0);  
          mapa.animateCamera(CameraUpdateFactory.newCameraPosition(update), 3000, null);  
     }  
}  

5) Estruturando o código.
Antes de avançarmos nosso estudo sobre o funcionamento do Google Maps API, iremos "organizar" melhor o nosso código/aplicativo. Iremos colocar um item de menu em nossa "ActionBar" para cada item aprendido neste tutorial. Sendo assim, precisamos fazer algumas alterações em nosso código.

* A - Nossa Activity deverá estender de ActionBarActivity ao invés de Activity;
* B - Criar o arquivo ".xml" referente ao arquivo de menu;
* C - Sobrescrever os métodos "onCreateOptionsMenu" e "onOptionsItemSelected";

Vamos lá!

* A - Apenas substitua Activity por ActionBarActivity na declaração de sua Activity:

 
public class ProjetoMapasActivity extends ActionBarActivity { 
 

* B - Dentro da pasta "/res/menu" (se não existir crie-a), crie um arquivo do tipo XML com o seguinte código:

 
<menu xmlns:android="http://schemas.android.com/apk/res/android"  
   xmlns:tools="http://schemas.android.com/tools">  
   
   <item  
     android:id="@+id/action_fixe_location"  
     android:orderInCategory="100"  
     android:showAsAction="never"  
     android:title="Localização Fixa"/>  
     
   <item  
     android:id="@+id/action_my_location"  
     android:orderInCategory="100"  
     android:showAsAction="never"  
     android:title="Minha Localização"/>  
     
   <item  
     android:id="@+id/action_map_types"  
     android:orderInCategory="100"  
     android:showAsAction="never"  
     android:title="Tipos de Mapa"/>  
     
   <item  
     android:id="@+id/action_personalized_mark"  
     android:orderInCategory="100"  
     android:showAsAction="never"  
     android:title="Marcador Personalizado"/>  
     
   <item
     android:id="@+id/action_personalized_infowindow"
     android:orderInCategory="100"
     android:showAsAction="never"
     android:title="Balão de Info Personalizado"/>

   <item  
     android:id="@+id/action_map_rote"  
     android:orderInCategory="100"  
     android:showAsAction="never"  
     android:title="Traçar Rota"/>  
 </menu> 
 

* C - Adicione o seguinte trecho de código em sua Activity para manipular os itens de menu que criamos acima:

  
   @Override  
   public boolean onCreateOptionsMenu(Menu menu) {  
        //Constrói nosso menu na ActionBar de nossa aplicação  
        getMenuInflater().inflate(R.menu.projeto_mapas, menu);  
        return super.onCreateOptionsMenu(menu);  
   }  
     
   @Override  
   public boolean onOptionsItemSelected(MenuItem item) {  
        switch (item.getItemId()) {  
            case R.id.action_fixe_location:  
                 //QUANDO A OPÇÃO 'LOCALIZAÇÃO FIXA' FOR CLICADA  
                 break;  
            case R.id.action_my_location:  
                 //QUANDO A OPÇÃO 'MINHA LOCALIZAÇÃO' FOR CLICADA  
                 break;  
            case R.id.action_map_types:  
                 //QUANDO A OPÇÃO 'TIPOS DE MAPA' FOR CLICADA  
                 break;  
            case R.id.action_personalized_mark:  
                 //QUANDO A OPÇÃO 'MARCADOR PERSONALIZADO' FOR CLICADA  
                 break;
            case R.id.action_personalized_infowindow:  
                 //QUANDO A OPÇÃO 'BALÃO DE INFO PERSONALIZADO' FOR CLICADA  
                 break;
            case R.id.action_map_rote:  
                 //QUANDO A OPÇÃO 'TRAÇAR ROTA' FOR CLICADA  
                 break;  
        }  
        return super.onOptionsItemSelected(item);  
   }  


A primeira opção de nosso menu (Localização Fixa) já foi criada no passo anterior deste tutorial, sendo assim, mova o código que antes colocamos no método "inicializarComponentes" referentes à adição do marcador e à animação de posicionamento e zoom para onde comentamos "QUANDO A OPÇÃO 'LOCALIZAÇÃO FIXA' FOR CLICADA". Apenas adicionaremos mais uma linha de código no início do "case" para limpar os marcadores anteriormente posicionados. Segue código completo da Activity depois das configurações:

 
 package com.jonathan.projetomapas;  
   
 import android.os.Bundle;  
 import android.support.v7.app.ActionBarActivity;  
 import android.view.Menu;  
 import android.view.MenuItem;  
   
 import com.google.android.gms.maps.CameraUpdateFactory;  
 import com.google.android.gms.maps.GoogleMap;  
 import com.google.android.gms.maps.MapFragment;  
 import com.google.android.gms.maps.model.CameraPosition;  
 import com.google.android.gms.maps.model.LatLng;  
 import com.google.android.gms.maps.model.MarkerOptions;  
   
   
 public class ProjetoMapasActivity extends ActionBarActivity {  
        
      private GoogleMap mapa;         
      private final LatLng cordGoogleSp = new LatLng(-23.586328, -46.682012);  
        
      @Override  
      protected void onCreate(Bundle savedInstanceState) {  
         super.onCreate(savedInstanceState);  
         setContentView(R.layout.activity_projeto_mapas);  
       
         inicializarComponentes();  
      }  
     
      private void inicializarComponentes(){  
         mapa = ((MapFragment) getFragmentManager().findFragmentById(R.id.mapa)).getMap();  
      }  
     
      @Override  
      public boolean onCreateOptionsMenu(Menu menu) {  
         //Constrói nosso menu na ActionBar de nossa aplicação  
         getMenuInflater().inflate(R.menu.projeto_mapas, menu);  
         return super.onCreateOptionsMenu(menu);  
      }  
     
      @Override  
      public boolean onOptionsItemSelected(MenuItem item) {  
         switch (item.getItemId()) {  
             case R.id.action_fixe_location:  
                  mapa.clear();
                  mapa.addMarker(new MarkerOptions().position(cordGoogleSp));  
                  CameraPosition update = new CameraPosition(cordGoogleSp, 15, 0, 0);  
                  mapa.animateCamera(CameraUpdateFactory.newCameraPosition(update), 3000, null);  
                    
                     break;  
             case R.id.action_my_location:  
                  //QUANDO A OPÇÃO 'MINHA LOCALIZAÇÃO' FOR CLICADA  
                     break;  
             case R.id.action_map_types:  
                  //QUANDO A OPÇÃO 'TIPOS DE MAPA' FOR CLICADA  
                     break;  
             case R.id.action_personalized_mark: 
                  //QUANDO A OPÇÃO 'MARCADOR PERSONALIZADO' FOR CLICADA  
                     break;  
             case R.id.action_map_rote:  
                  //QUANDO A OPÇÃO 'TRAÇAR ROTA' FOR CLICADA  
                     break;  
          }  
          return super.onOptionsItemSelected(item);  
      }  
 }  
     


6) Recuperando minha localização.
A primeira coisa que vamos fazer ao clicarmos na opção "Minha Localização" do menu é limpar toda a configuração atual do mapa (não limpa o zoom, tilt ou bearing, porém, se por exemplo dermos mais 15 de zoom, o mesmo não é adicional ao zoom já configurado). Para isso, como primeira instrução no "case" referente à esta opção no menu, faremos o seguinte:

 
 mapa.clear();  


O processo de recuperar nossa localização da melhor forma possível exige alguns passos. Para facilitar a leitura do tutorial e minimizar o tempo de aprendizado, comentei cada passo que devemos realizar. Segue código do "case" de "Minha Localização" comentado:

 
    case R.id.action_my_location:  
         //Limpamos o mapa atual retirando marcadores, elipses, etc.  
         mapa.clear();  
        
         //Ativamos a identificação de nossa localização no google maps (pontinho azul no mapa)  
         mapa.setMyLocationEnabled(true);  
   
         //Criamos um 'Listener' para tratar o evento disparado todas as vezes que a localização é alterada  
         mapa.setOnMyLocationChangeListener(new OnMyLocationChangeListener() {  
              @Override  
              public void onMyLocationChange(Location minhaLocalizacao) {  
                   //Limpamos novamente o mapa atual retirando marcadores, elipses, etc.  
                   mapa.clear();  
                                 
                   //Criamos o abjeto LatLng a partir de nossa localizacao  
                   LatLng latLngMinhaLocalizacao = new LatLng(minhaLocalizacao.getLatitude(),   
                                                            minhaLocalizacao.getLongitude());  
                        
                   //Adicionamos o marcador no mapa para a nossa localização
                   mapa.addMarker(new MarkerOptions().position(latLngMinhaLocalizacao));  
                        
                   //Repetimos o processo de animação para a nossa posição no mapa  
                   CameraPosition updateMinhaLocalizacao = new CameraPosition(latLngMinhaLocalizacao, 15, 0, 0);  
                   mapa.animateCamera(CameraUpdateFactory.newCameraPosition(updateMinhaLocalizacao), 3000, null);  
              }  
         });  
   break;  


Já que adicionamos um "Listener" que irá criar a nossa "animação" todas as vezes que a localização for alterada, temos que lembrar de tirar esse "Listener" nas outras opções de nosso menu. Adicione a seguinte linha no inicio de todas as outras opções que temos no menu (menos no item referente aos "tipos de mapa" pois nesse item não mudaremos a localização no mapa):

 
 mapa.setOnMyLocationChangeListener(null);  
   



7) Mudando o tipo do mapa.
Mudar o tipo de mapa é uma tarefa simples e fácil. Como utilizaremos apenas um botão (item de menu) para mudar o tipo do mapa para os quatro tipos (Híbrido, Terreno, Satélite, Normal), apenas faremos uma verificação de qual o tipo atual e mudaremos para o próximo tipo respeitando a sequencia citada acima. Para isso utilizaremos o método "setMapType" para alterar o tipo de mapa e o método "getMapType" para verificar qual o tipo atual do mapa.

 
case R.id.action_map_types:  
                       
    if(mapa.getMapType() == GoogleMap.MAP_TYPE_HYBRID){  
        mapa.setMapType(GoogleMap.MAP_TYPE_TERRAIN);  
                            
    }else if(mapa.getMapType() == GoogleMap.MAP_TYPE_TERRAIN){  
        mapa.setMapType(GoogleMap.MAP_TYPE_SATELLITE);  
                            
    }else if(mapa.getMapType() == GoogleMap.MAP_TYPE_SATELLITE){  
        mapa.setMapType(GoogleMap.MAP_TYPE_NORMAL);  
                            
    }else{  
        mapa.setMapType(GoogleMap.MAP_TYPE_HYBRID);  
    }  
break; 
 

Os tipos de mapa a seguir são Hibrido, Satélite e Terreno respectivamente:



8) Customizando marcadores.
Podemos customizar os marcadores que colocamos em nosso mapa adicionando títulos, descrição, alterando cores, colocando ícones, etc. Para este exemplo, irei colocar um marcador em meu endereço residencial atual e customizarei este marcador.
Primeiro, vá ao Google Maps e recupere a latitude e longitude de sua residência. Feito isso, crie um atributo em sua Activity do tipo "LatLng" com esses valores (já fizemos isso antes):

 
 private final LatLng cordMyHome = new LatLng(-23.555997,-46.481778);  


Como já definimos anteriormente, retiramos o "Listener" que manipula a mudança de localização de nosso mapa:

 
 mapa.setOnMyLocationChangeListener(null);  
   

Limpe os marcadores colocados no mapa anteriormente:

 
 mapa.clear(); 
 

Agora crie o marcador adicionando-o ao seu mapa:

 
 Marker meuMarcadorMarker = mapa.addMarker(new MarkerOptions().position(cordMyHome));
  

Adicione um título e descrição de sua preferência ao seu marcador através dos métodos "setTitle" e "setSnippet" para configurar o título e a descrição respectivamente:

 
 meuMarcadorMarker.setTitle("Minha Casa");  
 meuMarcadorMarker.setSnippet("Esta é minha residência..."); 
 

Para alterar o ícone do marcador, recupere o ícone que deseja (o ícone que coloquei pode ser encontrado AQUI), e o coloque na pasta "/res/drawable-hdpi" de sua aplicação. Não esqueça de redimensionar a imagem para o tamanho adequado (no caso da imagem que utilizei eu a redimensionei para 40x39 pixels e de renomea-la para que o nome da mesma não contem caracteres especiais nem letras maiúsculas). Depois de colocá-la na pasta indicada, compile seu aplicativo e adicione a seguinte linha na personalização de seu marcador:

 
 meuMarcadorMarker.setIcon(BitmapDescriptorFactory.fromResource(R.drawable.home_icon_custom));  


Onde "home_icon_custom" será o nome que você deu à sua imagem e a classe R é a do seu projeto, não a do pacote do Android.
Tornamos a nossa "Janela de Informações" (balão onde fica o título e descrição do marcador) visível por padrão. Caso queira que esta janela fique visível apenas com o clique no marcador ignore esta linha de código.

 
 meuMarcadorMarker.showInfoWindow();  
   

Repetimos a animação para o local que indicamos no mapa:

 
 CameraPosition updateMinhaCasa = new CameraPosition(cordMyHome, 15, 0, 0);  
 mapa.animateCamera(CameraUpdateFactory.newCameraPosition(updateMinhaCasa), 3000, null);  




9) Customizando 'InfoWindow' (balão de informações do marcador).
Para customizar o balão de informações do marcador precisaremos de um "Adapter". Na declaração de nossa Activity iremos definir que a mesma irá implementar a interface "com.google.android.gms.maps.GoogleMap.InfoWindowAdapter":

 
 public class ProjetoMapasActivity extends ActionBarActivity implements InfoWindowAdapter 
 

Com isso, temos que implementar os seguintes métodos:

   
 @Override  
 public View getInfoContents(Marker arg0) {  
             
      return null;  
 }  
   
 @Override  
 public View getInfoWindow(Marker arg0) {  
             
      return null;  
 }  
 

O primeiro método a ser invocado será o "getInfoWindow" que se retornado "null" então será invocado o método "getInfoContents" que por sua vez, se retornado "null" então o balão de informações padrão será utilizado.
A diferença é que o "getInfoWindow" permite a você oferecer uma view para ser utilizada. Já o método "getInfoContent" permite a você customizar os conteúdos do balão porém sem alterar o seu frame e seu background.
Utilizaremos a segunda opção que é mais simples para efeito de aprendizado.

Crie um arquivo XML de Layout para o seu balão de informações dentro da pasta "/res/layout/" do mesmo modo que é feito com Layouts referentes às Activities. Neste exemplo criei um arquivo chamado "infowindow_map_custom" com o seguinte layout:

 <?xml version="1.0" encoding="utf-8"?>  
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   android:layout_width="match_parent"  
   android:layout_height="wrap_content"   
   android:padding="20dp"  
   android:background="@color/wallet_holo_blue_light">  
   
   <ImageView  
     android:id="@+id/infowindow_imageview_imagem"  
     android:layout_width="40dp"  
     android:layout_height="40dp"  
     android:layout_alignParentLeft="true"  
     android:layout_alignParentTop="true"  
     android:src="@drawable/home_icon_custom" />  
   
   <LinearLayout   
     android:layout_width="fill_parent"  
     android:layout_height="wrap_content"  
     android:orientation="vertical"  
     android:layout_toRightOf="@+id/infowindow_imageview_imagem" 
     android:gravity="center"  
     android:layout_alignBottom="@+id/infowindow_imageview_imagem"  
     android:layout_alignTop="@+id/infowindow_imageview_imagem">  
       
     <TextView  
          android:id="@+id/infowindow_textview_titulo"  
          android:layout_width="wrap_content"  
          android:layout_height="wrap_content"  
          android:gravity="center"  
          android:text="Título"  
          android:textSize="15sp"  
          android:textStyle="bold"  
          android:textColor="@android:color/black"/>  
        
        <TextView  
          android:id="@+id/infowindow_textview_descricao"  
          android:layout_width="wrap_content"  
          android:layout_height="wrap_content"  
          android:text="Descrição aqui..."  
          android:textSize="12sp"  
          android:textStyle="italic"  
          android:textColor="@android:color/black"/>  
   </LinearLayout>  
 </RelativeLayout>
  

Crie um atributo em sua Activity do tipo "View" da seguinte forma:

 
 private View markerInfo; 
 

Implementamos o método "getInfoContents" recuperando os dados de nosso marcador e exibindo-os em nossa view personalizada:

 
 @Override
 public View getInfoContents(Marker arg0) {
     //Inflamos o nosso arquivo de layout em uma view
     markerInfo = getLayoutInflater().inflate(R.layout.infowindow_map_custom, null);
  
     //Configuramos o tamanho de nossa view
     markerInfo.setLayoutParams(new RelativeLayout.LayoutParams(200, RelativeLayout.LayoutParams.WRAP_CONTENT));
  
     //Vinculamos objetos com os componentes de tela em nossa view
     TextView titulo = (TextView) markerInfo.findViewById(R.id.infowindow_textview_titulo);
     TextView descricao = (TextView) markerInfo.findViewById(R.id.infowindow_textview_descricao);
  
     //Recuperamos os valores de nosso marcador e o colocamos em nossa view
     titulo.setText(arg0.getTitle());
     descricao.setText(arg0.getSnippet());
  
     //Retornamos para a tela a nossa view personalizada
     return markerInfo;
 }


Agora no "case" referente à opção de "Balão de Info Personalizado" configuramos o nosso Adapter em nosso mapa e repetimos o processo feito no "case" de "Marcador Personalizado" apenas para termos uma posição no mapa com informações no marcador:

 
 case R.id.action_personalized_infowindow:  
                       
     //Configuramos nosso adaptador em nosso mapa  
     mapa.setInfoWindowAdapter(this);  
                       
     //Repitimos o processo do passo anterior apenas para termos um marcador com informações a mostrar  
     mapa.setOnMyLocationChangeListener(null);  
     mapa.clear();  
                       
     Marker marcadorMinhaCasa = mapa.addMarker(new MarkerOptions().position(cordMyHome));  
                       
     marcadorMinhaCasa.setTitle("Minha Casa");  
     marcadorMinhaCasa.setSnippet("Esta é minha residência...");  
     marcadorMinhaCasa.setIcon(BitmapDescriptorFactory.fromResource(R.drawable.home_icon_custom));  
     marcadorMinhaCasa.showInfoWindow();  
                       
     CameraPosition updateMyHome = new CameraPosition(cordMyHome, 15, 0, 0);  
     mapa.animateCamera(CameraUpdateFactory.newCameraPosition(updateMyHome), 3000, null);  
 break;
  

Agora, em todos os outros "cases" (menos o case de Tipos de mapa) devemos limpar esta configuração de Adapter que colocamos, assim como limpamos a configuração do Listener anteriormente. Para isso adicione a seguinte linha no inicio dos outros "cases":

 
 mapa.setInfoWindowAdapter(null);  


Resultado do nosso "balão de informações" do marcador personalizado:



10) Traçando rotas.
Para traçarmos uma rota em nosso mapa utilizaremos o serviço The Google Directions API onde faremos uma requisição HTTP e o mesmo nos retornará um JSON com o trajeto a ser "pintado".
Crie um método em sua Activity que recebe as latitudes e longitudes de origem e destino. O mesmo será responsável por montar a URL de chamada ao serviço. Segue código:

 
 private String montarURLRotaMapa(double latOrigen, double lngOrigen, double latDestino, double lngDestino){  
     //Base da URL  
     String url = "http://maps.googleapis.com/maps/api/directions/json?origin=";  
     //Local de origem  
     url += latOrigen + "," + lngOrigen;  
     url += "&destination=";  
     //Local de destino  
     url += latDestino + "," + lngDestino;  
     //Outros parametros  
     url += "&sensor=false&mode=driving&alternatives=true";  
       
     return url;  
 }  


Agora desenvolveremos o método que fará a chamada ao serviço The Google Directions API através desta URL e converterá o resultado em JSON. Segue código comentado:

 
 public JSONObject requisicaoHTTP(String url) {  
     JSONObject resultado = null;  
             
     try {  
          //Criamos um cliente HTTP para que possamos realizar a   
          //requisição a um Servidor HTTP  
          DefaultHttpClient httpClient = new DefaultHttpClient();  
          //Definimos o método de requisição como sendo POST  
          HttpPost httpPost = new HttpPost(url);  
          //Recuperamos a resposta do Servidor HTTP  
          HttpResponse httpResponse = httpClient.execute(httpPost);  
          //Recuperamos o conteúdo enviado do Servidor HTTP  
          HttpEntity httpEntity = httpResponse.getEntity();  
          //Transformamos tal conteúdo em 'String'  
          String conteudo = EntityUtils.toString(httpEntity);   
         
          //Transformamos a 'String' do conteúdo em Objeto JSON  
          resultado = new JSONObject(conteudo);  
         
     } catch (UnsupportedEncodingException e) {  
          Log.e("ProjetoMapas", e.getMessage());  
     } catch (ClientProtocolException e) {  
          Log.e("ProjetoMapas", e.getMessage());  
     } catch (IOException e) {  
          Log.e("ProjetoMapas", e.getMessage());  
     } catch (JSONException e) {  
          Log.e("ProjetoMapas", e.getMessage());  
     }  
       
     //Retornamos o conteúdo em formato JSON  
     return resultado;  
 } 
 

Desenvolvemos também o método que irá literalmente "pintar o caminho" entre os dois pontos que escolheremos em nosso mapa. Segue código comentado:

 
 public void pintarCaminho(JSONObject json) {  
     try {  
         //Recupera a lista de possíveis rotas  
         JSONArray listaRotas = json.getJSONArray("routes");  
         //Para efeito de aprendizado iremos utilizar apenas a primeira opção  
         JSONObject rota = listaRotas.getJSONObject(0);  
         //Recuperamos os pontos a serem pintados para que surga a 'linha' no mapa  
         String pontosPintar = rota.getJSONObject("overview_polyline").getString("points");  
         //Recuperamos a lista de latitudes e longitudes para sabermos exatamente onde pintar  
         List<LatLng> listaCordenadas = extrairLatLngDaRota(pontosPintar);  
   
         //Percorremos por cada cordenada obtida  
         for(int ponto = 0; ponto < listaCordenadas.size()-1 ; ponto++){  
             //Definimos o ponto atual como origem  
             LatLng pontoOrigem= listaCordenadas.get(ponto);  
             //Definimos o próximo ponto como destino  
             LatLng pontoDestino= listaCordenadas.get(ponto + 1);  
             //Criamos um objeto do tipo PolylineOption para adicionarmos os pontos de origem e destino  
             PolylineOptions opcoesDaLinha = new PolylineOptions();  
             //Adicionamos os pontos de origem e destino da linha que vamos traçar  
             opcoesDaLinha.add(new LatLng(pontoOrigem.latitude, pontoOrigem.longitude),   
                    new LatLng(pontoDestino.latitude,  pontoDestino.longitude));  
             //Criamos a linha de acordo com as opções que configuramos acima e adicionamos em nosso mapa  
             Polyline line = mapa.addPolyline(opcoesDaLinha);  
             //Mudamos algumas propriedades da linha que acabamos de adicionar em nosso mapa  
             line.setWidth(5);  
             line.setColor(Color.BLUE);  
             line.setGeodesic(true);  
         }  
     }   
     catch (JSONException e) {  
         Log.e("ProjetoMapas", e.getMessage());  
     }  
 }   


Percebe-se que invocamos uma função chamada "extrairLatLngDaRota" que ainda não existe. Como o nome já é bastante intuitivo, esta função será responsável por decodificar e nos retornar a lista de Longitudes e Latitudes da rota que será traçada. A API retornará para nós algo parecido com isso:

 
~wwnCxmuzG{GSDg@HyASWiAE?j@BLNLb@JH@Iz@E|@AlBJrBb@dDF`A?dAIpAGb@e@~Ak@`CUlBQvBy@vPYnFEh@CREBUJ}ChAqAh@i@f@[l@i@tAuBbHk@fAoBrCaAlAsCdEcDfEQT]n@Wj@e@rAOp@e@jCMpBGpAMlAm@xCa@dCYtAs@jByCrGoF~LuA|C_AfBaD~EyCjEaApAqBzCuChEoAfBwDxFmBtCu@`AoBtCqAhBkBlBmFzFk@j@eAp@s@Z}Bv@mEdBaF|BUFaBPY@LvALhALjBFhDJ`FXjFf@hITvEBzACvAMhCa@~FIzD@`DFnA`@`EfAfK\xB\lBXnBZtCLfAV|AZ`BVfBXhCLjBPdCt@|GfAnMh@bFLxBPxAjAvHdCxRt@dFVrAtAjMf@nE`BrNdBrN~@rHlAbLLnCJjAvAjNz@nHN~@tAnFp@rCd@hCXfBPfBrAnKPv@lAvDf@vBZzBTvBZbDN|@n@tE`@nDzBdRPbCT~AnBfPpAbLjBdOTtCLjAR`Az@fDp@rBdAbCrE|JpBhEfA~BdBjD~DrIbEpJ`AzBhDlHnArCd@nAb@tAf@`Cj@tCFp@Dv@EvBm@pMg@pM[~DmApQM|BSdE[zE@r@F^Vt@`@j@rAv@|C`BzDbBnF~Bp@`@h@j@b@dARz@Dz@LrFV~IDNJ|CJnJ?|@I~AOzBORIf@UpAe@|EMjAGHSHKAOEyCyBYQCGGUBULIP?bANV?tBn@j@N~@V|HzBlCx@|@\rA^l@b@~BnAzAl@|@V`AP|C^hBHdADlDR|Ch@lE`@v@@nBKtBKfCBxDVxCNbALzBHlITvDJfGZ`@Bj@J`AR^PZVRVvBlD`D|F|JxOtB|CtCfElDrF\h@fBnCfAnBXf@B\@HbAnBr@bAdAlAbAbBj@rATxAZnB^zAp@|A~@zAf@r@pCzDzHrLRf@dAxET|ABlBKdBa@|D_@xEKhASlAo@hBeAfBeBrCSHEHQTaBjCc@pAMj@Ed@Ch@EpE@jADz@|@fE|@vCZ`AfBvD^n@t@bAp@t@|BzBlDhD@PjAhA^nAN~@jAjH~@`FdA~FJr@o@Lq@LkDn@yA`@cCb@_C`@mDn@ 
 

Para decodificar isso utilizaremos um código bem famoso e facilmente encontrado. Não me atreverei a comentá-lo para não acabar interpretando de maneira errônea e ensinando besteiras (rsrs).
Segue código:

 
 private List<LatLng> extrairLatLngDaRota(String pontosPintar) {  
     List<LatLng> listaResult = new ArrayList<LatLng>();  
     int index = 0, len = pontosPintar.length();  
     int lat = 0, lng = 0;  
    
     while (index < len) {  
               
          int b, shift = 0, result = 0;  
          do {  
               b = pontosPintar.charAt(index++) - 63;  
               result |= (b & 0x1f) << shift;  
               shift += 5;  
          } while (b >= 0x20);  
            
          int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));  
          lat += dlat;  
   
          shift = 0;  
          result = 0;  
          do {  
               b = pontosPintar.charAt(index++) - 63;  
               result |= (b & 0x1f) << shift;  
               shift += 5;  
          } while (b >= 0x20);  
            
          int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));  
          lng += dlng;  
   
          LatLng p = new LatLng( (((double) lat / 1E5)),  
               (((double) lng / 1E5) ));  
          listaResult.add(p);  
     }  
   
     return listaResult;  
 }  


Estamos com "quase" toda a estrutura montada para ser testada. Sabemos que nada relacionado à tráfego de rede pode ser disparado diretamente na Thread Main, sendo assim, usaremos uma "AsyncTask" para efetuarmos a requisição HTTP que estruturamos anteriormente. Para saber mais sobre como funciona a AsyncTask clique aqui.
Crie uma classe private que seja inner (aninhada) em sua classe. Para isso, simplesmente declare uma nova classe dentro de sua Activity:

 
 private class MinhaAsyncTask extends AsyncTask<String, Void, JSONObject>{  

 }


Onde o primeiro parâmetro (String) será o tipo de parâmetro que nossa tarefa assíncrona irá precisar para ser executada, o segundo (Void) será o tipo de parâmetro que indicará qual o status em nível de completude a nossa tarefa se encontra (não utilizaremos neste tutorial) e o terceiro (JSONObject), de qual tipo será o retorno de nosso método principal.
Toda AsyncTask precisa que ao menos um método seja implementado, o "doInBackground" onde (no nosso caso) começará tudo. Sobrescreva este método com a seguinte assinatura:

 
 @Override  
 protected JSONObject doInBackground(String... params) {  
            
      return null;  
 }  


Sobrescreva também o método "onPostExecute" que será invocado automaticamente logo após o retorno do método acima. Segue assinatura:

 
 @Override  
 protected void onPostExecute(JSONObject result) {  
            
       super.onPostExecute(result);    
 }  

Em nosso método "doInBackground" iremos apenas realizar a requisição HTTP e retornar o resultado de nossa requisição:


 @Override  
 protected JSONObject doInBackground(String... params) {  
      JSONObject resultJSON = requisicaoHTTP(params[0]);  
            
      return resultJSON;  
 }  


E em nosso método "onPostExecute" apenas faremos a verificação se o resultado de nossa requisição não é nulo e caso não seja, chamaremos a função responsável por pintar a nossa rota em nosso mapa:

 
 @Override  
 protected void onPostExecute(JSONObject resultadoRequisicao) {  
    super.onPostExecute(resultadoRequisicao);    
      
    if(resultadoRequisicao != null){  
       pintarCaminho(resultadoRequisicao);  
    }  
 }  


Nossa AsyncTask completa:

 
 private class MinhaAsyncTask extends AsyncTask<String, Void, JSONObject>{  
   
     @Override  
     protected JSONObject doInBackground(String... params) {  
       JSONObject resultJSON = requisicaoHTTP(params[0]);  
         
       return resultJSON;  
     }  
          
     @Override  
     protected void onPostExecute(JSONObject resultadoRequisicao) {  
       super.onPostExecute(resultadoRequisicao);    
      
       if(resultadoRequisicao != null){  
         pintarCaminho(resultadoRequisicao);  
       }  
     }  
 }  


Feito isso, vamos executar nossa tarefa assíncrona no clique de nosso botão "Traçar Rota" do menu que definimos. Dependendo de qual versão de API do Android estamos usando a forma de executar uma AsyncTask muda. Segue código comentado referente à ação a ser executada pelo clique do botão "Traçar Rota":

 
 case R.id.action_map_rote:
      //Limpamos quaisquer configurações anteriores em nosso mapa
      mapa.setInfoWindowAdapter(null);
      mapa.setOnMyLocationChangeListener(null);
      mapa.clear();  

      //Recuperamos a URL passando as cordenadas de origem como sendo a cordenada que definimos   
      //para a nossa residência e as coordenadas de destino como sendo a do escritório da Google em SP.  
      String url = montarURLRotaMapa(cordMyHome.latitude, cordMyHome.longitude, cordGoogleSp.latitude, cordGoogleSp.longitude);  
      //Criamos uma instância de nossa AsyncTask (para cada requisição deverá ser criada uma nova instância).  
      MinhaAsyncTask tarefa = new MinhaAsyncTask();  
                       
      //Se a versão de SDK do Android do aparelho que está executando o aplicativo for menor que a HONEYCOMB (11)  
      if(Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB){  
          //Executa a tarefa passando a URL recuperada  
          tarefa.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url);  
      }else{  
          //Executa a tarefa passando a URL recuperada  
          tarefa.execute(url);  
      }  
      
      //Adicionamos um marcador no ponto de partida e no ponto de destino
      mapa.addMarker(new MarkerOptions().position(cordMyHome));
      mapa.addMarker(new MarkerOptions().position(cordGoogleSp));

      //Animação para visualizar ponto de partida
      CameraPosition updateRota = new CameraPosition(cordMyHome, 15, 0, 0);
      mapa.animateCamera(CameraUpdateFactory.newCameraPosition(updateRota), 3000, null);
              
 break;  


Execute o código e terá a rota de "sua residência" ao escritório da Google em SP traçada no mapa em seu dispositivo!



É isso aí pessoal!! Agora você está apto a trabalhar com a API do Google Maps para Android! Aprendemos a adicionar marcadores, personalizar marcadores, personalizar o "balão de informações" do marcador, a recuperar sua localização, traçar rotas, etc..

Se você seguiu todo o passo-a-passo você terá em mãos um aplicativo com um mapa e suas principais ações!! Até o próximo!!