Tutorial Android #19 – Integrando com o Google Maps

Padrão

Olá pessoal! Como estão?

No tutorial de hoje, vamos aproveitar as coordenadas do GPS obtidas com o tutorial 17 e inseri-las em um mapa provido pelo Google Maps. Portanto, precisaremos que ele esteja instalado no emulador ou no seu celular para tal funcionalidade. Além disso, você também precisará de uma chave de desenvolvimento para que possa ocorrer a integração. Neste link, você obtém as informações necessárias sobre como conseguir esta chave.

Começando, vamos adicionar a opção para a visualização do mapa no FormularioDetalhes. Dessa forma, modifique o arquivo res/menu/opcao_detalhes.xml:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
	<item android:id="@+id/twitter"
	    android:title="Timeline Twitter"
	    android:icon="@drawable/twitter"/>
	<item android:id="@+id/localizacao"
	    android:title="Salvar Localização"
	    android:icon="@drawable/gps"/>
	<item android:id="@+id/mapa"
	    android:title="Exibir no Mapa"
	    android:icon="@drawable/mapa"/>
</menu>

O ícone do menu (mapa.png) é o ícone ic_menu_mapmode.png, devidamente renomeado, que pode ser encontrado lá na pasta da sua instalação do Android SDK.

Em seguida, vamos modificar o método onPrepareOptionsMenu() no FormularioDetalhes para somente habilitar esta opção quando já houverem as informações básicas do restaurante (edição).

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
	if (idRestaurante == null) {
		menu.findItem(R.id.localizacao).setEnabled(false);
		menu.findItem(R.id.mapa).setEnabled(false);
	}

	return super.onPrepareOptionsMenu(menu);
}

O próximo passo é criar uma Activity que será responsável por exibir o nosso mapa. Porém, antes disso, vamos informar ao arquivo AndroidManifest.xml que nossa aplicação fará uso da API do Google Maps. Dessa forma, dentro do nó application, adicione a seguinte linha:

<uses-library android:name="com.google.android.maps"/>

Em seguida, vamos criar uma nova classe no pacote principal do projeto (net.rafaeltoledo.restaurante), com o nome de MapaRestaurante. Esta classe estenderá MapActivity. Inicialmente teremos o método onCreate(), onde simplesmente atribuiremos seu layout, e isRouteDisplayed(), método abstrato exigido. Neste segundo, por ora simplesmente retornaremos falso.

package net.rafaeltoledo.restaurante;

import android.os.Bundle;
import com.google.android.maps.MapActivity;

public class MapaRestaurante extends MapActivity {

	@Override
	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		setContentView(R.layout.mapa);
	}

	@Override
	protected boolean isRouteDisplayed() {
		return false;
	}
}

O próximo passo é modificar o método onOptionsItemSelected() para que  inicie a Activity MapaRestaurante.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
	if (item.getItemId() == R.id.twitter) {
		if (redeDisponivel()) {
			Intent i = new Intent(this, TwitterActivity.class);
			i.putExtra(TwitterActivity.PERFIL, twitter.getText().toString());
			startActivity(i);
		} else {
			Toast.makeText(this, "Conexão com a Internet indisponível", Toast.LENGTH_LONG).show();
		}

		return true;
	} else if (item.getItemId() == R.id.localizacao) {
		locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
				0, 0, onLocationChange);
	} else if (item.getItemId() == R.id.mapa) {
		Intent i = new Intent(this, MapaRestaurante.class);

		startActivity(i);
		return true;
	}

	return super.onOptionsItemSelected(item);
}

O próximo passo é criarmos o XML que definirá o layout da Activity MapaRestaurante. Aqui vale a pena ressaltar dois pontos importantes:

  • Como o “layout” MapView não faz parte da biblioteca padrão de widgets do Android, é necessário especificar todo o seu caminho e;
  • Nesse ponto será necessária a chave para a API do Google Maps.

Prosseguindo, vamos criar um arquivo chamado mapa.xml que será salvo em res/layout:

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.maps.MapView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mapa"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:apiKey="0ewdgToro3C6BxW8TV976XhcuSXX4PWxN72Vz2w"
    android:clickable="true" />

Faça seu registro e substitua o valor pela sua própria chave (é gratuito!). Além disso, vale ressaltar que configuramos o atributo clickable como true para que o usuário possa movimentar o mapa, dar zoom, etc. Também devemos adicionar a nossa Activity no AndroidManifest.xml:

<activity android:name=".MapaRestaurante">            
</activity>

Para obter a latitude e longitude do FormularioDetalhes para o MapaRestaurantes, utilizaremos novamente os parâmetros extras possíveis de serem transferidos entre Acitivies através da classe Intent. Dessa forma, primeiramente vamos criar três atributos estáticos na classe MapaRestaurantes (nome do restaurante, latitude e longitude).

public static final String LATITUDE_EXTRA = "net.rafaeltoledo.LATITUDE_EXTRA";
public static final String LONGITUDE_EXTRA = "net.rafaeltoledo.LONGITUDE_EXTRA";
public static final String NOME_EXTRA = "net.rafaeltoledo.NOME_EXTRA";

Porém, temos um pequeno problema. Atualmente em nossa aplicação, não estamos armazenando no formulário os valores de latitude e longitude do restaurante atual. Para resolver isso, vamos criar dois atributos do tipo double chamados latitude e longitude na classe FormularioDetalhes.

double latitude = 0.0;
double longitude = 0.0;

E então atribuir seus valores no método carregar().

private void carregar() {
	Cursor c = gerenciador.obterPorId(idRestaurante);

	c.moveToFirst();
	nome.setText(gerenciador.obterNome(c));
	endereco.setText(gerenciador.obterEndereco(c));
	anotacoes.setText(gerenciador.obterAnotacoes(c));
	twitter.setText(gerenciador.obterTwitter(c));

	if (gerenciador.obterTipo(c).equals("rodizio")) {
		tipos.check(R.id.rodizio);
	} else if (gerenciador.obterTipo(c).equals("fast_food")) {
		tipos.check(R.id.fast_food);
	} else {
		tipos.check(R.id.a_domicilio);
	}

	latitude = gerenciador.obterLatitude(c);
	longitude = gerenciador.obterLongitude(c);

	localizacao.setText(String.valueOf(gerenciador.obterLatitude(c)) +
			", " + String.valueOf(gerenciador.obterLongitude(c)));

	c.close();
}

Agora modificamos mais uma vez o método onOptionsItemSelected() para passar os valores para a Activity MapaRestaurantes, através dos atributos extras da classe Intent.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
	if (item.getItemId() == R.id.twitter) {
		if (redeDisponivel()) {
			Intent i = new Intent(this, TwitterActivity.class);
			i.putExtra(TwitterActivity.PERFIL, twitter.getText().toString());
			startActivity(i);
		} else {
			Toast.makeText(this, "Conexão com a Internet indisponível", Toast.LENGTH_LONG).show();
		}

		return true;
	} else if (item.getItemId() == R.id.localizacao) {
		locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
				0, 0, onLocationChange);
	} else if (item.getItemId() == R.id.mapa) {
		Intent i = new Intent(this, MapaRestaurante.class);

		i.putExtra(MapaRestaurante.LATITUDE_EXTRA, latitude);
		i.putExtra(MapaRestaurante.LONGITUDE_EXTRA, longitude);
		i.putExtra(MapaRestaurante.NOME_EXTRA, nome.getText().toString());

		startActivity(i);
		return true;
	}

	return super.onOptionsItemSelected(item);
}

Na classe MapaRestaurantes, vamos obter os valores de latitude e longitude adicionando as duas linhas seguintes ao fim do método onCreate():

double latitude = getIntent().getDoubleExtra(LATITUDE_EXTRA, 0);
double longitude = getIntent().getDoubleExtra(LONGITUDE_EXTRA, 0);

Precisamos agora de gerenciar a montagem do mapa, ajustando o zoom a um valor razoável, centralizando o marcador na tela e controlando o posicionamento das coordenadas, através de um GeoPoint. Para isso, vamos criar um membro na classe do tipo MapView chamado mapa.

private MapView mapa = null;

E então, vamos adicionar mais algumas linhas ao fim do método onCreate() para criar o GeoPoint e adicionar as coordenadas a ele.

mapa = (MapView) findViewById(R.id.mapa);
mapa.getController().setZoom(17);
GeoPoint status = new GeoPoint((int) latitude * 1000000, (int) longitude * 1000000);
mapa.getController().setCenter(status);
mapa.setBuiltInZoomControls(true);

Vamos agora criar uma classe interna chamada SobreposicaoRestaurante que, como o próprio nome diz, será responsável por gerenciar a sobreposição do elemento no mapa.

private class SobreposicaoRestaurante extends ItemizedOverlay {
	private OverlayItem item = null;

	public SobreposicaoRestaurante(Drawable marcador, GeoPoint ponto, String nome) {
		super(marcador);

		boundCenterBottom(marcador);
		item = new OverlayItem(ponto, nome, nome);
		populate();
	}

	@Override
	protected OverlayItem createItem(int arg0) {
		return item;
	}

	@Override
	public int size() {
		return 1;
	}
}

Nela, gerenciamos para que aconteça a centralização do ponto, além de configurar o nosso ponto no mapa. Vamos agora adicionar mais algumas linhas ao final do método onCreate() para, aí sim, desenhar o ponto no mapa.

Drawable marcador = getResources().getDrawable(R.drawable.marcador);
	marcador.setBounds(0, 0, marcador.getIntrinsicWidth(), marcador.getIntrinsicHeight());
	mapa.getOverlays().add(new SobreposicaoRestaurante(marcador, status, getIntent().getStringExtra(NOME_EXTRA)));

O arquivo do marcador (marcador.png) pode ser baixado juntamente com o projeto no fim do post.

Por fim, para finalizar, vamos gerenciar para que, quando o usuário toque na tela do mapa, ele exiba o nome do restaurante que foi marcado. Coisa simples, mas útil por questões de estética. Para fazer isso, implemente o método onTap() na classe interna SobreposicaoRestaurante.

@Override
public boolean onTap(int index) {
	Toast.makeText(MapaRestaurante.this, item.getSnippet(), Toast.LENGTH_SHORT).show();
	return true;
}

Agora já podemos testar nossa aplicação.

Se o mapa não apareceu, provavelmente é porque você utilizou a minha chave de acesso ao Google Maps. 😛

A chave deve bater com a chave armazenada na sua máquina. Toda instalação da SDK do Android vem com uma chave que deve ser usada para gerar o MD5 requerido lá na página do Google Maps (link lá no começo do post, lembra?). Supondo que a pasta bin da sua JDK já esteja no Path do sistema, abra uma janela console a navegue até a pasta .android dentro da sua pasta pessoal (no meu caso, que estou usando o Ubuntu, /home/rafael/.android. No caso do Windows, seria C:\Users\Rafael\.android). Dentro dessa pasta deve conter o arquivo debug.keystore. Digite então o seguinte comando no console:

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

Deve aparecer algo como androiddebugkey, Apr 3, 2012, PrivateKeyEntry,Certificate fingerprint (MD5): …, sendo que onde estão as reticências deve aparecer o que importa pra nós. Se caso estiver usando Java 7, adicione a opção -v ao comando. Ah, e eu vi bastante gente na Internet dizendo que não conseguiu executar o comando no Windows… talvez por algum bug?

Enfim, espero que tenham curtido mais esse tutorial. Como é de praxe, pra baixar o projeto, só clicar aqui.

Até logo!