Mostrando postagens com marcador GPS. Mostrar todas as postagens
Mostrando postagens com marcador GPS. Mostrar todas as postagens

segunda-feira, 21 de novembro de 2011

Google Maps e GPS

Olá povo,

Nesse post de 2009 mostrei como fazer um "Hello World" com a API do Google Maps. Agora vamos incrementar um pouco esse exemplo colocando uma imagem para indicar o seu local baseado na posição GPS.

Pré-requisitos
A configuração inicial para obter a chave e exibir o mapa na tela é idêntica a apresentada no post de dois anos atrás, mas caso não consiga gerar a chave, dê uma olhada nesse post aqui (Dica 5). Outra ressalva é que, caso você esteja usando o emulador para testar sua aplicação, certifique-se de que ele esteja usando a "Google APIs" no "Android AVD Manager" conforme a figura abaixo:



O código do Overlay
Uma coordenada geográfica é representada pela classe GeoPoint. Ela tem a latitude e longitude de um ponto no mapa. Entretanto essa classe não é visual, ou seja, ela não serve para enxergarmos o ponto no mapa. Quando queremos adicionar no mapa alguma indicação para uma coordenada geográfica, devemos utilizar a classe Overlay, que é uma subclasse de View. O código abaixo cria um Overlay que desenhará uma imagem em um dado ponto no mapa (representado por um GeoPoint).
public class MeuOverlay extends Overlay {

private Bitmap imagem;
private GeoPoint geopoint;

public MeuOverlay(Bitmap img ) {
imagem = img;
}

public void setGeopoint(GeoPoint geopoint) {
this.geopoint = geopoint;
}

@Override
public void draw(Canvas canvas, MapView mapView,
boolean shadow) {
super.draw(canvas, mapView, shadow);

if (geopoint != null){
Point pontoNaTela = mapView.getProjection()
.toPixels(geopoint, null);

canvas.drawBitmap(imagem,
pontoNaTela.x - (imagem.getWidth() / 2),
pontoNaTela.y - (imagem.getHeight()), null);
}
}
}

Essa classe tem dois atributos: um Bitmap e um GeoPoint. O Bitmap será a imagem que desenharemos para representar a o ponto no mapa; já o GeoPoint, como o próprio nome diz, é um ponto geográfico, que obteremos a partir da posição do GPS.
No método draw() é desenhado o ponto no mapa. Inicialmente checamos se o GeoPoint foi setado, em caso positivo, convertemos essa coordenada geográfica em um ponto na tela, que é representado pela classe Point. Quem faz essa conversão é a classe Projection obtida através do método getProjection() do MapView recebido como parâmetro do método. Depois, é só desenhar a imagem na tela. O cálculo feito na chamada do método é para que a imagem fique imediatamente acima do ponto geográfico.

O arquivo de layout
O arquivo de layout não tem nada de mais, apenas a declaração da MapView com a API key (veja no post de 2009 como obter a sua API key).
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Exemplo GPS e MapActivity" />

<com.google.android.maps.MapView
android:id="@+id/mapa"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"
android:apiKey="SUA_API_KEY" />
</LinearLayout>

A Activity
O código abaixo representa a Activity da aplicação e está todo comentado e só vou fazer alguns comentários no final. A nossa classe herda de MapActivity e implementa a interface LocationListener, que é utilizada para receber as notificações do GPS.
public class Aula09Activity extends MapActivity
// Interface que tem os métodos para tratar
// os eventos do GPS
implements LocationListener {

private MapView mapa;
private MapController controller;
private MeuOverlay overlay;
private LocationManager locationManager;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// O LocationManager vai registrar e desregistrar
// a classe para ouvir eventos do GPS
locationManager = (LocationManager)
getSystemService(LOCATION_SERVICE);

// Configurando a MapView
mapa = (MapView)findViewById(R.id.mapa);
// Habilita os botões de Zoom
mapa.setBuiltInZoomControls(true);
// Mostra em modo satélite
mapa.setSatellite(true);

// O MapController 'controla' o mapa :)
controller = mapa.getController();
controller.setZoom(17);

// Cria a imagem que vai representar o Overlay
Bitmap marcador = BitmapFactory.decodeResource(
getResources(), R.drawable.ponto);

// Cria o Overlay e adiciona ao mapView
overlay = new MeuOverlay(marcador);
mapa.getOverlays().add(overlay);

// Determinando um ponto inicial
int latitude = (int)(-8.058698 * 1E6);
int longitude = (int)(-34.872129 * 1E6);

GeoPoint geopoint = new GeoPoint(latitude, longitude);
controller.setCenter(geopoint);
overlay.setGeopoint(geopoint);
}

@Override
protected void onResume() {
super.onResume();
// Registrando a Activity para receber notificações
// de mudança na posição GPS
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 0, 0, this);

locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, 0, 0, this);
}

@Override
protected void onPause() {
super.onPause();
// Desregistrando a Activity para receber
// notificações de mudança na posição GPS
locationManager.removeUpdates(this);
}

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

@Override
public void onLocationChanged(Location location) {
// Método chamado quando a posição GPS muda
int latitude = (int)(location.getLatitude() * 1E6);
int longitude = (int)(location.getLongitude() * 1E6);

GeoPoint geopoint = new GeoPoint(latitude, longitude);
overlay.setGeopoint(geopoint);
controller.animateTo(geopoint);
}

@Override
public void onProviderDisabled(String provider) {
// Método chamado quando o GPS é desabilitado
}

@Override
public void onProviderEnabled(String provider) {
// Método chamado quando o GPS é habilitado
}

@Override
public void onStatusChanged(String provider,
int status, Bundle extras) {
// Método chamado quando o status do GPS muda.
// Pode ser: OUT_OF_SERVICE,
// TEMPORARILY_UNAVAILABLE e AVAILABLE
}
}

No código acima fazemos algumas inicializações no método onCreate. A classe LocationManager é responsável por registrar uma classe para "ouvir" os eventos de GPS (definidos na interface LocationListener que nossa Activity está implementando). Para criar um GeoPoint devemos passar a latitude e longitude em microdegrees, para tal devemos multiplicar os valores por 1.000.000 ou em notação científica 1E6 (10 elevado a 6).
No método onResume registramos nossa classe para ouvir os eventos do GPS. O método requestLocationUpdates recebe como parâmetros:
- o provedor de informações de posição GPS: aqui podemos utilizar o GPS do telefone, que demora mais a obter os dados, porém eles são mais precisos; e o da rede de dados, que obtém a posição baseado no esquema de triangulação de antenas da operadora de telefonia;
- a distância mínima em metros na posição GPS;
- o intervalo de tempo em milisegundos para receber a atualização;
- o objeto de uma classe que implemente a interface LocationListener (no nosso caso, a própria Activity).
No método onPause, desregistramos nossa classe para não ouvir mais os eventos de GPS. Isso evitará que ao sair da aplicação o Android tente ficar enviando as coordenadas mesmo com a aplicação inativa.
O método onLocationChanged é o mais importante, pois a cada vez que a posição GPS muda, esse método é chamado pelo Android. A partir do objeto Location que vem como parâmetro, obtemos a latitude e longitude para atualizarmos a posição do Overlay.
AndroidManifest.xml
No manifest, adicionamos as permissões INTERNET e ACCESS_FINE_LOCATION para podermos utilizar o Google Maps e obter a posição GPS respectivamente. Outra informação relevante é a tag <uses-library> que adicionamos para usar a API do Google Maps, uma vez que ela não é "padrão" da plataforma Android.
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="br.edu.cesar.aula09"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk android:minSdkVersion="10" />

<uses-permission
android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION" />

<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".Aula09Activity" >
<intent-filter >
<action
android:name="android.intent.action.MAIN" />
<category
android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

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

Para testar o código da aplicação, você deve habilitar as opções de GPS no dispositivo através da opção Settings > Location & Security > My Location. Notem que temos duas opções nessa seção: GPS satélites e redes móveis. A primeira é mais precisa, porém demora mais; já a segunda obtém a posição baseada triangulação entre as distâncias das redes de telefonia ou wireless.

Mas se você estiver usando o emulador, use a aba "Emulator control" da perspectiva DDMS do Eclipse (para exibir, acesse Window > Open perspective > DDMS).

Digite uma coordena válida e clique em "Send". Para ober uma posição, vá no Google Maps, clique com o botão direito e selecione "O que há aqui? | What's here?". Na barra de busca, ficará a latitude e a longitude, aí é só copiar e testar.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

sexta-feira, 2 de setembro de 2011

Android: Dicas 4

Olá povo,

Segue mais um POST da série "dicas de Android".

Dica 1 - Obter o espaço disponível no aparelho

File path = Environment.getDataDirectory();
StatFs stat = new StatFs(path.getPath());
long blockSize = stat.getBlockSize();
long availableBlocks = stat.getAvailableBlocks();
long ocupedBlocks =
stat.getBlockCount() - availableBlocks;

String s1 = Formatter.formatFileSize(
this, availableBlocks * blockSize);
String s2 = Formatter.formatFileSize(
this, ocupedBlocks * blockSize);
System.out.println("---------------");
System.out.println("s = "+ s1);
System.out.println("s = "+ s2);

Dica 2 - Teclado Virtual Sobre o Layout
Ao exibir o teclado virtual, o Android automaticamente redimensiona a tela do aparelho para exibir todos os elementos. Então se você tiver, por exemplo uma barra com botões na parte inferior, eles irão aparecer imediatamente em cima do teclado virtual. Para evitar isso, basta colocar a seguinte configuração na declaração da sua Activity no AndroidManifest.xml.

<activity
android:name=".ui.MainActivity"
android:windowSoftInputMode="adjustPan"
/>

Dica 3 - Evitando abrir a mesma atividade 2 vezes
Os engenheiros de teste com frequência tentam "destruir" a aplicação de algum jeito :) E um bem comum é clicar rapidamente em um botão. Se esse botão chamar uma Activity serão abertas duas em sequência. Para evitar isso, basta colocar a seguinte configuração na declaração da sua Activity no AndroidManifest.xml.


<activity
android:name=".ui.MainActivity"
android:launchMode="singleTop"
/>

Isso faz com que só seja possível apenas uma instância no topo da pilha de atividades.

Dica 4 - Alterando a linha abaixo da TabWidget
Essa dica é um complemento da dica sobre como mudar a imagem das abas da TabWidget. O código abaixo mostra como mudar a linha que fica na parte inferior da TabWidget. Ela tem um aspecto interessante, pois a API só disponibilizou um método "digno" pra isso a partir do Android 2.2, para as versões anteriores, essa operação tem que ser feita via Reflection.


// Esse código deve ser chamado dentro de
// uma TabActivity
TabWidget tabWidget = getTabWidget();

if (android.os.Build.VERSION.SDK_INT >=
android.os.Build.VERSION_CODES.FROYO){
// For Android 2.2
tabWidget.setLeftStripDrawable(
R.drawable.barra_menu);
tabWidget.setRightStripDrawable(
R.drawable.barra_menu);
} else {
// For Android 2.1 or before
try {
Field stripLeft = tabWidget.getClass().
getDeclaredField("mBottomLeftStrip");
Field stripRight = tabWidget.getClass().
getDeclaredField("mBottomRightStrip");

if (!stripLeft.isAccessible())
stripLeft.setAccessible(true);
if (!stripRight.isAccessible())
stripRight.setAccessible(true);

stripLeft.set(tabWidget,
getResources().getDrawable(
R.drawable.barra_menu));

stripRight.set(tabWidget,
getResources().getDrawable(
R.drawable.barra_menu));

} catch (Exception e) {
e.printStackTrace();
}
}

Nesse exemplo, estou usando a API level 8 (Android 2.2) no Eclipse para poder ter acesso aos métodos setLeftStripDrawable e setRightStripDrawable. Porém ele pode ser executado em aparelhos 2.1, que neste caso executará o código via Reflection. Para dar suporte a API 2.1, mesmo desenvolvendo com a 2.2 basta colocar a tag abaixo no AndroidManifest.xml.

<uses-sdk android:minSdkVersion="7" />

Dica 5 - Salvar estado do scroll da ListView
Na aplicação que estou desenvolvendo, sempre estava reatribuindo o Adapter à ListView no onResume da Activity. O problema era que eu clicava em um item da lista lá do final pra edita-lo era aberta a tela pra edição. Quando fechava essa tela, aí abria a tela de detalhes, mas quando voltava pra tela de listagem queria manter a posição do scroll.


// savedIndex e savedY é um atributo int

// Salvar estado
// Esse código coloquei no onPause
savedIndex = getListView().getFirstVisiblePosition();
View v1 = getListView().getChildAt(0);
savedY = (v1 == null) ? 0 : v1.getTop();

// Restaurando estado
// Esse código eu coloquei no onResume
setListAdapter(novoAdapter);
getListView().setSelectionFromTop(savedIndex, savedY);

Dica 6 - Checar se o GPS está habilitado
Existe basicamente duas formas de obter a posição geográfica no Android: por GPS e pela Rede de Dados. Essa configuração fica no menu Configurações > Local > Meu Local. Se sua aplicação usa geolocalização, você deve checar se uma dessas opções está habilitada.

public boolean isEnabled() {
LocationManager lm = (LocationManager)
context.getSystemService(
Context.LOCATION_SERVICE);

return
locationManager.isProviderEnabled(
LocationManager.GPS_PROVIDER) ||
locationManager.isProviderEnabled(
LocationManager.NETWORK_PROVIDER);
}
Dica 7 - Problema no GoogleMaps
Essa dica foi mandada pelo meu aluno da LinuxFI, Sávio (Sávio saviojp@gmail.com).
Durante a aula de Mapas eu passei pelo seguinte problema: Mesmo com todas as configurações da API Key e permissões definidas de modo correto, o meu emulador não carregava o mapa. Para solucionar esse problema fiz o seguinte:

Customizei a execução da aplicação no eclipse por meio do Run Configurations/Android Application /na aba Target no campo Additional Emulator Command Line Options, setei o comando: -dns-server 8.8.8.8.

4br4ç05,
nglauber