Aprenda LBS em Android na Prática - Parte 1

Page 1

Aprenda LBS em Android na Prática Parte 1 • Introdução Sistemas Baseados em Localização (LBS – Location Based System) estão cada vez mais comum, não só em aparelhos móveis e no ramo automotivo, a esfera de utilização destes serviços também aumenta gradativamente e, ao que tudo indica, essa curva de crescimento ainda tem muito fôlego. Segundo relatório realizado recentemente pela JiWire´s, os usuários estão achando o fato de informarem seus dados para seus amigos cada vez mais comum, também, estas mesmas pessoas estão mais propícias a receberem anúncios, cupons promocionais e outros, de estabelecimentos que estejam próximos a sua localização. Esta mesma pesquisa indica que mapas é o serviço LBS com maior aceitação. E não são somente estes números que indicam isso. É visível a crescente demanda de aparelhos com GPS. Todas as principais plataformas líderes de mercado têm uma alta sensibilidade a aplicativos LBS. E não é só isso, a liderança isolada do GPS (Global Position System) pode estar ameaçada, isso porque existem outras tecnologias chegando para competir neste nicho de mercado. E, competição, na maioria dos casos, gera melhorias nos sistemas estagnados. A importância do conhecimento de programação deste tipo de aplicação parece estar bem clara para os programadores, analistas de sistemas e gerentes de projetos. Soma-se a isso o crescimento contínuo de outra plataforma, o Android, e têm-se uma demanda de mercado: conhecimento em criação de LBS´s para smartphones com o sistema operacional do Google. Este artigo visa sanar justamente esta brecha. Para isso, vamos seguir o modelo do artigo “Acessando aplicativos nativos de um Smartphone com a BlackBerry API”, publicado na revista WebMobile, edição de número 30. Neste artigo criamos uma aplicação que mostrava os contatos do device, permitindo que a visualização de um mapa 2D da localização de


cada um deles. Na próxima versão deste artigo, vamos dar um passo além, permitir também à visualização da rota de onde o usuário está até a localização do seu contato. Bem, vamos a codificação.

• Criar a tela de contatos O primeiro passo para esta aplicação é criar a interface gráfica da página principal, que irá mostrar todos os contatos do dispositivo que informem o endereço. Logo, nosso primeiro objetivo, é codificar a tela da Figura 1:

Figura 1: Primeira tela da aplicação.

Depois de criar o projeto “WhereIsMyContact”, o primeiro passo é configurar as permissões da nossa aplicação. Isso porque estaremos lendo os contatos do dispositivo. Para isso, abra a edição do AndroidManifest.xml e inclua a linha sublinhada: ... </application> <uses-permission android:name="android.permission.READ_CONTACTS" /> </manifest>

Obs: Se o leitor é iniciante no Android, por favor, leia os outros artigos sobre programação Android em http://issuu.com/ricardoogliari. Agora, vamos editar o arquivo main.xml para definir nossa interface gráfica. Veja a Listagem 1:


Listagem 1: 01: <?xml version="1.0" encoding="utf-8"?> 02: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" 03 android:layout_width="fill_parent" 04: android:layout_height="fill_parent" > 05: <TextView 06: android:layout_width="fill_parent" 07: android:layout_height="wrap_content" 08: android:text="Escolha um dos contatos (com endereço):" /> 09: <Spinner android:layout_width="fill_parent" 10: android:layout_height="wrap_content" 11: android:id="@+id/spNomes" /> 12: <Button android:layout_width="80px" 13: android:layout_height="wrap_content" 14: android:id="@+id/btnGo" 15: android:text="Ver" 16: android:layout_gravity="right" 17: android:layout_marginTop="15px" /> 18: </LinearLayout>

Utilizamos o gerenciador de layout LinearLayout, para mostrar os componentes em uma seqüência linear e vertical (Linha 2). Nas linhas 3 e 4 definimos que ela ocupará toda a tela horizontalmente e verticalmente. Na linha 5 inserimos uma View de texto, do tipo TextView. Na linha 6 informamos que o componente irá ocupar toda a largura da tela, na linha subseqüente definimos sua altura como igual a do seu conteúdo. Por fim, definimos seu texto na linha 8, dizendo para o usuário escolher um dos contatos mostrados na listagem. Sendo que, só serão apresentados os nomes que contenham endereço cadastrado. O Spinner é criado na linha 9. Definimos sua largura como o tamanho da tela (linha 10). A altura igual ao tamanho do componente (linha 11). E também precisamos informar seu id (linha 12) . O Button é criado na linha 12. Definimos sua largura como 80 pixeis. A altura é igual ao tamanho vertical do componente (linha 13). Definimos seu id na linha 14. O texto Ver é informado na linha 15. Na linha 16 alinhamos o componente a direita da tela e, finalmente, na linha 17 definimos sua margem superior como 15 pixeis. E como ficaria a nossa Activity. Veja a Listagem 2: Listagem 2: 01: public class WhereIsMyContact extends Activity { 02: private Spinner sp; 03: 04: public void onCreate(Bundle savedInstanceState) {


05; 06: 07: 08: 09:

super.onCreate(savedInstanceState); setContentView(R.layout.main); } }

sp = (Spinner) findViewById(R.id.spNomes);

A Listagem acima contém apenas o bê-a-bá do Android. Apesar de recuperarmos o Spinner na linha 8, ainda não adicionamos os nomes dos contatos na sua lista.

• Buscando as informações dos contatos Nossa classe terá um método muito importante, o LerContatos: private ArrayAdapter<String> lerContatos(){

Veja o conteúdo do método na Listagem 3: Listagem 3: 01:ContentResolver cr = getContentResolver(); 02:Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null); 03: 04:if (cur.getCount() > 0) { 05: Vector contatos = new Vector(); 06: Vector enderecos = new Vector(); 07: 08: while (cur.moveToNext()) { 09: String id = cur.getString(cur.getColumnIndex( 10: ContactsContract.Contacts._ID)); 11: String name = cur.getString(cur.getColumnIndex( ContactsContract.Contacts.DISPLAY_NA ME)); 12: 15: String addrWhere = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?"; 16: String[] addrWhereParams = new String[] { id, ContactsContract.CommonDataKinds. StructuredPostal.CONTENT_ITEM_TYPE }; 17: 18: Cursor addrCur = cr.query( ContactsContract.Data.CONTENT_URI, null, addrWhere, addrWhereParams, null); 19: 20: if (addrCur.moveToNext()) { 21: String street = addrCur.getString(addrCur .getColumnIndex( ContactsContract.CommonDataKinds. StructuredPostal.STREET));


22: 23:

24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34:

String city = addrCur.getString(addrCur . getColumnIndex(ContactsContract.CommonDataKinds.StructuredP ostal.CITY));

}

if (street != null && city != null){ enderecos.addElement(street+", "+city); contatos.addElement(name); }

addrCur.close(); } cur.close(); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, contatos);

35: 36: return adapter; 37:} else { 38: return null; 39:}

OBS: Sua explicação é longa e ficar lendo o texto e voltando até aqui para ver o código fonte vai atrapalhar muito. Então, deixe a IDE aberta e alterne entre a janela do artigo e a da IDE. Na linha 1 usamos o método getContentResolver para criar a instância de ContentResolver. Esta classe possui um método query, usado na linha 2, que serve como uma consulta aos dados de conteúdo do aparelho. Ele realmente se assemelha muito as query´s do SQL. O Android possui algumas tabelas de dados que podem ser consultados através do ContentResolver. As informações de contatos estão em ContactsContract.Contacts.CONTENT_URI . Nas versões anteriores ao Android 2.0, a URI é outra. O método query ainda possui outras features, como a possibilidade de buscar os dados com uma ordenação, porém, isso não será necessário aqui, mas é bom saber disso. Os dados retornados da consulta são uma instância de Cursor. Seu nome já diz tudo, é uma forma de navegar pelos resultados que a consulta retornou do banco de dados. Na linha 4 verificamos se o Cursou possui mais de um item, caso positivo, entramos em uma extensa lógica de código, caso contrário, retornamos null para o método e encerramos a busca (linha 38). Nas linhas 5 e 6 criamos os vetores com os nomes e endereços dos contatos, respectivamente.


Na linha 8 utilizamos o método moveToNext() do Cursor para navegar entre seus elementos. Nas linha 9 e 10 buscamos o id e o nome de cada contato. O id será importante posteriormente. Note, os dados básicos do contato, estão em ContactsContract.Data. O leitor pode acessar o site http://developer.android.com/reference/android/provider/ContactsContract. Data.html para saber sobre as outras constantes que estão disponíveis. Apenas para matar a curiosidade, veja algumas delas na Tabela 1: Tabela 1:

Na linha 18 vamos criar um novo cursor para buscar as outras informações necessárias do contato. Perceba que não estamos mais passando somente null para o método query. Na verdade estamos passando uma cláusula (addrWhere), que foi configurada na linha 16, e um conjunto de parâmetros (addrWhereParams), que foi criado na linha 17. A linhas 16 e 17 merecem uma atenção especial: String addrWhere = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?"; String[] addrWhereParams = new String[] { id, ContactsContract.CommonDataKinds.StructuredPostal. CONTENT_ITEM_TYPE };

Perceba que estamos limitando a pesquisa para os dados referentes somente ao contato que estamos trabalhando no momento, referenciado pelo seu id, e, para dados do tipo StructuredPostal. Existem alguns outros


filtros de informações que podem ser usados, não aqui, mas em outros casos: • • • • • • • • • • •

ContactsContract.CommonDataKinds.Email; ContactsContract.CommonDataKinds.Event; ContactsContract.CommonDataKinds.GroupMembership; ContactsContract.CommonDataKinds.Im; ContactsContract.CommonDataKinds.Nickname ; ContactsContract.CommonDataKinds.Note; ContactsContract.CommonDataKinds.Organization; ContactsContract.CommonDataKinds.Phone; ContactsContract.CommonDataKinds.Photo; ContactsContract.CommonDataKinds.Relation; ContactsContract.CommonDataKinds.StructuredName. Na linha 20 verificamos se foi achado algum dado na busca anterior.

Nas linhas 21 e 23 buscamos a rua e a cidade do contato. Na linha 24 testamos se o contato possui uma rua e uma cidade cadastrados, caso afirmativo, adicionamos seu nome e ser endereço nos seus respectivos vetores. Nas linhas 30 e 33 fechamos os cursores abertos. E, finalmente, na linha 34 criamos o adapter e na linha 36 retornamos a instância deste objeto. Para testar se nossa lógica está certa abra o emulador, em seguida cadastre quatro contatos, dois com endereço e dois sem. Veja a Figura 2:


Figura 2: Contatos no dispositivo.

Agora execute nosso aplicativo. Ao clicar no Spinner o usuário deve ver apenas duas opções. Veja a Figura 3:

Figura 3: Contatos no aplicativo.

• Buscando a posição geográfica do contato O leitor já deve ter percebido que cada contato tem seu endereço, mas precisamos de sua posição geográfica, ou seja, sua latitude e longitude.


Para mudar uma string “rua paraíso, 100, são paulo” para sua exata latitude e longitude, vamos usar um serviço do Google, onde passamos o endereço e a URL nos retorna os possíveis endereços com suas posições no formato JSON. OBS: JSON (com a pronuncia djeisón), um acrônimo para "JavaScript Object Notation", é um formato leve para intercâmbio de dados computacionais. JSON é um subconjunto da notação de objeto de JavaScript, mas seu uso não requer Javascript exclusivamente. Por exemplo: buscando a rua Bonifácio de Andrada, 147, São Paulo: http://maps.google.com/maps/api/geocode/json?address=Rua+Bonifacio+de+Andrada %2c+147%2c+sao+paulo&sensor=false&language=ptBR&key=ABQIAAAAPGO5qsT70FiyK7ypHmC_1xRKBN0VqitlrjMQ2o6oRJmuRxLx0hQhWhzC3uH HOIbfqO7s7NW2gq7BWA

Recebemos as seguintes informações, mostradas na Figura 4:

Figura 4: Retorno do serviço.

O leitor já deve ter percebido que teremos que fazer uma conexão http para este serviço do Google. Antes de continuar deixe-me explicar. O foco deste artigo não é conectividade, sendo assim, somente vou listar os códigos usados aqui. Vamos criar uma classe que será de vital importância, a HttpClient. Esta, por sua vez, fará toda a conexão com o serviço e o parser do JSON. Veja a Listagem 4, mas não se assuste:


Listagem 4: public class HttpClient{ public static Vector<Hashtable> enderecos; private static void parseGoogleMapsData(String rawData) throws IOException { enderecos = new Vector<Hashtable>(); if (rawData == null) { throw new IOException("Nenhum resultado"); } try { rawData = new String(rawData.getBytes(), "utf-8"); JSONObject obj = new JSONObject(rawData); String status = obj.getString("status"); if (!"OK".equals(status) || "ZERO_RESULTS".equals(status)){ return; } JSONArray results = obj.getJSONArray("results"); for (int i = 0; i < results.length(); i++) { JSONObject localObject = results.getJSONObject(i); 01: String>();

Hashtable<String, String> endereco = new Hashtable<String,

02: endereco.put("endereco", localObject.getString("formatted_address")); 03: 04: JSONObject geometry = localObject.getJSONObject("geometry"); 05: JSONObject location = geometry.getJSONObject("location"); 06: endereco.put("lat", ""+location.getDouble("lat")); 07: endereco.put("lng", ""+location.getDouble("lng")); 08: 09: enderecos.add(endereco); }

}

} catch (JSONException e) { throw new IOException(e.getMessage()); }

public static void executeMethod(String urlString) throws IOException{ URL url = new URL(urlString); URLConnection conn = url.openConnection(); int response; StringBuffer sb = new StringBuffer(); try{ HttpURLConnection httpConn = (HttpURLConnection) conn; httpConn.setAllowUserInteraction(false); httpConn.setInstanceFollowRedirects(true); httpConn.setRequestMethod("GET"); httpConn.connect();


response = httpConn.getResponseCode(); InputStreamReader is=new InputStreamReader(conn.getInputStream()); int responseCode = httpConn.getResponseCode(); if (response == HttpURLConnection.HTTP_OK) { int xmlchar; while ((xmlchar = is.read()) != -1) { sb.append((char)xmlchar); } } parseGoogleMapsData(sb.toString()); } catch (Exception ex) { throw new IOException(ex.toString()); } }

}

Fique a vontade para estudar este código, porém, para agora, o importante é que tenham em mente estas linhas de código: 01: String>();

Hashtable<String, String> endereco = new Hashtable<String,

02: endereco.put("endereco", localObject.getString("formatted_address")); 03: 04: JSONObject geometry = localObject.getJSONObject("geometry"); 05: JSONObject location = geometry.getJSONObject("location"); 06: endereco.put("lat", ""+location.getDouble("lat")); 07: endereco.put("lng", ""+location.getDouble("lng")); 08: 09: enderecos.add(endereco);

Perceba que criamos um Vector enderecos. Este vai armazenar uma instância de Hashtable para cada endereço. O Hashtable, por sua vez, armazena a informações de endereço, latitude e longitude no formato de par chave-valor. Pronto, esse é o ponto chave, essencial. Voltando para a tela principal, precisamos implementar o listener que ouvirá as ações de clique no botão. Veja a Listagem 5: Listagem 5: Button botao = (Button) findViewById(R.id.btnGo); botao.setOnClickListener(new View.OnClickListener(){ public void onClick(View v) { buscarMapa(); }


});

No onClick do Button apenas chamamos o método BuscarMapa, que é mostrado na Listagem 6: Listagem 6: 01:public void buscarMapa(){ 02: try { 03: String localizacao = URLEncoder.encode( enderecos.elementAt(sp.getSelectedItemPosition()).toString(), "utf-8"); 04: 05: HttpClient.executeMethod("http://maps.google.com/maps/api/ geocode/json?address="+localizacao+"&sensor=false&language=ptBR&key=ABQIAAAAPGO5qsT70FiyK7ypHmC_1xRKBN0VqitlrjMQ2o6oRJmuRxLx0hQhWhz C3uHHOIbfqO7s7NW2gq7BWA"); 06: 07; if (HttpClient.enderecos.size() > 1){ 08: Intent intent = new Intent(this, Enderecos.class); 09: startActivity(intent); 10: } 11: } catch (IOException e) { 12: e.printStackTrace(); 13: } 14:}

Na 3 utlizamos a classe URLEncoder para transformar o endereço que está informado no contato para um formato aceitável para URL. Na linha 5 chamamos o método de HttpClient que efetivamente busca os dados, faz o parser das informações recebidas e, guarde o resultado no vetor com instâncias de Hashtable. Na linha 7 verificamos se foram encontradas mais de uma opção de endereço. Isso porque, o endereço que o contato forneceu pode ser muito ambíguo. Algo como: avenida paulista, São Paulo. Caso afirmativo, mostramos a tela de endereços. Veja a Figura 4:


Figura 4: Possíveis endereços do contato.

A parte de seleção de uma das opções e visualização do mapa 2D será o próximo passo. Bem, vamos ver o xml e a classe Activity desta nova tela. A Listagem 7 mostra o endereços.xml, responsável pela interface apresentada na Figura 4: Listagem 7: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ListView android:id="@+id/android:list" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout>

Um simples ListView. Agora veja a Listagem 8 com a ListActivity que utiliza o xml visto anteriormente: Listagem 8 public class Enderecos extends ListActivity{ String[] nomeRuas; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);


setContentView(R.layout.enderecos); 01: Vector enderecos = HttpClient.enderecos; 02: nomeRuas = new String[enderecos.size()]; 03; for (int i = 0; i < enderecos.size(); i++){ 04: Hashtable<String, String> endereco = (Hashtable<String, String>) enderecos.elementAt(i); 05: nomeRuas[i] = endereco.get("endereco"); 06: } 07: 08: setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, nomeRuas)); } }

O que é importante aqui são as linhas numeradas de 1 a 8. A linha número 1 recupera o Vector de endereços da classe HttpClient. A linha dois instancia o vetor de String que contém somente o nome dos logradouros. O laço da linha 3 recupera o endereço de cada Hashtable, na linha 4 e 5. Por fim, na linha 8, associamos o adapter ao ListView.

• Mostrar Mapa O próximo passo é mostrar um mapa 2D ilustrando a localização de um dos contatos. O primeiro passo é criar uma nova tela que será chamada pelo nosso aplicativo. Primeiramente vamos estudar o arquivo localização.xml mostrado na Listagem 9 Listagem 9: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <com.google.android.maps.MapView android:id="@+id/mapView" android:layout_width="fill_parent" android:layout_height="fill_parent" android:enabled="true" android:clickable="true" android:apiKey="xxxxxxxxxxxx" </RelativeLayout>


Como gerenciador de layout usamos a classe RelativeLayout. Posteriormente, veremos que ele será muito útil. Usamos um único View, a classe MapView. Este componente apresenta um mapa na tela. Suas propriedades são bem conhecidas do leitor. Apenas atente para o valor true em enabled e clickable, com isso, o mapa pode ser clicado pelo usuário. Muito importante é a propriedade apiKey, esta chave pode ser encontrada no site do google e é ela que permite a utilização do mapa 2D do Google. Depois de visto o layout xml, vamos estudar a classe Localizacao que utiliza este arquivo. Veja a Listagem 10: Listagem 10: 1:public class Localizacao extends MapActivity 2:{ 3: MapView mapView; 4: MapController mc; 5: GeoPoint p; 6: 7: public void onCreate(Bundle savedInstanceState) { 8: super.onCreate(savedInstanceState); 9: setContentView(R.layout.localizacao); 10: 11: mapView = (MapView) findViewById(R.id.mapView); 12: 13: mc = mapView.getController(); 14: 15: String coordinates[] = {ConstLocalizacao.latitude, ConstLocalizacao.longitude}; 16: 17: double lat = Double.parseDouble(coordinates[0]); 18: double lng = Double.parseDouble(coordinates[1]); 19: 20: p = new GeoPoint((int) (lat * 1E6), (int) (lng * 1E6)); 21: 22: mc.animateTo(p); 23: mc.setZoom(15); 24: 25: mapView.invalidate(); 26:} 27:@Override 28:protected boolean isRouteDisplayed() { 29: return false; 30:} 31:}

Na linha 1 já percebemos uma grande mudança. Não utilizamos Activity, mas sim a classe MapActivity. Na linha 7 sobrescrevemos o método onCreate, as linhas 8 e 9 também são muito conhecidas em qualquer aplicativo Android. Na linha 11 recuperamos o objeto MapView definido no xml. Na linha 13 usamos o


método getController() para instanciar um objeto MapController. Esta classes, por sua vez, controla aspectos do mapa, como zoom por exemplo. Na linha 15 criamos um vetor de Strings com as constantes da classe ConstLocalizacao. Veja esta classe na Listagem 11. Nas linhas 17 e 18 transformamos os valores de String para double. Na linha 20 criamos uma instância de GeoPoint, usando os dois valores em ponto flutuante citados anteriormente. Esta classe é imutável e representa um par de latitude e longitude. Lembram do MapController? Com ele podemos controlar o ponto central do mapa, para isso usamos seu método animeTo(GeoPoint p) (linha 22). Também podemos chamar o método setZoom paa definir a profundidade do mapa 2D. Finalmente, na linha 25, forçamos uma atualização da View tornando ela inválida. Listagem 11: public class ConstLocalizacao { public static String latitude; public static String longitude; }

Volte na Listagem 6, perceba que só mostramos os possíveis endereços (Figura 4) se o serviço do Google retornar mais de um endereço, caso contrário já possuímos a localização geográfica (latitude e longitude). Vamos começar deste ponto. Ao o executar o exemplo, selecionei o contato Sheila Lima, que possui um endereço completo, sendo assim, o serviço do Google já traz somente uma opção. Novamente, volte a Listagem 6, nas linhas 7 até a 10 temos somente o teste lógico if. Agora nosso código ficou assim: if (HttpClient.enderecos.size() > 1){ Intent intent = new Intent(this, Enderecos.class); startActivity(intent); } else { 1: Hashtable hash = (Hashtable) HttpClient.enderecos.elementAt(0); 2: 3: ConstLocalizacao.latitude = hash.get("lat").toString(); 4: ConstLocalizacao.longitude = hash.get("lng").toString(); 5: 6: startActivity(new Intent(this, Localizacao.class)); }


Como teremos só um endereço, podemos capturar o Hashtable que se encontra no índice 0 (linha 1). Nas linhas 3 e 4 configuramos as constantes da classe ConstLocalizacao. Por fim, a linha 6 cria a inicia a Intent da classe Localizacao. Na aplicação, verei a tela da Figura 5 para o contato Sheila Lima:

Figura 5: Localização do contato Sheila Lima.

E no segundo caso? Onde o contato não possui um endereço muito claro e a tela de possíveis endereços é mostrada (Figura 4). Na classe Enderecos vamos adicionar o método que responde as ações de clique: public void onListItemClick(ListView parent, View v, int position, long id) { Hashtable hash = (Hashtable) HttpClient.enderecos.elementAt(position); ConstLocalizacao.latitude = hash.get("lat").toString(); ConstLocalizacao.longitude = hash.get("lng").toString(); }

startActivity(new Intent(this, Localizacao.class));


A única diferença é na primeira linha do método. Anteriormente acessávamos o Hashtable diretamente do índice 0 (zero), agora, teremos que buscar o elemento referente a posição que o usuário escolheu na lista. Vamos pegar agora o caso do segundo contato, o ricardo ogliari, ao clicarmos no botão Ver a tela de possíveis endereços é mostrada (Figura 4). Ao selecionar o primeiro item da lista, o endereço Avenida Paulista, 900 – Bela Vista, São Paulo, 01310-100, Brasil, veremos a imagem da Figura 6:

Figura 6: Localização do contato ricardo ogliari.

• Melhorando o Mapa Bem, digamos que atingimos nosso objetivo de ver onde nosso contato está. Porém, se o usuário quiser dar zoom in e zoom ou neste mapa? Ou ainda, poderíamos colocar um ícone que identifica o local exato do endereço de nossos amigos, o mapa atual abrange um bairro inteiro. Sendo assim, vamos adicionar algumas features na classe Localizacao. Primeiramente, altere o localização.xml, adicione uma ViewGroup no RelativeLayout. Veja a Listagem 12:


Listagem 12: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <com.google.android.maps.MapView ... /> <LinearLayout android:id="@+id/zoom" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" /> </RelativeLayout>

O LinearLayout adicionado nessa versão, será o container dos controles de zoom. Perceba que tanto sua altura quanto sua largura será referente aos seus próprios limites. Como utilizamos o RelativeLayout como gerenciador de layout, colocamos este último componente abaixo do anterior (alignParentBottom), além de adicionar ele ao centro (centerHorizontal). Agora as alterações serão na classe Localizacao. Veja na Listagem 13 as linhas de código que foram adicinadas: Listagem 13: mapView = (MapView) findViewById(R.id.mapView); 1: LinearLayout zoomLayout = (LinearLayout)findViewById(R.id.zoom); 2: View zoomView = mapView.getZoomControls(); 3: 4: zoomLayout.addView(zoomView, new LinearLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 5: mapView.displayZoomControls(true); 6: mc = mapView.getController();

As linhas não numeradas já existiam na versão anterior do código fonte. Na primeira linha recuperamos o objeto LinearLayout. Na linha 2 usamos o método getZoomControls() da classe MapView. Este método retorna um Widget que controla o nível de zoom do mapa. A instância criada é adiciona ao LinearLayout, na linha 4. Na linha 5 definimos que ao mostrar o mapa, este widget será visualizada por alguns segundos, depois disso, o mesmo pode retornar quando o mapa é clicado. Agora, veja as figuras 7 e 8:


Figura 7: Mapa com o MapController.

Figura 8: Mapa com ZoomIn.

Na Figura 7 eu cliquei no mapa e o MapController foi ativado. Então, apliquei o zoom in algumas vezes e cheguei na Figura 8. O MapView também possui dois métodos muito úteis, o setSatellite(boolean b) e o setStreetView(boolean b). Como o leitor deve imaginar, com ele, podemos mudar o modo de visualização do mapa. Vamos fazer isso agora. Na classe Localização, adicione o código da Listagem 14: Listagem 14: 1: private void createMenu(android.view.Menu menu) { 2: MenuItem mnu1 = menu.add(0, 0, 0, "Modo Satélite"); 3: { 4: mnu1.setAlphabeticShortcut('s'); 5: } 6: MenuItem mnu2 = menu.add(0, 1, 1, "Modo Rua"); 7: { 8: mnu2.setAlphabeticShortcut('r'); 9: } 10: } 11: 12: private boolean MenuChoice(MenuItem item) { 13: switch (item.getItemId()) { 14: case 0: 15: mapView.setSatellite(true); 16: mapView.setStreetView(false); 17: return true;


18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36:

}

case 1: mapView.setSatellite(false); mapView.setStreetView(true); return true; } return false;

@Override public boolean onCreateOptionsMenu(android.view.Menu menu) { super.onCreateOptionsMenu(menu); createMenu(menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { return MenuChoice(item); }

Vamos sobrescrever o método onCreateOptionMenu (linha 27) para criar um comportamento otimizado em resposta a ação da chamada ao menu principal. Na linha 28 chamamos o método da própria classe Activity, logo após, chamamos o método createMenu(), que tem seu corpo definido na linha 1. Neste método adicionamos dois novos MenuItem. Um deles para modo satélite e outro para modo rua. Como nosso objetivo aqui não é aprender interfaces gráficas, não vamos entrar em detalhes da criação de menus em Android. Também sobrescrevemos o método onOptionsItemSeleceted para responder as interações nos itens de menu. Na linha 35 repassamos esta tarefa para o método criado na linha 12. Caso o id do item de menu for igual a 0 (teste da linha 14), significa que a opção selecionada foi modo satélite, sendo assim, habilitamos o modo satélite (linha 15) e desabilitamos o modo rua (linha 16). O teste realizado na linha 18 apenas aplica a lógica inversa. Vamos executar novamente o aplicativo. Veja as Figuras 9 e 10.


Figura 9: Mapa com o MapController.

Figura 10: Mapa com o MapController.

Na Figura 9 cliquei no botão Menu do aparelho, recebendo o menu. Optei pela opção Rua, visto que, por padrão, o mapa está no modo satélite. O resultado pode ser visto na Figura 10. Para deixarmos nosso mapa perfeito falta apenas uma questão. Ainda não colocamos um identificador que defina exatamente a localização do contato. Algo como a Figura 11:

Figura 11: Identificador de posicionamento.

Para isso vamos usar uma classe abstrata chamada Overlay, que é simplesmente um ponto que pode ser coloco no topo do mapa. Formando a aparência visual vista na Figura 11. Logicamente, o primeiro passo será criar uma classe que extenda de Overlay. Veja a Listagem 15:


Listagem 15: 1: class BolaOverlay extends Overlay{ 2: private int imageId; 3: private Paint paint = new Paint(); 4: private GeoPoint geoPoint; 5: private Point p; 6: 7: public BolaOverlay (GeoPoint geoPoint, int imageId){ 8: this.geoPoint = geoPoint; 9: this.imageId = imageId; 10: } 11: 12: @Override 13: public void draw(Canvas canvas, MapView mapView, boolean shadow) { 14: super.draw(canvas, mapView, shadow); 15: 16: if (geoPoint != null){ 17: p = mapView.getProjection().toPixels(geoPoint, null); 18: Bitmap bitmap = BitmapFactory.decodeResource( mapView.getResources(), this.imageId); 19: RectF r = new RectF(p.x-bitmap.getWidth()/2, p.ybitmap.getHeight(), p.x+bitmap.getWidth()/2, p.y); 20: canvas.drawBitmap(bitmap, null, r, paint); 21: } 22: } 23:}

O único método que deve ser sobrescrito de forma obrigatório é o draw. Nele, vamos desenhar oque queremos mostrar como overlay do mapa. No nosso caso apenas desenhamos uma imagem (linha 20) que é recebida por parâmetro (linha 9). Também por parâmetro, é recebido uma instância de GeoPoint (linha 10) para definir o local da imagem. A linha 19 também é importante, perceba que é feito um cálculo com o GeoPoint e o tamanho da tela e da imagem. Vamos a última alteração. No final do método onCreate da classe Localização colocamos o seguinte código: ... BolaOverlay bola = new BolaOverlay(p, R.drawable.ponto); mapView.getOverlays().add(bola); }

mapView.invalidate();

...

Primeiro cria-se a instância de BolaOverlay, passando como parâmetro a posição e a imagem. O ponto está na pasta drawable do projeto. Em seguida, através do método getOverlays(), recuperamos a lista


de Overlays do mapa. Para, finalmente, adicionar a instância de BolaOverlay a esta listagem. Agora a aplicação mostra o seguinte mapa para o contato ricardo ogliari e depois de alguns zoom in.

Figura 12: Identificador de posicionamento para o contato ricardo ogliari.

• Conclusão O uso de sistemas baseados em localização está se tornando quase uma commodity no mundo da tecnologia da informação. Isso se deve a muitos fatores, como por exemplo: o aumento do uso de smartphones, que apresentam na sua grande maioria um sistema de posicionamento global nativo, também, a cultura das pessoas vem mudando e o compartilhamento das suas posições geográficas já não é um impeditivo tecnológico e cultural. Seguindo na mesma linha de crescimento e importância, o sistema operacional vem ganhando espaço e já figura entre os três principais do mundo, porém, as previsões é que até final de 2010 fique atrás somente do Symbian.


Portanto, temos a união de LBS e Android, que foi tratada na primeira parte deste artigo, além de dar uma rápida olhada em como importar os contatos do smatphone para seu aplicativo. Aguardem a próxima parte, onde adicionaremos a rota entre o usuário e seu contato.

• Sobre Mim Meu nome é Ricardo da Silva Ogliari, sou graduado em Ciência da Computação pela Universidade de Passo Fundo. Atualmente também estou cursando uma pós-graduação em web, estratégias de inovação e tecnologia, no Senac SP. Trabalho com mobile a 6 anos, escrevo artigos para algumas revistas nacionais especializadas. Sou criador e mantenedor do http://www.mobilidadetudo.com e sou um dos membros do www.javamovel.com. Palestrei em eventos nacionais e internacionais, como o FISL e o JustJava, além de ter dezenas de artigos meus espalhados pelo mundão da internet.


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.