terça-feira, 30 de outubro de 2012

Gesto de Swipe no Android

Olá povo,

Recentemente precisei implementar a detecção do gesto de swipe em uma aplicação iOS que estava desenvolvendo. Achei super fácil e resolvi pesquisar como fazer o mesmo no Android. Eis que achei a solução abaixo. (Não me lembro o post original, quem souber, posta um comentário pls).
public class MainActivity extends Activity 
  implements OnClickListener, OnTouchListener {

  private GestureDetector gestureDetector;
  private TextView txtResultado;
    
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
        
    txtResultado = 
      (TextView)findViewById(R.id.txtResultado);
        
    gestureDetector = 
      new GestureDetector(this, new DetectorDeGesto());
    
    // Apesar da linha abaixo não fazer nada, 
    // não funcionou sem...
    findViewById(R.id.raizLayout).
      setOnClickListener(this);
    findViewById(R.id.raizLayout).
      setOnTouchListener(this);
  }

  @Override
  public void onClick(View v) {
    Log.d("NGVL", "onclick");
  }  
    
  @Override
  public boolean onTouch(View v, MotionEvent event) {
    return gestureDetector.onTouchEvent(event);
  } 

  class DetectorDeGesto 
    extends SimpleOnGestureListener {
     
    private static final int DISTANCIA_MINIMA  = 120;
    private static final int DISTANCIA_MAXIMA  = 250;
    private static final int VELOCIDADE_MINIMA = 200;
     
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, 
      float velocityX, float velocityY) {
      try {
        boolean excedeuDistanciaEmY =
          Math.abs(e1.getY() - e2.getY()) 
          > DISTANCIA_MAXIMA;
               
        if (excedeuDistanciaEmY) return false;
                
        boolean velocidadeValida =
          Math.abs(velocityX) > VELOCIDADE_MINIMA;
                
        boolean swipeLeft = 
          e1.getX() - e2.getX() > DISTANCIA_MINIMA;
        boolean swipeRight =
          e2.getX() - e1.getX() > DISTANCIA_MINIMA;
                  
        // right to left swipe
        if(swipeLeft && velocidadeValida) {
          txtResultado.setText("Left Swipe");

        // left to right swipe
        }  else if (swipeRight && velocidadeValida) {
          txtResultado.setText("Right Swipe");
        }
      } catch (Exception e) {
      }
      return false;
    }
  }  
}
A classe SimpleOnGestureListener é uma classe básica do Android que consegue detectar diversos gestos simples como scroll, double tap, entre outros. Nossa classe DetectorDeGesto, herda dessa classe e sobrescreve o método onFling, que é chamado quando o usuário está deslizando o dedo sobre a tela (esse evento é armazenado em e2) e guarda a referência de quando o usuário pressionou o dedo sobre a tela inicialmente (armazenado em e1). Nesse método verificamos a direção que foi feito o swipe, bem como a velocidade que o movimento foi feito. As distâncias mínima e máxima para considerarmos o gesto válido, assim como a velocidade mínima estão definidas nas constantes da classe.
Nossa Activity implementa as interfaces View.OnClickListener e OnTouchListener. No método onTouch, delegamos o tratamento do evento de touch para o nosso detector de Gesto. Para que isso aconteça, no onCreate, criamos um GestureDetector (que é outra classe do próprio Android) passando o nosso detector de gestos. Em seguida, pegamos a referência da view que queremos tratar o evento de touch e setamos o onTouchListener e o onClickListener.
O arquivo de layout usado no projeto é mostrado abaixo:
<RelativeLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/raizLayout"
  android:layout_width="match_parent"
  android:layout_height="match_parent" >

  <TextView
    android:id="@+id/txtResultado"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:textSize="40dp"
    tools:context=".MainActivity" />
</RelativeLayout>
Pronto! É só rodar a aplicação e testar SWIPE pra esquerda e direita.

Qualquer dúvida, é só deixar seus comentários.

4br4ç05,
nglauber

segunda-feira, 22 de outubro de 2012

Context Menu em Listas no Android

Olá povo,

Estava dando aula e precisei mostrar para os meus alunos um exemplo de um menu de contexto. Mas o que é um menu de contexto? É basicamente é uma lista de opções que é exibida quando o usuário dá um clique longo em um componente da tela. Isso é particularmente útil em uma lista, onde você pode realizar várias operações sobre um determinado item.
É esse exemplo que mostraremos aqui. Crie um novo projeto, e em seguida crie o arquivo menu_contexto.xml na pasta menu (essa pasta não existe, você deve criá-la).
<menu 
  xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:id="@+id/opcao1" 
    android:title="Opção 1"/>
  <item android:id="@+id/opcao2" 
    android:title="Opção 2"/>
  <item android:id="@+id/opcao3" 
    android:title="Opção 3"/>
</menu>
Esse arquivo contém as opções de menu que aparecerão no menu de contexto. Carregaremos essas opções no método onCreateContextMenu na nossa Activity.
public class MainActivity extends ListActivity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
        
    String[] nomes = {
      "Fulano", "Cicrano", "Beltrano", "João",
      "Maria", "José", "Antônio", "Francisco" };
        
    ArrayAdapter<String> adapter = 
      new ArrayAdapter<String>(this,
        android.R.layout.simple_list_item_1, nomes);
    setListAdapter(adapter);
    registerForContextMenu(getListView());
  }
    
  @Override
  public void onCreateContextMenu(ContextMenu menu, 
    View v, ContextMenuInfo menuInfo) {
    super.onCreateContextMenu(menu, v, menuInfo);
    getMenuInflater().inflate(
      R.menu.menu_contexto, menu);
  }
    
  @Override
  public boolean onContextItemSelected(MenuItem item){
    AdapterContextMenuInfo info = 
      (AdapterContextMenuInfo)item.getMenuInfo();
     
    String nomeSelecionado = (String)
      getListView().getItemAtPosition(info.position);
     
    switch (item.getItemId()) {
    case R.id.opcao1:
      Toast.makeText(this, "Opção 1 - "+ 
        nomeSelecionado, Toast.LENGTH_SHORT).show();
      return true;
    case R.id.opcao2:
      Toast.makeText(this, "Opção 2 - "+ 
        nomeSelecionado, Toast.LENGTH_SHORT).show();
      return true;
    case R.id.opcao3:
      Toast.makeText(this, "Opção 3 - "+ 
        nomeSelecionado, Toast.LENGTH_SHORT).show();
      return true;   
    }
    return super.onContextItemSelected(item);
  }
}
A primeira etapa para criar o menu de contexto é chamar o método registerForContextMenu, passando um objeto View. Pode ser qualquer View, mas no nosso exemplo estamos pegando a ListView (que a classe ListActivity tem internamente). No método onCreateContextMenu utilizamos a classe MenuInflater para carregar o arquivo de menu que criamos anteriormente.
Por fim, para tratar as opções de menu, utilizamos o método onContextItemSelected. Esse método começa resolvendo um problema que temos quando usamos um menu de contexto em uma lista: quando o menu de contexto é aberto, perdemos o item que estava selecionado na lista.
Felizmente, podemos obter essa informação através do método getMenuInfo, que quando chamado em um item de menu que está associada a view, que é preenchida por um Adapter (a ListView está usando um ArrayAdapter para exibir seu conteúdo), retorna uma instância da classe AdapterContextMenuInfo. Nela podemos obter a linha que foi selecionada através do atributo position.
Para saber a opção de menu que foi selecionada, usamos o método getItemId, e comparamos com os ids que foram definidos no arquivo XML de menu. Abaixo temos a nossa aplicação em execução.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

terça-feira, 16 de outubro de 2012

Pós Graduação em Dispositivos Móveis em Fortaleza-CE

Olá povo,

A convite do meu nobre colega Robério Gomes, estarei ministrando nos próximos finais de semana, a disciplina de Google Android na Especialização em Desenvolvimento de Sistema para Dispositivos Móveis na Faculdade FA7 em Fortaleza-CE.
Ainda nesse mesmo curso, ministrarei a disciplina de iOS, onde estudaremos como desenvolver aplicações para os dispositivos da Apple (iPhone, iPad e iPod).

Espero que dê tudo certo e que os alunos aproveitem.

4br4ç05,
nglauber


terça-feira, 9 de outubro de 2012

NSPredicate no iOS

Olá povo,

Na última Campus Party Recife, assisti uma palestra do professor Fernando Castor da UFPE, e uma coisa que ele falou me chamou a atenção (veja minuto 29:58): "Java é igual ao Marlon Brando, quando chegou era bonitão, mas hoje envelheceu"(e o ator já morreu).
Estou atualmente em um projeto com iOS, e recentemente fiz um curso de Adobe Air e outro de Windows Phone. O que pude notar, é que em todos esses ambientes, as linguagens evoluíram e estão trazendo dinamismos que facilitam bastante a vida do desenvolvedor. O conceito de Binding utilizado pelo Air e pelo WP7 é um exemplo desse tipo de coisa.
Depois dessa introdução poética :) hoje quero falar de um conceito bem bacana do Objective-C, linguagem utilizada para criar aplicativos para o iOS: os predicates. Eles permitem fazer buscas em listas de objetos sem ter que iterar sobre eles. Felipe Vasconcelos me mostrou isso e achei bem bacana! :)

#define KEY_NOME  @"nome"
#define KEY_IDADE @"idade"
#define KEY_PAI   @"pai"

@interface MinhaPessoa : NSObject
@property (strong, nonatomic) NSString *nome;
@property (assign, nonatomic) int idade;
@property (strong, nonatomic) MinhaPessoa *pai;
@end

@implementation MinhaPessoa

@end

int main(int argc, char *argv[])
{
  NSMutableArray *complexList = 
    [NSMutableArray new];

  // Adicionando um NSDictionary
  [complexList addObject: @{
    KEY_NOME  : @"Antônio",
    KEY_IDADE : @10,
    KEY_PAI   : @{ KEY_NOME : @"Fulano" }
  }];

  // Outro NSDictionary
  [complexList addObject: @{
    KEY_NOME  : @"Francisco",
    KEY_IDADE : @20,
    KEY_PAI   : @{ KEY_NOME : @"Fulano"}
  }];

  // Agora um objeto Pessoa
  MinhaPessoa *p  = [MinhaPessoa new];
  p.nome     = @"João";
  p.idade    = 30;
  p.pai      = [MinhaPessoa new];
  p.pai.nome = @"Cicrano";
  [complexList addObject:p];

  // De novo um NSDicionary
  [complexList addObject: @{
    KEY_NOME  : @"Nelson",
    KEY_IDADE : @28,
    KEY_PAI   : @{ KEY_NOME : @"Beltrano"}
  }];
  // E pra finalizar, outra Pessoa
  p          = [MinhaPessoa new];
  p.nome     = @"Glauber";
  p.idade    = 28;
  p.pai      = [MinhaPessoa new];
  p.pai.nome = @"Beltrano";
  [complexList addObject:p];

  // pai.nome (nome do pai que contenha B)
  NSString *attribute = [NSString 
    stringWithFormat:@"%@.%@", KEY_PAI, KEY_NOME];
  // predicado para a busca
  NSPredicate* predicate = [NSPredicate 
    predicateWithFormat:
      @"%K contains[cd] %@", attribute, @"B"];
  NSArray *resultado = [complexList 
    filteredArrayUsingPredicate:predicate];

  for (id item in resultado) {
    NSLog(@"%@", [item description]);
  }
}
O código acima mostra bem o dinamismo no Objective-C. Primeiro, criamos um array misturando dois tipos de objetos: NSDictionary e MinhaPessoa. Essas classes têm em comum que os atributos da classe MinhaPessoa são iguais às chaves usadas no dicionário.
Uma vez alimentado o array complexList, criamos um objeto NSPredicate que fará o filtro "pai.nome contains[cd] 'B'", ou seja, todos os objetos onde o nome do pai contenha "B". O 'cd' significa que a busca será case insensitive e que serão ignorados os acentos. Através do método filteredArrayUsingPredicate obtemos um array resultado com os objetos filtrados. Por fim, fazemos um foreach para percorrer os resultados.

Saiba mais sobre predicados aqui.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

segunda-feira, 1 de outubro de 2012

Upload de arquivos no Android

Olá povo,

Esse post tava há um bom tempo como rascunho no blog e só agora consegui terminar para postar aqui. Vou mostrar como fazer uma aplicação Android que faz upload de arquivos para um servidor web.
public class UploadActivity extends Activity {

  private TextView txtArquivo;
 
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_upload);

    txtArquivo = (TextView)
      findViewById(R.id.txtArquivo);
  }
 
  // Ao selecionar o arquivo, preenchemos o 
  // TextView com o caminho real do arquivo
  @Override
  protected void onActivityResult(int requestCode, 
    int resultCode, Intent data) {
    super.onActivityResult(requestCode, 
      resultCode, data);

    if (resultCode == RESULT_OK) {
      if (requestCode == 0) {
        Uri selectedImageUri = data.getData();
        txtArquivo.setText(getPath(selectedImageUri));
      }
    }  
  }

  // Botão que abre a galeria de mídia para 
  // selecionar um arquivo
  public void selecionarArquivo(View v){
    Intent intent = new Intent();
    intent.setType("image/*");
    intent.setAction(Intent.ACTION_GET_CONTENT);
    startActivityForResult(Intent.createChooser(
      intent,"Selecione uma imagem"), 0);
  }
 
  // Botão que envia o arquivo selecionado
  public void enviarArquivo(View v){
    try {
      new Thread(){
        public void run(){
          executeMultipartPost();
        }
      }.start();
    } catch (Exception e) {
      Log.e("NGVL", e.getMessage());
    }  
  }
 
  // Obtém o caminho real do arquivo 
  // através do ContentProvider
  public String getPath(Uri uri) {
    String[] projection = { 
      MediaStore.Images.Media.DATA };

    Cursor cursor = getContentResolver().query(
      uri, projection, null, null, null);

    int column_index = cursor.getColumnIndexOrThrow(
      MediaStore.Images.Media.DATA);

    cursor.moveToFirst();
    return cursor.getString(column_index);
  }
 
  // Método que realmente envia o arquivo
  public void executeMultipartPost() 
    throws Exception {

    String caminhoDoArquivoNoDispositivo = 
      txtArquivo.getText().toString();

    String urlDoServidor = 
      "http://url_servidor/pagina_que_trata_upload";
    String lineEnd = "\r\n";
    String twoHyphens = "--";
    String boundary = "*****"; // Delimitador

    byte[] buffer;
    int bytesRead, bytesAvailable, bufferSize;
    int maxBufferSize = 1 * 1024 * 1024; // 1MB

    try {
      // Criando conexão com o servidor
      URL url = new URL(urlDoServidor);
      HttpURLConnection connection = 
        (HttpURLConnection) url.openConnection();

      // Conexão vai ler e escrever dados
      connection.setDoInput(true);
      connection.setDoOutput(true);
      connection.setUseCaches(false);

      // Setando método POST
      connection.setRequestMethod("POST");

      // Adicionando cabeçalhos
      connection.setRequestProperty(
        "Connection", "Keep-Alive");
      connection.setRequestProperty(
        "Content-Type",
        "multipart/form-data;boundary=" + boundary);
      connection.connect();
   
      // Escrevendo payload da requisição
      DataOutputStream outputStream = 
        new DataOutputStream(
          connection.getOutputStream());
      outputStream.writeBytes(
        twoHyphens + boundary + lineEnd);
      outputStream.writeBytes(
        "Content-Disposition: form-data; "+
        "name=\"uploadedfile\";filename=\""+ 
        caminhoDoArquivoNoDispositivo + "\"" +
        lineEnd);
      outputStream.writeBytes(lineEnd);

      // Stream para ler o arquivo
      FileInputStream fileInputStream = 
        new FileInputStream(new File(
          caminhoDoArquivoNoDispositivo));
   
      // Preparando para escrever arquivo
      bytesAvailable = fileInputStream.available();
      bufferSize = Math.min(
        bytesAvailable, maxBufferSize);
      buffer = new byte[bufferSize];

      // Lendo arquivo e escrevendo na conexão
      bytesRead = fileInputStream.read(
        buffer, 0, bufferSize);

      while (bytesRead > 0) {
        outputStream.write(buffer, 0, bufferSize);
        bytesAvailable = fileInputStream.available();
        bufferSize = Math.min(
          bytesAvailable, maxBufferSize);
        bytesRead = fileInputStream.read(
          buffer, 0, bufferSize);
      }
      outputStream.writeBytes(lineEnd);
      outputStream.writeBytes(
        twoHyphens + boundary + 
        twoHyphens + lineEnd);

      // Obtendo o código e a mensagem
      // de resposta do servidor
      int serverResponseCode = 
        connection.getResponseCode();
      String serverResponseMessage = 
        connection.getResponseMessage();

      Log.d("NGVL", serverResponseCode +" = "+ 
        serverResponseMessage);
   
      fileInputStream.close();
      outputStream.flush();
      outputStream.close();
   
    } catch (Exception ex) {
      // Exception handling
    }
  }
}
O código acima está todo comentado, então vou mostrar o arquivo de layout da aplicação. O único detalhe que eu quero ressaltar, é que utilizei uma Thread apenas pra facilitar o código, mas deveríamos utilizar uma AsyncTask ou até um Service.
<LinearLayout 
  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"
  android:orientation="vertical" >

  <TextView
    android:id="@+id/txtArquivo"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Arquivo a ser enviado" />

  <Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Selecionar arquivo" 
    android:onClick="selecionarArquivo"/>

  <Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Enviar" 
    android:onClick="enviarArquivo"/>

</LinearLayout>
A última coisa a fazer é adicionar a permissão de INTERNET no AndroidManifest.xml.
<uses-permission 
  android:name="android.permission.INTERNET"/>
A figura abaixo mostra a aplicação em execução:

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

segunda-feira, 24 de setembro de 2012

Mapas no iOS

Olá povo,

Esse post é grande, mas é bem legal. Nele, vou falar sobre a utilização de Mapas no iOS. Conheceremos as principais classes do MapKit, e além desse objetivo principal, utlizaremos um conceito interessante do Objective-C, os blocks.
Para começar crie um novo projeto no Xcode (pode ser Single View Application sem utilizar Storyboards). Adicione os frameworks MapKit e CoreLocation à sua aplicação. Em seguida, crie uma nova classe chamada Endereço e deixe-a conforme abaixo:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>

@interface Endereco : NSObject<MKAnnotation>

@property (strong, nonatomic) NSString *titulo;
@property (strong, nonatomic) NSString *subtitulo;

- (id)initWithCoordinate:(CLLocationCoordinate2D) c;
@end
#import "Endereco.h"

@implementation Endereco

@synthesize titulo, subtitulo;

@synthesize coordinate;

- (id)initWithCoordinate:(CLLocationCoordinate2D) c {
    coordinate = c;
    NSLog(@"%f,%f", c.latitude, c.longitude);
    return self;
}

- (NSString *)subtitle {
    return subtitulo;
}

- (NSString *) title {
    return titulo;
}
@end
A classe acima está implementando o protocolo MKAnnotation que exige que exista uma propriedade coordinate e os métodos subtitle e title. Isso porque, usaremos essa classe para representar um ponto no Mapa.
Agora deixe o ViewController da sua aplicação conforme abaixo (o nome da classe obviamente pode ser alterado...).
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#import "Endereco.h"

@interface NGViewController : UIViewController 
  <UISearchBarDelegate, MKMapViewDelegate>

@property (weak, nonatomic) IBOutlet 
  UISearchBar *searchBar;
@property (weak, nonatomic) IBOutlet 
  MKMapView *mapa;
@property (weak, nonatomic) IBOutlet 
  UISegmentedControl *segMapType;
@property (strong, nonatomic) 
  Endereco *endereco;

- (IBAction)mapViewChanged:(id)sender;

@end
#import "NGViewController.h"
#import "Endereco.h"

@implementation NGViewController

@synthesize searchBar;
@synthesize mapa;
@synthesize segMapType;
@synthesize endereco;

#define API_KEY @"SUA_CHAVE"
#define TEMPLATE_URL 
  @"http://maps.google.com/maps/geo?q=%@&output=csv&key=%@"

#pragma mark - View lifecycle
- (void)viewDidUnload {
    [self setSearchBar:nil];
    [self setMapa:nil];
    [self setSegMapType:nil];
    [super viewDidUnload];
}

#pragma mark - SegmentedControl value changed
- (IBAction)mapViewChanged:(id)sender {
  // De acordo com a posição selecionada no 
  // SegmentedControl, mudamos o tipo de 
  // visualização do mapa
  switch ([segMapType selectedSegmentIndex]) {
    case 0: mapa.mapType = MKMapTypeStandard;  break;
    case 1: mapa.mapType = MKMapTypeSatellite; break;
    case 2: mapa.mapType = MKMapTypeHybrid;    break;
  }
}

#pragma mark - SearchBar methods
-(void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
  // Ao clicar em cancelar na barra de busca, 
  // ocultamos o teclado
  [self.searchBar resignFirstResponder];
}

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
  [self.searchBar resignFirstResponder];    
    
  [self retrieveLocation:self.searchBar.text];
}

#pragma mark - Utility Methods

- (void) retrieveLocation:(NSString *)search {
  // Criando URL de requisição
  NSString *query = 
    [search stringByAddingPercentEscapesUsingEncoding:
      NSUTF8StringEncoding]; 
   
  NSString *urlString = [NSString stringWithFormat:
    TEMPLATE_URL, query, API_KEY];
    
  // Preparando request
  NSURLRequest *request = [[NSURLRequest alloc]
    initWithURL:[NSURL URLWithString:urlString]];
    
  // Declarando o Block "trataResposta" para tratar 
  // retorno. Esse block é uma função que retorna 
  // void e recebe 3 parâmetros: uma response, os 
  // dados e um erro. 
  id trataResposta = ^(NSURLResponse *response, 
    NSData *data, NSError *error) {
      if (error){
        [self showErrorDialog];
            
      } else {
        [self trataResposta:data];
      }
    };
    
  // Fazendo a requisição assícrona para evitar que 
  // a tela pareça estar travada. Aqui usamos o 
  // recurso de blocks, declarado acima e que será 
  // chamado após a requisição ser concluída
  [NSURLConnection sendAsynchronousRequest:request 
    queue:[NSOperationQueue mainQueue] 
      completionHandler:trataResposta];
}

- (void) trataResposta:(NSData *)data {
  NSString *locationString = [[NSString alloc]
    initWithData:data encoding:NSUTF8StringEncoding];

  // Nesse array, o primeiro elemento é o código de 
  // resposta HTTP (200 é sucesso) os próximos (se 
  // deu tudo certo) é a latitude e longitude.
  NSArray *listItems = [locationString 
    componentsSeparatedByString:@","];
    
  double longitude = 0;
  double latitude = 0;
    
  if ([listItems count] > 0 && 
     [[listItems objectAtIndex:0] 
       isEqualToString:@"200"]){

    latitude = [[listItems objectAtIndex:2] 
      doubleValue];
    longitude = [[listItems objectAtIndex:3] 
      doubleValue];
  }
    
  // Cria o objeto location fazer obter informações 
  // do local via GeoCoder
  CLLocation *location = [[CLLocation alloc] 
    initWithLatitude:latitude longitude:longitude];
  [self retrieveGeocoder:location];
}

- (void) retrieveGeocoder:(CLLocation *)location {
  CLGeocoder *geocoder = [[CLGeocoder alloc] init];
    
  // Mais uma chamada assícrona que recebe 
  // um block como parâmetro
  [geocoder reverseGeocodeLocation:location 
    completionHandler:
      ^(NSArray *placemarks, NSError *error) {
         
    if ([placemarks count] > 0){
             
      CLPlacemark *placemark = 
        [placemarks objectAtIndex:0];
             
      [self addAnnotation:location.coordinate 
         withMark:placemark];
             
    } else {
      [self showErrorDialog];
    }
  }];
}

- (void)showErrorDialog {
  UIAlertView *alert = [[UIAlertView alloc] 
    initWithTitle:@"Erro" 
    message:@"Erro ao obter dados da localização" 
    delegate:nil cancelButtonTitle:@"OK" 
    otherButtonTitles:nil, nil];
  [alert show];
}

- (void)addAnnotation:
  (CLLocationCoordinate2D)coordinate 
   withMark:(CLPlacemark *)placemark {

  // Se já houver uma marcação no mapa, remova
  if (self.endereco != nil) {
    [mapa removeAnnotation:self.endereco];
    self.endereco = nil;
  }
    
  // Criando MKNotation para adicionar no Mapa
  self.endereco = [[Endereco alloc]
  initWithCoordinate:coordinate];
    
  // Configura o título e subtítulo da MKNotation 
  // baseado no CLPlacemark
  self.endereco.titulo = [NSString stringWithFormat:
    @"%@, %@\n%@", 
      placemark.locality, 
      placemark.administrativeArea, 
      placemark.postalCode];
    
  self.endereco.subtitulo = 
    [NSString stringWithFormat:@"%@, %f,%f", 
      placemark.thoroughfare,
      coordinate.latitude, 
      coordinate.longitude];
    
  // Adiciona a notação ao mapa
  [self.mapa addAnnotation:self.endereco];

  /* A MKCoordinateSpan representa a quantidade 
     de área usada pela view e é usada para 
     determinar o zoom. Quanto maior o valor, 
     maior a área visível. */
  MKCoordinateSpan span;
  span.latitudeDelta = 0.02;
  span.longitudeDelta = 0.02;
    
  // A MKCoordinateRegion serve para movimentar o 
  // mapa até a região determinada no MKNotation
  MKCoordinateRegion region;
  region.center = coordinate;
  region.span = span;
    
  [self.mapa setRegion:region animated:YES];
  [self.mapa regionThatFits:region]; 
}

#pragma mark - MapView
-(MKAnnotationView *)mapView:(MKMapView *)mapView 
  viewForAnnotation:(id<MKAnnotation>)annotation {
  
  /* Ao adicionar uma notação no mapa, esse método 
     é chamado. É aqui que criamos o pin que aparece
     no mapa */
  MKPinAnnotationView *pin = 
    [[MKPinAnnotationView alloc] initWithAnnotation:
      annotation reuseIdentifier:@"currentLoc"];
    
  pin.pinColor = MKPinAnnotationColorGreen;
  pin.animatesDrop = TRUE;
  pin.canShowCallout = YES;
  pin.calloutOffset = CGPointMake(-5, 5);
    
  return pin;
}

@end
O código está comentado, qualquer dúvida, deixe seus comentários. Agora abra o XIB referente a essa tela e adicione uma SearchBar, um MapView e um SegmentedControl de modo que ele fique conforme a figura abaixo:
No SearchBar, ligue-o com a propriedade searchBar e sete o delegate. Para o MapView, faça o mesmo com a propriedade mapa. E por fim, com o SegmentedControl ligue-o com a propriedade segMapType,  e no evento Value Changed ligue com o método mapViewChanged.

Agora é só rodar e pesquisar pelos lugares desejados.
Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

domingo, 16 de setembro de 2012

Paginação com Swipe no Android

Olá povo,

O versão 20 do plugin ADT (Android Development Toolkit) trouxe algumas novidades, entre elas, alguns templates de projetos que podemos optar na criação de um novo projeto Android. Hoje vou  explicar cada um desses templates e ver como utilizar o template que permite fazer uma paginação similar a do Google Play mostrada abaixo.


Note que a tela "Mais rentáveis" está em evidência, mas podemos notar que há uma opção a esquerda e outra a direita na parte superior. Esse é o intuito da paginação, que permitirá ao usuário exibir a próxima informação apenas deslizando para os lados. Vamos mostrar como fazer isso na sua aplicação.
Inicie a criação de um novo projeto e preencha os campos conforme abaixo e pressione Next.


Na segunda tela, há a opção de criarmos uma Activity em branco ou do tipo Mestre/Detalhe. A primeira cria uma Activity simples,  já a segunda, cria uma Activity que exibe uma listagem e os respectivos detalhes. Se a aplicação estiver executando em um tablet, a listagem e os detalhes de um item aparecem na mesma Activity, caso seja um smartphone, a listagem ficará em uma Activity e os detalhes serão exibidos em outra.
Selecione Blank Activity e clique em Next.
A próxima tela do assistente, solicita o nome da Activity que queremos criar, o nome do seu respectivo arquivo de layout e o título da Activity. Mas a informação mais interessante para nós aqui é o campo Navigation Type, que nos dá as seguintes opções:
A opção None não cria nenhuma opção pré-definida de navegação;
Selecionando Tabs, será criada uma estrutura básica de um Activity com abas usando uma ActionBar (que requer Android 4.1);
Tabs + Swipe é igual a anterior, mas será possível alternar entre as abas com o gesto de swipe (também requer Android 4.1);
Swipe Views + Title Strip permite alternar entre views usando swipe, e o título da view corrente é mostrado em um componente PageStrip;
A última opção é a Dropdown, que configura uma ActionBar onde o usuário poderá selecionar, através de um combobox, a tela que deseja abrir. Ao selecionar o item da lista, um Fragment será carregado (também requer Android 4.1)
Vamos utilizar a opção Swipe Views + Title Strip, pois funcionará em todas as versões do Android. Selecione-a e clique em Finish.
Uma coisa curiosa, é que para utilizar os templates acima, o assistente exige que seja selecionada a versão 4.0.1 do Android na primeira tela do assistente. Mas no caso do template que selecionamos, é possível fazer algumas alterações no projeto para que o mesmo funcione em aparelhos com versões anteriores. Tudo isso, graças a API de compatibilidade do Google (que fica na pasta libs do projeto).
1) Clique com o botão direito sobre o projeto e selecione Properties. Na tela que for exibida, selecione Android do lado esquerdo e em seguida, selecione Android 2.3 (API Level 10) e clique em OK.
2) A Activity dará erro, pois estamos importando duas classes que não existem no Android 2.3: android.app.ActionBar e android.app.FragmentTransaction. Remova esses dois imports.
3) Agora vá no AndroidManifest.xml e modifique o minSdkVersion para 10 (Android 2.3).
<uses-sdk
  android:minSdkVersion="10"
  android:targetSdkVersion="15"/>
4) Dará erro agora nas pastas values-v11, values-v14 e menu. Remova as três.
5) E para finalizar, remova o método onCreateOptionsMenu da sua Activity.

Pronto! Agora é só rodar no seu dispositivo 2.3, o resultado ficará como abaixo:
Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

P.S.: Post relacionado: http://nglauber.blogspot.com.br/2011/09/pagescroll.html