Mostrando postagens com marcador excluir. Mostrar todas as postagens
Mostrando postagens com marcador excluir. 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

sábado, 4 de dezembro de 2010

Enviando e Recebendo SMS no Android

Olá povo,

Questionar é uma coisa muito sábia. Quando estava dando aula sobre SMS um aluno (Renato se não me engano) questionou meu código, que na verdade estava muito similar (pra não dizer igual) ao código do livro de Ricardo Lecheta. Quando paramos pra analisar vimos como ficou fácil interceptar um SMS.

Primeiro devemos criar um BroadcastReceiver e registrá-lo no AndroidManifest.xml para ser disparado quando a ação "android.provider.Telephony.SMS_RECEIVED" for disparada pelo sistema operacional. Além disso, deve-se utilizar as permissões para enviar e receber SMS.


public class SmsReceiver extends BroadcastReceiver {

public void onReceive(Context context, Intent it){
SmsMessage message =
SMSUtils.getMessagesFromIntent(it)[0];

Toast.makeText(context,
"Mensagem recebida de "+
message.getOriginatingAddress() + " - "+
message.getMessageBody(),
Toast.LENGTH_LONG).show();
}
}


<receiver android:name="SmsReceiver">
<intent-filter>
<action
android:name="android.provider.Telephony.SMS_RECEIVED"/>
<category
android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>

<!-- Adicione essas permissões na tag manifest -->
<uses-permission
android:name="android.permission.RECEIVE_SMS"/>
<uses-permission
android:name="android.permission.SEND_SMS"/>



public class SMSUtils {

public void enviarSms(String numero, String msg) {

SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(numero, null, msg,
null, null);
}

public static SmsMessage[] getMessagesFromIntent(
Intent intent){

Object[] pdusExtras =
(Object[])intent.getSerializableExtra("pdus");

SmsMessage[] messages =
new SmsMessage[pdusExtras.length];

for (int i = 0; i < pdusExtras.length; i++) {
messages[i] = SmsMessage.createFromPdu(
(byte[])pdusExtras[i]);
}
return messages;
}

}// end class


O Android utiliza o formato de mensagens em PDU que permite a leitura de SMS em vários encondings. Por isso, ao receber as mensagens, lemos a informação extra da intent chamada de "pdus". Essa informação é um array de arrays de bytes. Percorremos o array de bytes (que em cada posição tem outro array) e passamos cada array para um método que cria a mensagem SMS. Uma vez com o objeto android.telephony.SmsMessage, podemos obter as informações como telefone de origem e texto da mensagem.

Quando a mensagem é recebida ela vai para a caixa de entrada. Para excluírmos a mensagem recebida, basta usar o content provider da aplicação de SMS conforme explicado nesse post

Se quisermos evitar que a mensagem chegue na aplicação de mensagens, basta colocar uma prioridade (android:priority) maior do que a da aplicação de mensagens no intent-filter do Broadcast que recebe o SMS. E dentro dele, chamar o método abortBroadcast().

4br4ç05,
nglauber