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

segunda-feira, 15 de julho de 2013

ListView com seleção múltipla + ActionBar + ActionMode

Olá povo,

Depois de quase um mês sem postar, resolvi aproveitar o feriadão para colocar um post colaborativo e que me tirou um trauma. Senta que lá vem história...
Um dia desses, meu amigo André Melo (a.k.a. Deco Balaca) me perguntou como fazia uma ListView com seleção múltipla, e que os itens selecionados ficassem com o background diferente. Quatro anos mexendo com o danado do Android e me enrolei pra fazer isso.
A classe ListView tem uma propriedade android:listSelector que nos permite modificar o visual dos itens da lista de acordo com o seu estado (eu falei de selector nesse post aqui).
O problema é que apesar de haver a propriedade android:state_checked (usado também em componentes como Checkbox e ToggleButton), devemos usar a propriedade android:state_activated para indicar que um item da lista está checado (#tenso).
Muito bem! Tudo resolvido? Sim, se você não quiser dar suporte a versões anteriores à 3.0 (Honeycomb), caso contrário você terá que fazer as coisas manualmente.
Meu aluno do TECDAM, Carlos Eduardo Carneiro, me deu essa dica. Abaixo temos um adapter que verifica se a linha está checada, em caso positivo, ele muda o background da View da linha correspondente.
class MultiSelectAdapter extends ArrayAdapter<String>{

  public MultiSelectAdapter(
    Context context, 
    int textViewResourceId,
    List<String> objects) {

    super(context, textViewResourceId, objects);
  }

  @Override
  public View getView(int position, 
    View convertView, ViewGroup parent) {

    View v = super.getView(
      position, convertView, parent);

    ListView listView = (ListView) parent;
    int color = listView.isItemChecked(position) ? 
      Color.argb(0xFF, 0x31, 0xB6, 0xE7) : 
      Color.TRANSPARENT;

    v.setBackgroundColor(color);

    return v;
  }
}
Estou usando um ArrayAdapter simples, mas essa abordagem funcionaria para um adapter customizado também, só que você setaria o background do layout que você carregou...
Resolvido esse problema, resolvi aproveitar esse post para mostrar como utilizar um recurso muito utilizado em aplicações Android: o Action Mode. Essa é uma característica da ActionBar do Android que permite exibir opções de menu de acordo com a necessidade.
Se tornou praticamente padrão, excluir múltiplos itens de uma lista dando um clique longo em um dos itens e depois selecionar outros (podemos ver isso no Gmail, galeria de mídia, etc.). No nosso exemplo, vou mostrar como permitir excluir múltiplos itens de uma ListView. Mas você pode estar se perguntando: ActionBar não é só pro Android 3.0 ou superior? Dê uma olhada nesse link e conheça o Sherlock, pois vamos usá-lo aqui.
O exemplo consta apenas de uma Activity mostrada (uma parte) abaixo:
public class MainActivity 
  extends SherlockActivity 
  implements 
    OnItemClickListener, 
    OnItemLongClickListener, 
    ActionMode.Callback {

  private ListView listView;
  private List<String> nomes;
  private ActionMode actionMode;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setTheme(R.style.Theme_Sherlock_Light_DarkActionBar);

    nomes = new ArrayList<String>();
    nomes.add("nglauber");
    nomes.add("CESAR.edu");
    nomes.add("CESAR");
    nomes.add("Unibratec");

    listView = new ListView(this);
    listView.setOnItemClickListener(this);
    listView.setOnItemLongClickListener(this);
    listView.setAdapter(new MultiSelectAdapter(this,
      android.R.layout.simple_list_item_1, nomes));
      setContentView(listView);
  }

  @Override
  public void onItemClick(
    AdapterView<?> adapterView, View view,

    int position, long id) {
    if (actionMode == null) {
      // Faça algo ao clicar no item normalmente
    } else {
      int checkedCount =  
        atualizarItensMarcados(listView, position);

      if (checkedCount == 0) {
        actionMode.finish();
      }
    }
  }

  @Override
  public boolean onItemLongClick(
    AdapterView<?> adapterView, View view,
    int position, long id) {

    boolean consumed = (actionMode == null);

    if (consumed) {
      actionMode = startActionMode(this);
      listView.setChoiceMode(
        ListView.CHOICE_MODE_MULTIPLE);

      listView.setItemChecked(position, true);
      atualizarItensMarcados(listView, position);
    }
    return consumed;
  }
A nossa classe implementa três interfaces, onde as duas primeiras são bem conhecidas. A última é que vamos detalhar mais no próximo trecho de código. O atributo actionMode é que vai exibir a opção de excluir e mostrar quantos itens estão selecionados. Esse atributo é inicializado no método onItemLongClick através da chamada do método startActionMode. Nesse momento, habilitamos a seleção múltipla na ListView e já checamos o item da posição onde clicamos.
Sendo assim, no método onItemClick, nós verificamos se actionMode é igual a null, nesse caso, não estamos com o ActionMode ativo na ActionBar e devemos processar o clique normalmente. Caso contrário, atualizamos a lista com novo item que foi clicado. Mas se não houver mais itens selecionados, desativamos o ActionMode chamando o método finish.

Vamos agora ver o código da interface ActionMode.Callback e o método atualizarItensMarcados.
  @Override
  public boolean onCreateActionMode(
    ActionMode mode, Menu menu) {

    getSupportMenuInflater().inflate(
      R.menu.menu_delete_list, menu);

    return true;
  }

  @Override
  public boolean onPrepareActionMode(
    ActionMode mode, Menu menu) {

    return false;
  }

  @Override
  public boolean onActionItemClicked(
    ActionMode mode, MenuItem item) {

    if (item.getItemId() == R.id.action_delete) {
      SparseBooleanArray checked = 
        listView.getCheckedItemPositions();

      for (int i = checked.size()-1; i >= 0; i--) {
        if (checked.valueAt(i)) {
          nomes.remove(checked.keyAt(i));
        }
      }
      actionMode.finish();
      return true;
    }
    return false;
  }

  @Override
  public void onDestroyActionMode(ActionMode mode) {
    actionMode = null;
    listView.clearChoices();
    ((BaseAdapter) listView.getAdapter())
      .notifyDataSetChanged();

    listView.setChoiceMode(ListView.CHOICE_MODE_NONE);
  }

  private int atualizarItensMarcados(
    ListView l, int position) {

    SparseBooleanArray checked = 
      l.getCheckedItemPositions();

    l.setItemChecked(position, 
      l.isItemChecked(position));

    int checkedCount = 0;
    for (int i = 0; i < checked.size(); i++) {
      if (checked.valueAt(i)) {
        checkedCount++;
      }
    }

    actionMode.setTitle(
      checkedCount + " selecionados");

    return checkedCount;
  }
}
O método onCreateActionMode permite carregar um arquivo de menu para actionMode, note que ele retorna true para informar que o actionMode pode ser criado. Já o método onPrepareActionMode é usado para quando queremos atualizar o actionMode após sua criação, retornamos falso para indicar que ele não foi atualizado. Quando uma opção de menu do ActionMode é selecionada, o método onActionItemClicked é chamado. Nesse método, checamos se a opção selecionada foi a de excluir, em caso positivo, obtemos a lista das posições da lista que estão marcadas. Removemos essas posições da lista e depois finalizamos o ActionMode.
Ao chamar o método finish do ActionMode, o método onDestroyActionMode é chamado. Nesse momento, setamos o atributo actionMode para null, desmarcamos os itens (caso haja algum), chamamos o notifyDatasetChanged para que a ListView seja redesenhada, e por fim, voltamos o tipo de seleção da lista como nenhum.
O método atualizarItensMarcados apenas retorna a quantidade de itens que estão checados e seta o título da actionMode.

O arquivo de menu utilizado no método onCreateActionMode é mostrado abaixo:
<menu 
  xmlns:android="http://schemas.android.com/apk/res/android">
  <item
    android:id="@+id/action_delete"
    android:icon="@android:drawable/ic_menu_delete"
    android:orderInCategory="100"
    android:showAsAction="ifRoom"/>
</menu>

E o resultado, podemos ver na figura a seguir:

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

quinta-feira, 24 de março de 2011

Adapter Eficiente no Android

Olá povo,

Para preencher diversos componentes de UI como: ListView, Spinner (combo box) e GridView, o Android utiliza o conceito de Adapters. Esse é um padrão de projeto muito útil, e serve (como o próprio nome diz) para adaptar duas interfaces incompatíveis. No caso do Android, esse padrão serve para adaptar uma lista de objetos para uma lista elementos de interface gráfica, como as linhas de uma ListView.
Por exemplo, imagine uma lista de objetos da classe Pessoa, e você quer exibi-los na tela. O Android, através da classe ListView (que é um componente que exibe os componentes em forma de lista) solicita, para cada linha da lista um novo objeto View (que é a classe mãe de todos os elementos visuais do Android). Cabe ao Adapter, criar um objeto View que represente visualmente o objeto da posição específica da lista de objetos. Vamos detalhar mais como isso funciona na prática.
Crie um novo projeto no Eclipse marcando a opção para criar uma activity e nomeando-a como TelaListaActivity. Antes de mexer na Activity vamos criar a classe de objetos que queremos listar. No nosso caso, será a classe Pessoa que citamos acima.
public class Pessoa {
  private long id;
  private String nome;
  private String estado;

  public Pessoa(long id, String nome, String estado) {
     this.id = id;
     this.nome = nome;
     this.estado = estado;
  }

  public long getId() {
     return id;
  }
  public void setId(long id) {
     this.id = id;
  }
  public String getNome() {
     return nome;
  }
  public void setNome(String nome) {
     this.nome = nome;
  }
  public String getEstado() {
     return estado;
  }
  public void setEstado(String estado) {
     this.estado = estado;
  }
}

A classe acima não passa de um POJO (Plain Old Java Object), ou seja, apenas uma classe com métodos gets e sets. Agora vamos criar o arquivo de layout que representará cada linha a ser exibida na ListView. Crie o arquivo linha.xml dentro do diretório res/layout e deixe-o conforme abaixo:
<LinearLayout android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
   android:id="@+id/textView1"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:textsize="26dp"
   android:textcolor="#00FF00"/>
<TextView
   android:id="@+id/textView2"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
</LinearLayout>

Nosso layout também é bem simples. Consta apenas de um LinearLayout com dois TextViews, onde deixamos primeiro com o texto um pouco maior que o segundo e com a cor verde. Vamos agora criar nosso adaptador. Crie uma nova classe chamada PessoaAdapter e deixe-a conforme abaixo.
public class PessoaAdapter extends BaseAdapter {

   private Context ctx;
   private List<Pessoa> pessoas;

   public PessoaAdapter(Context ctx, 
      List <Pessoa> pessoas) {
      this.ctx = ctx;
      this.pessoas = pessoas;
   }

   @Override
   public int getCount() {
      return pessoas.size();
   }

   @Override
   public Object getItem(int position) {
      return pessoas.get(position);
   }

   @Override
   public long getItemId(int position) {
      return pessoas.get(position).getId();
   }

   @Override
   public View getView(int position, View convertView,
      ViewGroup parent) {
      
      Pessoa p = pessoas.get(position);

      View v = LayoutInflater.from(ctx).inflate(
         R.layout.linha, null);

      TextView txt1 = (TextView) v.findViewById(
         R.id.textView1);
      TextView txt2 = (TextView) v.findViewById(
         R.id.textView2);

      txt1.setText(p.getNome());
      txt2.setText(p.getEstado());

      return v;
   }
}

Nossa classe herda BaseAdapter e tem dois atributos que são inicializados no construtor da classe. O atributo da classe Context será utilizado para carregar o arquivo de layout que acabamos de criar. Já o segundo é a lista de objetos da classe Pessoas que queremos adaptar.
Quando criamos um adapter, devemos implementar quatro métodos: getCount, getItem, getItemId e getView. O primeiro retorna a quantidade de linhas que o adaptador representa, se estamos adaptando a lista de pessoas, basta retornar o tamanho da lista de pessoas.
O método getItem serve para acessarmos o objeto que o adaptador está representando. No nosso caso, basta retornar o objeto da posição da lista.
Já o método getItemId serve pra retornar um identificador do objeto da posição passada como parâmetro. Esse método é opcional, mas como temos um atributo chamado id na classe pessoa, ele passa a ser um bom candidato. Então obtemos o objeto da classe Pessoa da posição especificada pelo parâmetro e retornamos o seu id.
O último método é o mais importante. É ele que vai pegar um objeto da lista de pessoas, carregar o arquivo de layout e atribuir os valores dos atributos do objeto pessoa para a view que representará a linha da lista.
Para carregar o arquivo de layout, utilizamos a classe LayoutInflater. Uma vez que esse layout é carregado, é retornado um objeto View que representa a árvore de Views definida no arquivo XML, que no nosso caso, tem um LinearLayout e dentro dele, dois TextViews. Uma vez de posse desse objeto View, obtemos as referências para os TextViews para podemos passar os valores dos atributos da classe Pessoa para eles. No final retornamos a View que carregamos e modificamos.

Vamos agora para atividade da aplicação. Nela vamos criar uma lista de objetos pessoa, criar o uma instância do nosso adapter e defini-lo como "preenchedor" da lista.
public class TelaListaActivity extends ListActivity {

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

      ArrayList<Pessoa> pessoas = 
         new ArrayList<Pessoa>();

      pessoas.add(
         new Pessoa(1, "Nelson", "Pernambuco"));
      pessoas.add(
         new Pessoa(2, "Glauber", "São Paulo"));
      pessoas.add(
         new Pessoa(3, "José", "Minas Gerais"));
      pessoas.add(
         new Pessoa(4, "João", "Rio de Janeiro"));
      pessoas.add(
         new Pessoa(5, "Silvio", "Pernambuco"));
      pessoas.add(
         new Pessoa(6, "Marta", "Pernambuco"));
 
      PessoaAdapter adapter = 
         new PessoaAdapter(this, pessoas);

      setListAdapter(adapter);
   }
}

Se executarmos nossa aplicação teremos um resultado similar à figura abaixo:

Muito bom! Nossa lista de objetos pessoa foi exibido perfeitamente na ListView que é criada automaticamente quando herdamos da classe ListActivity. Mas vamos recaptular um pouco... O método getView é chamado para cada linha da linha. Então se tivermos 10000 linhas na lista carregaremos 10000 layouts? Pois é, e isso não é bom. Mas não se desespere, os engenheiros da Google criaram uma forma padrão de otimizar essas listas. Altere o método getView para que fique conforme abaixo:
@Override
public View getView(int position, View convertView, 
   ViewGroup parent) {
  
   ViewHolder holder;

   Pessoa p = pessoas.get(position);

   if (convertView == null){
      convertView = LayoutInflater.from(ctx).inflate(
         R.layout.linha, null);
 
      holder = new ViewHolder();
      holder.txtNome = (TextView) 
         convertView.findViewById(R.id.textView1);
      holder.txtEstado = (TextView) 
         convertView.findViewById(R.id.textView2);
      convertView.setTag(holder);
   } else {
      holder = (ViewHolder)convertView.getTag();
   }
   holder.txtNome.setText(p.getNome());
   holder.txtEstado.setText(p.getEstado());

   return convertView;
}

static class ViewHolder {
   TextView txtNome;
   TextView txtEstado;
}

A classe ViewHolder vai manter as referências para a Views filhas para evitar chamadas desnecessárias ao método findViewById para cada linha. O parâmetro convertView representa uma linha pode ser reusada, uma vez que nem todas as linhas podem estar sendo vistas na tela. Quando convertView é diferente null, podemos reusá-la, então não será necessário carregar uma nova instância do layout. Se ela for nula, carregamos o layout e instanciamos um novo ViewHolder para armazenar as Views que iremos alterar, e em seguida setamos esse ViewHolder na propriedade tag (que pode armazenar qualquer objeto) da convertView.

O resultado para o número de registros que usamos não é aparente, mas tente adicionar 10000 objetos rodar a lista com o primeiro e o segundo exemplo :) Façam esse teste usando a instrução abaixo pra alimentar a lista;

for (int i = 0; i < 10000; i++) {
   pessoas.add(new Pessoa(i, "Pessoa "+ i, "Acre"));  
}


Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

Fonte: Android Developers e Google IO 2010

quinta-feira, 2 de dezembro de 2010

Android: Dicas de programação da semana

Olá povo,

No novo projeto em que estou envolvido estamos desenvolvendo várias aplicações. Como temos várias pessoas envolvidas, algumas dúvidas se tornam recorrentes, então resolvi postá-las.

Pergunta 1: Quando eu giro o telefone o método onCreate da Activity é chamado novamente. Tem como evitar isso?

Resposta: Sim, basta adicionar a propriedade android:configChanges na tag <activity> do seu AndroidManifest.xml. Isso evitará que a atividade seja recriada.

<activity android:name="MinhaActivity"
android:configChanges="keyboardHidden|orientation"/>

No caso acima, se o telefone mudar de orientação ou se o teclado físico (se houver) for aberto, a atividade não será recriada.

Pergunta 2: Tem como deixar uma atividade apenas em landscape ou portrait?

Resposta: Sim, basta utilizar a propriedade android:screenOrientation na tag <activity> do seu AndroidManifest.xml.

<activity name=".MinhaActivity"
android:screenOrientation="landscape"/>


Pergunta 3: Como permitir apenas letras na caixa de texto (EditText)?

Resposta: Não existe uma propriedade para fazer isso. Se quisermos colocar para aceitar apenas números podemos utilizar a propriedade android:inputType="number". Ou quando queremos deixar todas as letras em maiúsculo podemos usar o valor textCapCharacters. Mas para não aceitar nem números nem símbolos só com a boa e velha expressão regular.

Pattern pattern = Pattern.compile(
"^[A-ZÁÀÄÃÂÆÅÉÈËÊÍÌÏÎÓÒÖÕÔØŒÚÙÜÛÝÿYÑÇ ]+$");

Matcher matcher = pattern.matcher(
meuEditText.getText().toString().toUpperCase());

if (!matcher.matches()){
Log.e("ERRO",
"Esse campo só aceita letras e espaço em branco!");
}


Pergunta 4: Como fazer uma lista múltipla que eu possa selecionar vários itens?

Resposta: Devo confessar que esse problema me encheu muito o saco. Não para criar a lista, mas sim para obter os itens selecionados. Pra informar que sua ListView permite seleção múltipla use o método setChoiceMode(int).

lstView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);

Feito isso, sua lista já permitirá seleção múltipla. Agora como fazer para obter os itens selecionados? Bem, a classe ListView tem dois métodos que você pode obter arrays com as posições e os ids dos itens selecionados: getCheckItemPositions() e getCheckItemIds(). Infelizmente não tive uma boa experiência com eles. Ao marcar e desmarcar itens, aconteciam momentos em que os itens ficavam inconsistentes, ou seja, ele informava que itens estavam marcados, quando na verdade não estavam. Sendo assim, preferi fazer da minha forma:

private int getCheckedCount(){
lstView.getCheckItemIds();

int count = 0;
int listSize = lstView.getAdapter().getCount();
for (int i = 0; i < listSize; i++) {
if (lstView.isItemChecked(i)){
count++;
}
}
return count;
}

// Para marcar ou desmarcar um item específico,
// podemos usar:
lstView.setItemChecked(posicao, false);


Pergunta 5. Como carregar uma imagem da aplicação baseada no nome do arquivo?

Resposta: Se esse recurso estiver na pasta res/drawable você pode fazer via reflection. Eu já fiz um post sobre isso, que vocês podem conferir aqui. Mas se quiser usar de maneira descente :) podem usar a pasta assets do seu projeto. Coloque as imagens que você deseja carregar dinamicamente nesse diretório e faça como abaixo:

try {
InputStream is = getAssets().open("imagem.png");
Bitmap bmp = BitmapFactory.decodeStream(is);
ImageView img = (ImageView)
findViewById(R.id.ImageView01);

img.setImageBitmap(bmp);

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


Pergunta 6. Como salvar uma informação simples como o recorde do jogo?

Resposta: Para persistir informações simples, o Android disponibiliza a classe SharedPreferences onde você pode salvar pequenas configurações. Vejam abaixo como ler/escrever uma preferência através de dois métodos de exemplo que salvam e recuperam o recorde de pontos de um jogo.

public int getRecord(){
SharedPreferences prefs =
context.getSharedPreferences(
"configs", Context.MODE_PRIVATE);

return prefs.getInt("record", 0);
}

public void setRecord(int record){
SharedPreferences prefs =
context.getSharedPreferences(
"configs", Context.MODE_PRIVATE);

SharedPreferences.Editor editor = prefs.edit();
editor.putInt("record", record);
editor.commit();
}


Pergunta 7. Como ler um XML do diretório res/xml?

Resposta: Quem é meu aluno já conhece essa frase que eu uso sempre nas aulas: Existem mil maneiras de se fazer Neston :) Uma das maneiras de ler um XML é usando o XMLResourceParser, segue um exemplo bem simples do seu uso. Clique aqui pra ver o post original.


private String getEventsFromAnXML(Context c)
throws XmlPullParserException, IOException {

StringBuffer stringBuffer = new StringBuffer();
Resources res = c.getResources();
XmlResourceParser xpp = res.getXml(R.xml.meu_xml);
xpp.next();

int eventType = xpp.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT){
if(eventType == XmlPullParser.START_DOCUMENT) {
stringBuffer.append("--- Start XML ---");

} else if(eventType == XmlPullParser.START_TAG){
stringBuffer.append("\nSTART_TAG: "+
xpp.getName());

} else if(eventType == XmlPullParser.END_TAG){
stringBuffer.append("\nEND_TAG: "+
xpp.getName());

} else if(eventType == XmlPullParser.TEXT) {
stringBuffer.append("\nTEXT: "+xpp.getText());
}
eventType = xpp.next();
}
stringBuffer.append("\n--- End XML ---");
return stringBuffer.toString();
}

De acordo com a evolução do projeto vou colocando mais dicas.

4br4ç05,
nglauber

terça-feira, 27 de julho de 2010

Utilização de Estilos no Android - Parte 2

Olá povo,

Dando continuidade ao exemplo de utilização de estilos no Android, vou mostrar como modificar o visual de alguns componentes. Todos os elementos visuais que são selecionáveis ou clicáveis podem utilizar o recurso <selector> que permite modificar a aparência do componente, indicando quais imagens serão utilizadas para cada estado do componente. Essas imagens são definidas em um arquivo XML que deve ficar dentro da pasta res/drawable. Isso mesmo, a pasta drawable não armazena só imagens.
Vamos criar um selector que modificará as imagens de um RadioButton. Para isso, crie o arquivo res/layout/meuradiobutton.xml que deve ficar como abaixo:

<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_window_focused="false"
android:state_checked="false"
android:drawable="@drawable/img_radio_desmarcado" />
<item
android:state_window_focused="true"
android:state_checked="true"
android:drawable="@drawable/img_radio_marcado" />
</selector>

No exemplo acima, dentro da tag <selector> definimos as imagens utilizadas quando o componente estiver marcado (checked) e desmarcado. Lembrando que as imagens "img_radio_marcado" e "img_radio_desmarcado" também devem estar dentro da pasta res/drawable.

Agora devemos aplicar o selector ao RadioButton. Para utilizar o selector, basta utilizar a propriedade android:button.


<RadioButton
android:checked="false"
android:button="@drawable/meuradiobutton"
android:layout_width="wrap_content"
android:layout_heighth="wrap_content"
android:id="@+id/radioButton1"
android:text="Fácil" />


Podemos observar o resultado na figura abaixo:

Um outra característica legal de modificar é a cor de seleção de Grid e Lista. A idéia é a mesma, criamos um selector e o associamos ao GridView ou ao ListView.

<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<!--Imagem do item da lista clicado -->
<item
android:state_pressed="true"
android:drawable="@drawable/img_clicado" />
<!--Imagem do item da lista selecionado sem foco -->
<item
android:state_focused="false"
android:drawable="@drawable/img_selecionado" />
<!--Imagem do item da lista selecionado com foco -->
<item
android:state_window_focused="true"
android:drawable="@drawable/img_selecionado" />
<!--Imagem do item da lista não selecionado -->
<item
android:state_window_focused="false"
android:drawable="@android:color/transparent" />
</selector>


Para esse exemplo temos uma imagem para quando item da lista está selecionado (navegando com o direcional, se o telefone possuir) e outra pra quando o item for clicado. Para aplicar o selector à lista, altere a propriedade listSelector.

<ListView
android:id="@+id/listView1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:listSelector="@drawable/meuselector"/>


O resultado é apresentado na figira abaixo.



Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber