sexta-feira, 22 de fevereiro de 2013

Android: Dicas 8

Olá povo,

Mais um post da série "Dicas de Android", aproveitem! E como sempre, qualquer dúvida ou sugestão deixem seus comentários.

Dica 1 - Crash Report

Essa dica veio de Charles Alves do Curso de Android EAD do CESAR.edu. É possível enviar relatórios de erro da sua aplicação para onde o desenvolvedor desejar através do ACRA (https://github.com/ACRA/acra), por padrão, ele envia para um formulário no google docs.

Dica 2 - Verificar recursos do aparelho.

Através da classe PackageManager podemos verificar se um aparelho conta com um determinado recurso de hardware, como câmera, wi-fi, etc.

PackageManager pm = getPackageManager();
boolean temCamera = 
  pm.hasSystemFeature(
    PackageManager.FEATURE_CAMERA);
boolean temWiFiDirect = 
  pm.hasSystemFeature(
    PackageManager.FEATURE_WIFI_DIRECT);
boolean temScreenPortrait = 
  pm.hasSystemFeature(
    PackageManager.FEATURE_SCREEN_PORTRAIT);

Dica 3 - Mockups pra Android (Dica de Eric Cavalcanti)

Uma ótima ferramenta de prototipagem para Android é o Pencil (http://pencil.evolus.vn). Vale a pena conferir.

Dica 4 - Obtendo o tamanho da tela
Muitas vezes é necessário obter o tamanho da tela. Podemos fazer isso através da classe Display. Ela é retornada pela classe WindowManager que é um serviço do sistema.

Display display = ((WindowManager) 
  getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
int width = display.getWidth();  
int height = display.getHeight();

Dica 5 - Rotação do Emulador 2.3
Um "problema" que eu sempre tinha no emulador do 2.3, era que quando eu girava o emulador para landscape e voltava para portrait, o mesmo continuava em landscape. Para contornar isso, acesse a opção Menu/Settings/Screen e desabilite a opção Autorotate.

Dica 6 - Invertendo os itens da ListView
Podemos fazer com que os itens de uma ListView começem de baixo, basta usar a propriedade android:stackFromBottom="true".

Dica 7 - PopupWindow
Só suportado no Android 3.0 (API Level 11)
public void showPopup(View v) {
    PopupMenu popup = new PopupMenu(this, v);
    MenuInflater inflater = popup.getMenuInflater();
    inflater.inflate(R.menu.actions, popup.getMenu());
    popup.show();
}

Dica 8 - Exibir um vídeo
Dica de Tiago Gomes, aluno da Unibratec. Tocar vídeo da pasta res/raw

VideoView video = (VideoView) 
  findViewById(R.id.videoView1);
Uri uri = Uri.parse("android.resource://" + 
  getPackageName() + "/raw/arquivo_video");
video.setVideoURI(uri);
video.requestFocus();
video.start();

Dica 9 - API para gráficos de "pizza"
Dúvida de Rodrigo Bezerra. Eu nunca usei, mas aparentemente funciona :)
http://www.achartengine.org/

4br4ç05,
nglauber

sexta-feira, 15 de fevereiro de 2013

iOS: Dicas 2

Olá povo,

Esse post vai em homenagem a minha turma do Curso de iOS à distância que ministrei no CESAR.edu. Foram deles as dúvidas abaixo que resolvi compartilhar com vocês.

Dica 1 - Adicionando UITableView ao UIAlertView
Podemos adicionar qualquer UIView dentro de um UIAlertView. Basta usar o método addSubView e posicioná-la da maneira que deseja. O que achei "feio" aqui é que para determinar o tamanho do alert setamos o texto dele com "\n". Poderíamos utilizar o método initWithFrame, mas achei o código ainda mais complexo (embora mais preciso em termos de tamanho).

UITableView *table = 
  [[UITableView alloc] 
    initWithFrame:CGRectMake(10, 50, 265, 100)];    
table.dataSource = self;
table.delegate = self;
    
UIAlertView *alert = [[UIAlertView alloc] 
  initWithTitle:@"Escolha uma opção"
  message:@"\n\n\n\n\n" 
  delegate:self 
  cancelButtonTitle:@"OK" 
  otherButtonTitles:nil, nil];
    
[alert addSubview:table];
alert.autoresizesSubviews = YES;
[alert show];

Tem uma opção interessante aqui também.

Dica 2 - Desabilitando um segmento do UISegmentedControl
Essa foi simples de achar, mas na aula passou desapercebido.

[_meuSegmentedControl 
  setEnabled:NO forSegmentAtIndex:0];

Dica 3 - Definir o badge da aplicação
O badge nada mais é do que o número que aparece em cima do ícone da aplicação, por exemplo quanto temos chamadas não atendidas e/ou mensagens recebidas.

[[UIApplication sharedApplication] 
  setApplicationIconBadgeNumber:12];

Dica 4 - Adicionar View ao teclado
Ao invés de criar a view no código, você pode adicionar a mesma no XIB da tela e setar a propriedade inputAccessoryView. Para exemplificar, adicionei uma Toolbar ao XIB e crie um IBOutlet para ela.
@interface NGViewController : UIViewController
@property (weak, nonatomic) 
  IBOutlet UITextField *txtSobrenome;
@property (weak, nonatomic) 
  IBOutlet UIToolbar *minhaBarra;
@end
Depois é só colocar isso no viewDidLoad
[_txtSobrenome setInputAccessoryView:_minhaBarra];


Dica 5 - Ocultar teclado virtual
Sempre xingava o iOS por ter que ocultar o teclado via código. Mas achei a melhor maneira para ocultar o teclado. É só usar o método touchesBegan:withEvent da classe UIRespoder (super classe de UIView e UIViewController)  e chamar o método endEditing da UIView.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self.view endEditing:YES];
}
Independente da quantidade de UITextFields, o teclado vai sempre desaparecer.

Dicas 6 - Obtendo a orientação
Uma das melhores maneiras de obter a orientação do dispositivo é a abaixo:

// Verifica se é portrait ou portrait upsidedown
if (UIDeviceOrientationIsPortrait(
      [[UIApplication sharedApplication] 
        statusBarOrientation])){
}

Essa abordagem resolve alguns problemas que já tive. Por exemplo, quando o telefone está sobre a mesa, o método [[UIDevice currentDevice]orientation] retorna a orientação UIDeviceOrientationFaceUp (com o display para cima), enquanto que o método acima retorna a orientação da barra de status do aparelho e não do aparelho em si.

Qualquer dúvida (ou sugestão), deixem seus comentários,

4br4ç05
nglauber

sexta-feira, 1 de fevereiro de 2013

@selector, Blocks e NSInvocation

Olá povo,

O iOS trabalha fortemente com o padrão Delegate, onde você determina uma classe que implementa um dado protocolo e a mesma tratará algum tipo de evento. Isso nada mais é do que o padrão de projeto observer (levemente modificado). Mas em diversos pontos da API também vemos uma outra abordagem: as funções/metódos de callback. Os callbacks, bem famosos no JavaScript, são métodos ou funções que são passados como parâmetros para outros métodos, e são chamados em um dado momento da execução do método para qual ele foi passado.
Vou falar nesse post de como podemos usar os callbacks no iOS.

@selector
Uma forma de usar os callbacks é usando selectors. Digamos que você esteja implementando um método que fará uma série de processamento, e ao final você quer avisar para quem chamou esse método que o mesmo acabou. Isso seria a clássica implementação de um Observer (ou Delegate). Mas podemos fazer com selectors dessa forma:
// Precisamos pra chamar objc_msgSend
#import <objc/message.h>

- (void) executar:(SEL)funcaoDeCallback {
  NSLog(@"Um monte de processamento aqui…");
  if ([self respondsToSelector:funcaoDeCallback]){
    objc_msgSend(self, funcaoDeCallback, @"Glauber");
  }
}
O método executar tem um parâmetro do tipo SEL, que é um ponteiro para um método. Dentro do método checamos se a nossa classe (self) responderá aquele selector, ou seja, se ela declarou um método com essa assinatura. Em caso positivo, usamos a função objc_msgSend passando qual o objeto que tem o método, o selector (ou seja, o método) e um parâmetro (que eu quis passar só pra ilustrar). Então, para executarmos um selector, temos sempre que passar o target (que é o objeto que tem o método) e o selector.
Aqui nós usamos a função objc_msgSend ao invés do famoso performSelector. Isso porque com a migração para o ARC, o compilador não consegue garantir que a função existe e então exibe um Warning. Uma alternativa para essa função e continuar usando o performSelector é conforme abaixo:
#pragma clang diagnostic push
#pragma clang diagnostic ignored 
  "-Warc-performSelector-leaks"
[self performSelector:funcaoDeCallback 
  withObject:@"Glauber"];
#pragma clang diagnostic pop
E para chamar o método executar, de modo que depois ele chame o método aqui:
SEL selector = @selector(meuMetodo:);
[self executar:selector];
Digamos que o método aqui ficasse dessa forma:
- (NSString *)meuMetodo:(NSString *)texto {
    NSLog(@"O executar retornou: %@", texto);
    return @"OK";
}
Um exemplo clássico do uso de selector na API é na criação de botões para NavigationBar, onde determinamos o método que será chamado quando clicar no mesmo.
UIBarButtonItem *leftButton = [[UIBarButtonItem alloc] 
  initWithTitle:@"Esquerda" 
  style:UIBarButtonItemStyleBordered 
  target:self action:@selector(meuClick)];


NSIvocation
E como faríamos para pegar o retorno usando @selector? Temos uma outra alternativa. Utilizar a classe NSInvocation.
SEL selector = @selector(aqui:);
    
NSString *parametro = @"Glauber";
    
NSMethodSignature * assinatura = 
  [self methodSignatureForSelector:selector];
NSInvocation * invocacao = [NSInvocation 
  invocationWithMethodSignature:assinatura];
[invocacao setTarget:self];
[invocacao setSelector:selector];
[invocacao setArgument:&parametro atIndex:2];
[invocacao invoke];
    
NSString *retorno;
[invocacao getReturnValue:&retorno];
NSLog(@"Retorno: %@", retorno);
Notem que para passar o parâmetro usamos 2 para o primeiro (e único) parâmetro. Isso porque as posições 0 e 1 estão reservadas para self e _cmd respectivamente.

Blocks
A partir do iOS 4, foi disponibilizado uma outra forma de usar funções de callback, os blocks. Seu propósito é bem similar ao selector. Eles são particularmente úteis para executar ações concorrentes ou para callbacks. As principais vantagens dos blocks são: Eles permitem escrever código o código perto da sua chamada (como inner classes do Java); e permitem acessar variáveis locais. Vejamos abaixo a sintaxe e como ele funciona.
int multiplicador = 7;
int (^meuBloco)(int) = ^(int num) {
    return num * multiplicador;
};
 
NSLog("%d", meuBloco(3));
No código acima, declaramos um bloco que retorna um int, chama-se meuBloco e recebe um inteiro como parâmetro. O nome do parâmetro (num) é definido na inicialização do block. Se quisermos declarar um método que recebe um block que retorna void e recebe uma NSString por exemplo:
- (void)meuMetodoRecebeUmBlock:(void (^)(NSString *texto))bloco{
    NSLog(@"Faça alguma coisa");
    bloco(@"Terminou");
}
E para chamar:
[self meuMetodoRecebeUmBlock:^(NSString* texto){
    NSLog(@"meuMetodoRecebeUmBlock disse: %@", texto);
}];

Outra grande vantagem de utilizar os blocks em relação ao selector, é que não se faz necessário passar o target para o método. Podemos chamá-lo diretamente sem ter a referência do objeto que tem o método. Além disso, conseguimos pegar um retorno de um método mais facilmente do que com o @selector.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

Fontes: 
http://iphonedevblog.com/code/how-to-use-nsinvocation/
http://pragmaticstudio.com/blog/2010/7/28/ios4-blocks-1

sábado, 26 de janeiro de 2013

YouTube API para Android

Olá povo,

No dia 22/01/2013 a Google anunciou uma API para adicionar vídeos do YouTube nas aplicações Android. Os requisitos mínimos para utilização da API é que o dispositivo rode Android 2.2 ou superior e ter a aplicação do Youtube versão 4.2.16 ou mais recente instalada (por isso, não consegui rodar no emulador). Outro detalhe importante é que os vídeos poderão ser executados tanto em Activities diretamente ou em Fragments.
E nesse primeiro post de 2013 vou mostrar como dar os primeiros passos.

Gerando a chave de acesso
A primeira coisa que devemos fazer é registrar nossa aplicação no site Google APIs Console. Para termos acesso aos mais diversos serviços do Google, precisamos criar um projeto (se você já não tiver um). Para tal, clique em Create new Project. Agora acesse a opção API access depois clique no botão Create new Android key (se você não tiver uma), no popup que for exibido clique em Create. Será gerada uma chave no padrão SHA-1 similar a essa que usaremos no nosso código: AIzbSyCyoMobvh72ZrSv4xQddOLzDOlaLqCcILU

Baixando a API
Uma vez que temos a chave de acesso, baixe a API clicando aqui. Descompacte o arquivo zip e adicione o arquivo libs/YouTubeAndroidPlayerApi.jar à pasta libs do seu projeto Android. Ainda não criou o projeto? Tá esperando o quê? :)

Hello World Youtube!
Sem muitas delongas, adicione a permissão de Internet no AndroidManifest.xml e deixe sua Activity como abaixo:

import android.os.Bundle;

import com.google.android.youtube.player.*;
import com.google.android.youtube.player.YouTubePlayer.*;

public class MainActivity 
  extends YouTubeBaseActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    YouTubePlayerView youTubeView = 
      new YouTubePlayerView(this);

    setContentView(youTubeView);
    youTubeView.initialize(
      "AIzbSyCyoMobvh72ZrSv4xQddOLzDOlaLqCcILU", 
      new OnInitializedListener(){

      @Override
      public void onInitializationFailure(
        Provider provider,
        YouTubeInitializationResult result) {
      }

      @Override
      public void onInitializationSuccess(
        Provider provider,
        YouTubePlayer player, 
        boolean wasRestored) {
          if (!wasRestored){
            player.cueVideo("9bZkp7q19f0");
          }
      }
    }); 
  }
}
Nossa classe herda de YouTubeBaseActivity e no método onCreate instanciamos uma YouTubePlayerView que é quem exibirá o vídeo. Com esse objeto chamamos o método initialize passando a chave de acesso que geramos anteriormente além de uma instância da interface OnInitilizedListener. Essa interface tem dois métodos: um para quando a inicialização foi feita com sucesso; e outra para quando houve algum problema.
No método onInitializationSuccess usamos o objeto YouTubePlayer para inicializar o vídeo usando o método cueVideo passando o id do vídeo do YouTube. O parâmetro wasRestored é pra saber se o vídeo está sendo restaurado do ponto onde parou.



O exemplo que fizemos aqui usamos a YouTubeView, mas você pode fazer o mesmo utilizando a classe YouTubeFragment (e YouTubeSupportFragment para dispositivos anteriores ao Android 3).

Mais informações sobre a YouTube API você pode ter aqui.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

domingo, 30 de dezembro de 2012

Android Google Maps v2

Olá povo,

No último dia 03/12/2012  a Google disponibilizou a versão 2.0 da API do Google Maps para Android que trouxe diversas melhorias em relação a versão anterior. O problema é que tudo mudou completamente :) Desde a geração da chave de acesso até as classes envolvidas. A parte boa disso tudo, é que essa API funciona desde a versão 2.2 do Android e trouxe melhorias significativas \o/
Nesse post vou mostrar como dar os primeiros passos nessa nova API.

Gerando a chave de acesso

Quem já fez algum exemplo sabe que precisamos de uma chave de acesso para utilizar o serviço de mapas. Para ter acesso aos mais diversos serviços do Google, precisamos criar um projeto no site do Google APIs Console. Uma vez que o projeto foi criado, acesse a opção Services e habilite o serviço Google Maps Android API v2. Uma vez habilitado o serviço, acesse a opção API access depois clique no botão Create new Android key, no popup que for exibido clique em Create.
Será gerada uma chave no padrão SHA-1 que usaremos no nosso código similar a essa: AIzbSyCyoMobvh72ZrSv4xQddOLzDOlaLqCcILU

Biblioteca dos serviços do Google Play
A segunda etapa é baixar o pacote de serviços do Google Play. Abra o Android SDK Manager e baixe o Google Play services.
Quando terminar o download, será criado o seguinte diretório [android-sdk/extras/google/google_play_services/libproject] que contém as classes necessárias para usar a nova API. Precisamos importar esse projeto para dentro do Eclipse, pois nosso projeto irá referencia-lo.
Acesse a opção File | New project... Em seguida, selecione Android | Android Project From Existing Code e então selecione o diretório citado anteriormente. O projeto será importado para o Eclipse e agora podemos começar nosso projeto de exemplo.

Projeto de exemplo
Crie um novo projeto Android e vamos começar a brincadeira :) A primeira coisa é referenciar o projeto  do Google Play Service dentro do nosso projeto. Para isso, clique com o botão direito sobre o projeto e selecione  Properties. Na janela que for exibida, selecione Android do lado esquerdo. Clique no botão Add... e selecione google-play-services_lib e então clique em Ok.
Estamos prontos pra começar, deixe o AndroidManifest.xml como abaixo.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android=
  "http://schemas.android.com/apk/res/android"
  package="ngvl.testegmaps_v2"
  android:versionCode="1"
  android:versionName="1.0" >

  <uses-sdk
    android:minSdkVersion="9"
    android:targetSdkVersion="15" />

  <uses-permission android:name=
    "android.permission.INTERNET" />
  <uses-permission android:name=
    "android.permission.ACCESS_NETWORK_STATE"/>
  <uses-permission android:name=
    "android.permission.WRITE_EXTERNAL_STORAGE" />
  <uses-permission android:name=
    "com.google.android.providers.gsf.permission.READ_GSERVICES" />

  <uses-feature
    android:glEsVersion="0x00020000"
    android:required="true" />

  <application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <activity
      android:name="ngvl.testegmaps_v2.MainActivity"
      android:label="@string/app_name" >
      <intent-filter>
        <action android:name=
          "android.intent.action.MAIN" />
        <category android:name=
          "android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>

    <meta-data android:name=
      "com.google.android.maps.v2.API_KEY"
      android:value="suaApiKeyAqui!!!!!!" />
  </application>
</manifest>

Vamos comentar alguns detalhes do arquivo. Começamos utilizando as permissões de INTERNET e WRITE_EXTERNAL_STORAGE para podermos baixar os mapas e fazer cache dos mesmos no cartão de memória. Um requisito dessa nova API é que o aparelho deve suportar OpenGL que declaramos logo em seguida. A última configuração importante que fizemos aqui é a tag meta-data que declaramos a chave do Google Maps que geramos na primeira etapa desse post.
Agora deixe o arquivo de layout conforme abaixo.
<fragment
  xmlns:android=
    "http://schemas.android.com/apk/res/android"
  android:id="@+id/map"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:name=
    "com.google.android.gms.maps.SupportMapFragment" />
Aqui temos uma grande mudança: a classe MapView e MapActivity não são mais utilizadas. Em seu lugar entrou a classe MapFragment, mas como quis deixar a compatibilidade com versões anteriores do Android, usei a SupportMapFragment. Isso nos permite utilizar mapas em fragments, o que só era possível anteriormente através de APIs de terceiros, uma vez que tínhamos que herdar de MapActivity. Outro detalhe é que o nome do pacote também mudou, agora é com.google.android.gms.maps.
Por fim, vamos ao código da Activity.

package ngvl.testegmaps_v2;

import android.os.Bundle;
import android.support.v4.app.*;

import com.google.android.gms.common.*;
import com.google.android.gms.maps.*;
import com.google.android.gms.maps.model.*;

public class MainActivity extends FragmentActivity {

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
 
    SupportMapFragment fragment =
     (SupportMapFragment)getSupportFragmentManager()
       .findFragmentById(R.id.map);
    GoogleMap map = fragment.getMap();

    LatLng latLng = new LatLng(-23.561706,-46.655981);
    map.addMarker(new MarkerOptions()
      .position(latLng)
      .icon(BitmapDescriptorFactory.fromResource(
        R.drawable.ic_launcher))
      .title("Av. Paulista")
      .snippet("São Paulo"));
  
    configuraPosicao(map, latLng);
  }

  private void configuraPosicao(
    GoogleMap map, LatLng latLng) {

    map.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
    map.animateCamera(
     CameraUpdateFactory.newLatLngZoom(latLng, 17.0f));
  }
}
Nossa classe herda de FragmentActivity da API de compatibilidade do Google que falei aqui. Em seguida, obtemos a referência para o MapFragment, e a partir dele obtemos a instância do GoogleMaps (que substitui o MapView). A classe GeoPoint foi substituída pela classe LatLng que representa uma coordenada geográfica. Com essa informação, adicionamos um ponto no mapa, passando uma imagem que servirá de marcador no mapa, um título e um resumo, que serão exibidos ao clicar na imagem no mapa. O método configuraPosicao, define o tipo de mapa para satélite e o zoom para 17 (os valores variam de 2 a 21, sendo 21 mais próximo possível).
Se rodarmos nossa aplicação, em um aparelho 2.3 ficará similar a figura abaixo:
Uma das novidades dessa versão da API do GoogleMaps é poder visualizar o mapa em relevo, mostrando, por exemplo, os prédios do local selecionado no mapa. Modifique o método configuraPosicao para ficar como abaixo:
private void configuraPosicao(
  GoogleMap map, LatLng latLng) {
  map.moveCamera(
   CameraUpdateFactory.newLatLngZoom(latLng, 15));
  map.animateCamera(
   CameraUpdateFactory.zoomTo(10), 2000, null);

  CameraPosition cameraPosition = 
    new CameraPosition.Builder()
      .target(latLng)   
      .zoom(17)     
      .bearing(90)
      .tilt(45)
      .build();
  map.animateCamera(
    CameraUpdateFactory.newCameraPosition(
      cameraPosition));
}
Notem que não usamos o modo satélite aqui (não ficou muito bom quando usei :) mas posicionamos a câmera de uma maneira diferente. Além da Latitude/Longitude e do Zoom, setamos o bearing (rotação do mapa em 90 graus) e o tilt (inclinação em 45 graus). O resultado pode ser visto abaixo.
Bem pessoal, espero que tenham gostado e qualquer dúvida, deixem seus comentários.
Mais detalhes em: https://developers.google.com/maps/documentation/android/

 4br4ç05,
nglauber

quarta-feira, 26 de dezembro de 2012

Android ExpadableListView

Olá povo,

Esse post estava no rascunho a algum tempo e resolvi publica-lo finalmente :) Vou mostrar como utilizar a lista agrupada do Android, a ExpandableListView. Como o próprio nome diz, ela contém uma lista de elementos que ao serem clicados exibem uma sublista. Como exemplo, teremos uma lista de estados, e ao clicar no nome do estado, exibiremos suas respectivas cidades.
Abaixo temos o código da Activity da aplicação.

public class ExemploExpladableListViewActivity
  extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(
      R.layout.activity_exemplo_expladable_list_view);

    ExpandableListView listview =(ExpandableListView)
      findViewById(R.id.expandableListView1);
   
    List<String> listPe = new ArrayList<String>();
    listPe.add("Caruaru");
    listPe.add("Recife");
   
    List<String> listSp = new ArrayList<String>();
    listSp.add("São Paulo");
    listSp.add("Campinas");
   
    Map<String, List<String>> dados = 
      new HashMap<String, List<String>>();
    dados.put("PE", listPe);
    dados.put("SP", listSp);
   
    listview.setAdapter(
      new MeuExpadableAdapter(dados));
  }
}

Como podemos notar, ao invés de usar uma lista simples, utilizamos um HashMap onde cada elemento será composto de uma chave que será o nome do estado e o valor será uma lista com as cidade daquele estado. O arquivo de layout contém apenas uma ExpandableListView, conforme abaixo.

<ExpandableListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/expandableListView1"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Como uma ListView comum, utilizaremos um Adapter para exibir o conteúdo da lista. A diferença é que herdaremos de BaseExpandableListAdapter, onde teremos que implementar mais alguns métodos além dos que já estamos acostumados nas listas convencionais:

  • getGroup: retorna o objeto que representa o grupo de informações. No nosso caso, a sigla do estado;
  • getGroupCount: retorna quantos grupos nós teremos. Aqui estamos retornando quantas entradas (chaves) temos na nossa HashMap;
  • getGroupId: algum inteiro que sirva de identificador único para o grupo, aqui estamos retornando a posição do grupo;
  • getGroupView: retorna a View que representará visualmente o grupo na lista;
  • getChild: similar ao getItem do Adapter convencional, a diferença é que é passado o índice do grupo e do elemento que se deve retornar;
  • getChildId: identificador único do elemento do grupo;
  • getChildView: retorna a View que representará o elemento dentro do grupo. Similar ao getView do adapter convencional;
  • getChildrenCount: retorna a quantidade de elementos dentro de um grupo;
  • hasStableIds: informa se os identificadores dos grupos e dos elementos são fixos;
  • isChildSelectable: retorna true se os elementos filhos podem ser selecionados, ou false caso contrário.

class MeuExpadableAdapter 
  extends BaseExpandableListAdapter {

  private Map<String, List<String>> dados; 
  private List<String> keys;
 
  public MeuExpadableAdapter(
    Map<String, List<String>> dados){

    this.dados = dados;
    this.keys = new ArrayList<String>(
      dados.keySet());
  }
 
  @Override
  public Object getGroup(int groupPosition) {
    return keys.get(groupPosition);
  }

  @Override
  public int getGroupCount() {
    return dados.size();
  }

  @Override
  public long getGroupId(int groupPosition) {
    return groupPosition;
  }

  @Override
  public View getGroupView(int groupPosition, 
    boolean isExpanded, View convertView, 
    ViewGroup parent) {

    View v = LayoutInflater.from(
      parent.getContext()).inflate(
        android.R.layout.simple_expandable_list_item_1, 
        null);

    TextView txt = (TextView)
      v.findViewById(android.R.id.text1);

    txt.setText(keys.get(groupPosition));
    return v;
  }
 
  @Override
  public Object getChild(
    int groupPosition, int childPosition) {

    return dados.get(
      keys.get(groupPosition)).get(childPosition);
  }

  @Override
  public long getChildId(
    int groupPosition, int childPosition) {
    return 0;
  }

  @Override
  public View getChildView(int groupPosition, 
    int childPosition, boolean isLastChild, 
    View convertView, ViewGroup parent) {

    View v = LayoutInflater.from(
      parent.getContext()).inflate(
        android.R.layout.simple_list_item_1, null);
    TextView txt = (TextView)
      v.findViewById(android.R.id.text1);
    txt.setText(dados.get(
      keys.get(groupPosition)).get(childPosition));
    return v;
  }
 
  @Override
  public int getChildrenCount(int groupPosition) {
    return dados.get(keys.get(groupPosition)).size();
  }

  @Override
  public boolean hasStableIds() {
   return true;
  }

  @Override
  public boolean isChildSelectable(
    int groupPosition, int childPosition) {
    return true;
  } 
}

O exemplo acima (com uma leve modificação :) está aqui.
Qualquer dúvida deixem seus comentários.

4br4ç05,
nglauber

quarta-feira, 5 de dezembro de 2012

Curso de iOS à distância

Olá povo,

O CESAR.edu está promovendo o curso à distância de Desenvolvimento de Aplicativos para iOS. As aulas serão ministradas por mim on line e ao vivo, o que permitirá maior interação com os alunos.

A plataforma iOS é uma das líderes no mercado mobile e a demanda por aplicativos para seus dispositivos vem crescendo, e consequente a busca por profissionais capacitados nessa plataforma.
Neste curso serão apresentados os conceitos fundamentais para desenvolvimento de aplicações para os dispositivos móveis da Apple: iPhone, iPad e iPod Touch. O Ambiente de desenvolvimento, a linguagem de programação Objective-C, os componentes de interface gráfica e os principais recursos destes dispositivos, serão demonstrados através de uma abordagem totalmente prática e focadas em problemas reais.

O objetivo do curso é preparar os alunos para desenvolver aplicativos para a plataforma iOS da Apple. Apresentando os principais conceitos, os alunos terão uma base sólida para criar aplicações robustas e que atendam as principais demandas do mercado. Os tópicos apresentados no curso focam em problemas reais, cobrindo os requisitos mais solicitados por aplicações desenvolvidas no mercado.

Aguardo vocês lá!

4br4ç05,
nglauber