sábado, 26 de outubro de 2013

nglauber no Google Play

Olá povo,

Depois de anos enrolando, finalmente postei a aplicação daqui do blog no Google Play. A aplicação é gratuita e compatível com dispositivos Android 2.2 ou superior. Nela, você pode ver todos os posts daqui blog em seu smartphone e/ou tablet e ter um acesso rápido e fácil aos seus posts favoritos.

Durante o desenvolvimento da aplicação surgiram alguns desafios interessantes e que podem gerar muito conteúdo aqui pro blog. Sendo assim, como essa é a primeira versão, vou aguardar alguns feedbacks de vocês aqui pelo blog (e pelo Google Play também, claro) e assim que tiver uma versão bem estável, disponibilizarei o código ;) Acho que isso ajudará o pessoal que está começando e também aqueles que já têm experiência com Android.

Então é isso pessoal! Baixem a aplicação clicando na imagem abaixo, testem e mandem suas sugestões e bugs encontrados. Testei pouco a aplicação, então vocês devem achar vários bugs :( Mas podem reportar sem problema. Aí vou corrigindo a medida do possível.


Ah! Se algum designer caridoso quiser ajudar com o visual da aplicação, eu agradeço (mas não pago nada, digo logo :)

Abaixo coloquei dois screenshots da app.


4br4ç05,
nglauber

sexta-feira, 11 de outubro de 2013

Adeus AlertDialog! Bem-vindo DialogFragment

Olá povo,

Até a versão 2.3 do Android (API Level 10), a forma padrão de exibir mensagens para os usuários era através da classes AlertDialog, mas partir da versão 3 do Android (Honeycomb API Level 11) uma nova abordagem foi adotada, a utilização da classe DialogFragment.
Apesar de ter sido lançada na versão 3, a API de compatibilidade do Android nos permite usar esse recurso em versões anteriores. Umas das vantagens dessa abordagem é o maior controle sobre o ciclo de vida do Dialog, uma vez que ele nada mais é do que um Fragment.
Apenas para relembrar, até o 2.3 fazíamos isso:

int x = 0;
 
@Override
protected Dialog onCreateDialog(int id) {
  AlertDialog dialog = new AlertDialog.Builder(this)
    .setTitle("Título")
    .setMessage("Deseja exibir "+ x++)
    .setPositiveButton("Sim", null) // Listener aqui
    .setNegativeButton("Não", null) // e aqui
    .create();
  return dialog;
}
 
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
  ((AlertDialog)dialog).setMessage("Mensagem "+ x++);
  super.onPrepareDialog(id, dialog);
}
 
public void abrirAlert(View v){
  showDialog(0);
}
A abordagem acima era a recomendada pois, se abríssimos um dialog e girássemos a tela, o mesmo era perdido. No método onCreateDialog, é criado o dialog (dããã), mas se precisássemos alterar o texto mudando apenas um detalhe (no exemplo acima, o valor de x) deveríamos usar o método onPrepareDialog conforme acima. Todos os dialogs da Acvity deviam ser criados no onCreateDialog e cada um tem o seu ID que é recebido por parâmetro. Para exibir o dito-cujo, era só chamar showDialog(ID).

A classe DialogFragment envolve (wraps) um AlertDialog, ou pode ter qualquer view. Podemos dizer que seria como um "Fragment Modal". No exemplo abaixo, temos um exemplo de um DialogFragment que recebe um título, uma mensagem e os (de 1 a 3) botões.

import android.app.AlertDialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;

public class SimpleDialog extends DialogFragment 
  implements OnClickListener {
 
  private static final 
    String EXTRA_ID      = "id";
  private static final 
    String EXTRA_MESSAGE = "message";
  private static final 
    String EXTRA_TITLE   = "title";
  private static final 
    String EXTRA_BUTTONS = "buttons";
  private static final 
    String DIALOG_TAG    = "SimpleDialog";
 
  private int dialogId;
 
  public static SimpleDialog newDialog(int id, 
    String title, String message, int[] buttonTexts){
    // Usando o Bundle para salvar o estado
    Bundle bundle  = new Bundle();
    bundle.putInt(EXTRA_ID, id);
    bundle.putString(EXTRA_TITLE, title); 
    bundle.putString(EXTRA_MESSAGE, message);
    bundle.putIntArray(EXTRA_BUTTONS, buttonTexts);
  
    SimpleDialog dialog = new SimpleDialog();
    dialog.setArguments(bundle);
    return dialog; 
  }
 
  @Override
  public Dialog onCreateDialog(
    Bundle savedInstanceState) {

    String title = getArguments()
      .getString(EXTRA_TITLE);
    String message = getArguments()
      .getString(EXTRA_MESSAGE);
    int[] buttons = getArguments()
      .getIntArray(EXTRA_BUTTONS);
     
    AlertDialog.Builder alertDialogBuilder = 
      new AlertDialog.Builder(getActivity());
    alertDialogBuilder.setTitle(title);
    alertDialogBuilder.setMessage(message);
        
    switch (buttons.length) {
      case 3:
        alertDialogBuilder.setNeutralButton(
          buttons[2], this);

      case 2:
        alertDialogBuilder.setNegativeButton(
          buttons[1], this);
   
      case 1:
        alertDialogBuilder.setPositiveButton(
          buttons[0], this);
    }    
    return alertDialogBuilder.create();
  }
    
  @Override
  public void onClick(
    DialogInterface dialog, int which) {
    // Sua Activity deve implementar essa interface
    ((FragmentDialogInterface)getActivity())
      .onClick(dialogId, which);
  }

  public void openDialog(
    FragmentManager supportFragmentManager) {

    if (supportFragmentManager.findFragmentByTag(
      DIALOG_TAG) == null){

      show(supportFragmentManager, DIALOG_TAG);
    }  
  }
  // Interface que erá chamada ao clicar no bot"ao
  public interface FragmentDialogInterface {
    void onClick(int id, int which);
  }
}
Podemos notar no código acima que estamos herdando da classe DialogFragment. Criei um método estático para inicializar o Dialog, lembrando que essa é uma boa prática uma vez que sempre devemos ter o construtor padrão e o Bundle armazenará o estado do Fragment. No método onCreateDialog, inicializamos o AlertDialog com os dados passados no "método construtor".
Nossa Activity deve implementar essa interface, pois não podemos manter a referência dela, uma vez que ela pode ter sido destruída ao girar o aparelho. Para abrir o dialog, verificamos se ele já foi adicionado ao FragmentManager, caso contrário, exibimos.
Pra exibir o dialog, basta fazer conforme abaixo:
public void abrirSimpleDialog(View v) {
  SimpleDialog dialog = SimpleDialog.newDialog(
    0,              // Id do dialog
    "Alerta",       // título
    "Mensagem",     // mensagem
    new int[]{      // texto dos botões
      android.R.string.ok, 
      android.R.string.cancel });
    dialog.openDialog(getSupportFragmentManager());
  }

  @Override
  public void onClick(int id, int which) {
    Toast.makeText(MainActivity.this, 
      "Botão clicado "+ which, Toast.LENGTH_SHORT)
        .show();
  }
Agora se quisermos um Dialog customizado, para por exemplo, receber input do usuário, podemos criar um arquivo de layout e associá-lo ao DialogFragment.
 
<LinearLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/edtName"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_gravity="center"
  android:orientation="vertical"
  android:padding="16dp" >

  <TextView
    android:id="@+id/lbl_your_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Your name" />

  <EditText
    android:id="@+id/txt_your_name"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:imeOptions="actionDone"
    android:inputType="text" />

</LinearLayout>
O Layout é simple e contém apenas um TextView e um EditText. Vamos ver como ficará o DialogFragment que usaria esse layout.
public class EditNameDialog 
  extends DialogFragment 
  implements OnEditorActionListener {

  private static final 
    String DIALOG_TAG = "editDialog";
  private static final 
    String EXTRA_INPUT_TEXT = "message";
  private static final 
    String EXTRA_TITLE = "inputText";

  private EditText mEditText;

  // Sua Activity deve implementar essa Interface
  public interface EditNameDialogListener {
    void onFinishEditDialog(String inputText);
  }

  public static EditNameDialog newInstance(
    String title, String inputText) {

    Bundle bundle = new Bundle();
    bundle.putString(EXTRA_TITLE, title);
    bundle.putString(EXTRA_INPUT_TEXT, inputText);

    EditNameDialog dialog = new EditNameDialog();
    dialog.setArguments(bundle);
    return dialog;
  }

  @Override
  public View onCreateView(LayoutInflater inflater, 
    ViewGroup container, Bundle savedInstanceState){

    String title = getArguments()
      .getString(EXTRA_TITLE);
    String inputText = getArguments()
      .getString(EXTRA_INPUT_TEXT);

    View view = inflater.inflate(
      R.layout.layout_dialog, container);

    TextView txtView = (TextView)
      view.findViewById(R.id.lbl_your_name);
    txtView.setText(inputText);
    // Exibe o teclado virtual ao exibir o Dialog
    getDialog().getWindow().setSoftInputMode(
      WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);

    getDialog().setTitle(title);

    mEditText = (EditText)
      view.findViewById(R.id.txt_your_name);
    mEditText.requestFocus();
    // Listener para quando clicarmos 
    // em 'Done' no teclado
    mEditText.setOnEditorActionListener(this);

    return view;
  }

  @Override
  public boolean onEditorAction(TextView v, 
    int actionId, KeyEvent event) {
    // Se clicou em 'Done'
    if (EditorInfo.IME_ACTION_DONE == actionId) {
      // Notifique a Activity
      EditNameDialogListener activity = 
        (EditNameDialogListener) getActivity();
      activity.onFinishEditDialog(
        mEditText.getText().toString());
      // Feche o dialog
      dismiss();
      return true;
    }
    return false;
  }

  public void openDialog(FragmentManager fm) {
    if (fm.findFragmentByTag(DIALOG_TAG) == null) {
      show(fm, DIALOG_TAG);
    }
  }
}
Para exibir e tratar esse dialog usamos o código abaixo:
public void abrirEditDialog(View v){
  EditNameDialog editNameDialog = 
    EditNameDialog.newInstance(
      "Informação", "Digite seu nome");

  editNameDialog.openDialog(
    getSupportFragmentManager());
}
 
@Override
public void onFinishEditDialog(
  String inputText) {

  Toast.makeText(this, "Olá, " + inputText, 
    Toast.LENGTH_SHORT).show();  
}
O resultado ficará conforme a figura.


[EDITADO em (11/10/2013 às 11:20)]
Como você devem ter notado, em ambos os exemplo estamos usando a Activity para tratar o retorno do Dialog. Mas e se quisermos chamar o Dialog a partir de um Fragment?
Nesse caso podemos utilizar a propriedade Target Fragment. Ela funciona como o startActivityForResult, só que para Fragments.
// A partir de um Fragment
public void abrirSimpleDialog() {
  SimpleDialog dialog = SimpleDialog.newDialog(
    0, // Id do dialog
    "Alerta", // título
    "Mensagem", // mensagem
    new int[] { // texto dos botões
      android.R.string.ok, 
      android.R.string.cancel });
  // Segredo do sucesso! :)
  // 1 = RequestCode
  dialog.setTargetFragment(this, 1); 
  dialog.openDialog(
    getActivity().getSupportFragmentManager());
}

@Override
public void onActivityResult(int requestCode, 
  int resultCode, Intent data) {

  super.onActivityResult(
    requestCode, resultCode, data);

  int which = data.getIntExtra("which", -1);
  // Tratar dialog  
}
Com essa alteração, a interface FragmentDialogInterface pode ser eliminada (uma vez que a Activity não precisa mais implementá-la), e o código do onClick da classe SimpleDialog, ficaria dessa forma:
@Override
public void onClick(DialogInterface dialog, int which) {
  Intent it = new Intent();
  it.putExtra("which", which);
  // Chamando o onActivityResult do targetFragment
  getTargetFragment().onActivityResult(
    getTargetRequestCode(), Activity.RESULT_OK, it);
}

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

segunda-feira, 9 de setembro de 2013

O que é ser um programador?

Olá povo,

Faz tempo que não posto nada aqui pois estou muito ocupado com minhas aulas de Android e iOS. Mas meu amigo Daniel Ferreira, postou no Twitter um ótimo vídeo produzido pelo site code.org que vale esse post. Com depoimentos de grandes nomes da computação como Bill Gates e Mark Zuckenberg, o vídeo mostra o porquê, e quão legal é ser um programador. Ele também enfatiza a importância que todos nós saibamos programar um computador :)



4br4ç05,
nglauber

quarta-feira, 31 de julho de 2013

ActionBar na API de Compatibilidade

Olá povo,

No último 18/07 a Google finalmente adicionou o suporte à ActionBar na API de compatibilidade. Pelo menos para API v7 (Android 2.1 ou superior). Para usá-la, vá até o SDK Manager e atualize a API de compatibilidade marcando a opção Extras > Android Support Library.



Feito isso, vá até o Eclipse e importe o projeto appcompat disponível no diretório SDK_DIR/extras/android/support/v7. Crie um novo projeto e referencie o projeto importado anteriormente clicando com o botão direito sobre o projeto e selecionando Properties.  Em seguida, selecione a opção Android no lado esquerdo e na parte inferior, clique em Add. Selecione o projeto android-support-v7-appcompat. Agora apague o arquivo android-support-v4.jar da pasta lib do projeto, pois o projeto que acabamos de importar já tem esse arquivo.

Nossa Activity agora herdará ActionBarActivity (que herda de FragmentActivity).
import android.support.v7.app.ActionBarActivity;

public class MainActivity extends ActionBarActivity {
  // Implementação normal da Activity
}

Obrigatoriamente, nossa Activity tem que ter o estilo da R.style.Theme.AppCompat. Para tal, vá até o arquivo values/styles.xml e modifique o tema da aplicação conforme abaixo.

<style name="AppBaseTheme" 
  parent="@style/Theme.AppCompat">

Pronto! Agora é só usar a ActionBar como se estivesse no Android 3.0 ou superior :)

EDITADO em 05/08/2013

Para usar a SearchView nesse nova biblioteca, adicione o arquivo res/menu/main.xml.
<menu 
  xmlns:android="http://schemas.android.com/apk/res/android" 
  xmlns:suaapp="http://schemas.android.com/apk/res-auto">

  <item
    android:id="@+id/action_settings"
    android:orderInCategory="100"
    android:showAsAction="always|withText"
    android:icon="@android:drawable/ic_menu_info_details"
    android:title="@string/action_settings"/>

  <item android:id="@+id/search"
    android:title="Search"
    android:icon="@android:drawable/ic_menu_search"
    suaapp:showAsAction="collapseActionView|ifRoom"
    suaapp:actionViewClass="android.support.v7.widget.SearchView" />
</menu>
Agora deixe o código da sua Activity como abaixo:
public class MainActivity extends ActionBarActivity 
  implements SearchView.OnQueryTextListener {

  private MenuItem mSearchItem;
  private SearchView mSearchView;
  private TextView mTextTeste;
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mTextTeste =
     (TextView)findViewById(R.id.textView1);
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.main, menu);
  
    mSearchItem = menu.findItem(R.id.search); 
    mSearchView = (SearchView) 
      MenuItemCompat.getActionView(mSearchItem); 
    mSearchView.setOnQueryTextListener(this);
  
    return true;
  }

  @Override
  public boolean onQueryTextChange(String text) {
    mTextTeste.setText(text);
    return false;
  }

  @Override
  public boolean onQueryTextSubmit(String text) {
    mTextTeste.setText(text +" GO!");
    MenuItemCompat.collapseActionView(mSearchItem);
    return true;
  }
}


4br4ç05,
nglauber

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

quarta-feira, 19 de junho de 2013

SlidingPaneLayout: um menu lateral estilo Facebook

Olá povo,

Uma das coisas boas de mexer a muito tempo com uma tecnologia, é que as pessoas sempre perguntam a você, mas obviamente, nem sempre você sabe a resposta. Meu amigo Rodrigo Jardim (a.k.a. Praieiro), me perguntou se eu conhecia o SlidingPaneLayout, e eu nunca tinha visto o dito cujo. Mas ele permite implementarmos um padrão de interface gráfica amplamente utilizada nas aplicações Android: menus laterais. Abaixo, coloquei um screenshot de duas "pequenas" aplicações que usam essa abordagem.


Como podemos notar, o Facebook e o GMail são dois bons exemplos de grandes aplicações que usam essa abordagem. Nesse post vou mostrar como dar os primeiros passos para construir um menu desse tipo. Vamos começar pelo arquivo de layout da aplicação, mostrado abaixo.
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SlidingPaneLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/sliding_pane_layout"
  android:layout_width="match_parent"
  android:layout_height="match_parent" >

  <!-- Menu Lateral -->
  <ListView
    android:id="@+id/left_pane"
    android:layout_width="280dp"
    android:layout_height="match_parent"
    android:layout_gravity="left" />
    
  <!-- Conteúdo da tela -->
  <RelativeLayout
    android:id="@+id/rightPane"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ff333333" >

    <Button
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentLeft="true"
      android:layout_alignParentTop="true"
      android:text="MENU" 
      android:onClick="abrirMenu"/>

    <ImageView
      android:id="@+id/imageView1"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:src="@drawable/ic_launcher" />

  </RelativeLayout>

</android.support.v4.widget.SlidingPaneLayout>
No SlidingPanelLayout temos apenas duas partes, a primeira (uma ListView) será o menu da aplicação. Enquanto que a segunda, será o conteúdo da tela em si. Abaixo temos o código da Activity.
public class MainActivity extends FragmentActivity 
  implements OnItemClickListener, PanelSlideListener {

  private SlidingPaneLayout mSlidingLayout;
  private ListView mList;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Colocando a Activity em tela cheia (opcional)
    requestWindowFeature(Window.FEATURE_NO_TITLE);

    setContentView(R.layout.activity_main);
  

    mSlidingLayout = (SlidingPaneLayout) 
      findViewById(R.id.sliding_pane_layout);
    mSlidingLayout.setPanelSlideListener(this);
  
    String[] opcoes = new String[] { 
      "Opção 1", "Opção 2", "Opção 3",
      "Opção 4", "Opção 5" };

    mList = (ListView) findViewById(R.id.left_pane);
    mList.setAdapter(new ArrayAdapter<String>(
      this, 
      android.R.layout.simple_list_item_1, 
      opcoes));
    mList.setOnItemClickListener(this);
  }

  // Evento de clique do botão
  public void abrirMenu(View v){
    // Se estive aberto, feche. Senão abra.
    if (mSlidingLayout.isOpen()){
      mSlidingLayout.closePane();
    } else {
      mSlidingLayout.openPane();
    }
  }

  @Override
  public void onItemClick(AdapterView<?> adapterView, 
    View view, int position, long id) {
    // TODO Tratar opções de Menu (ListView) aqui!
  }

  @Override
  public void onPanelClosed(View arg0) {
    // TODO Ao fechar o painel
  }

  @Override
  public void onPanelOpened(View arg0) {
    // TODO Ao abrir o painel
  }

  @Override
  public void onPanelSlide(View arg0, float arg1) {
    // TODO Enquanto o painel desliza
  }
}
O código acima é bem simples e está comentado.
O mais legal desse componente é que ao deslizar o dedo sobre a tela da esquerda para direita, o menu exibido (da direita para esquerda, ele fecha). Ou seja, o botão acima é opcional, mas indicado, como vemos nas aplicações em geral (como a do Facebook e Gmail) para facilitar a visualização do usuário.
Abaixo podemos ver nossa aplicações em execução com o menu lateral aberto.

É isso pessoal! Temos um menu no padrão de aplicações profissionais. Vale salientar que esse componente funciona em todas as versões do Android.

EDITADO em 29/11/2013

Várias pessoas me perguntaram (por email.... deixem os comentários aqui povo!) o que fazer com o clique do item da lista. O mais legal aqui é usar Fragments e sua stack. Ou seja, criar uma pilha de Fragments como o Android faz com as Activities. Ou seja, ao invés de irmos chamando Activities, chamamos Fragments e vamos empilhando-os no layout da direita.
Vamos ao exemplo... Crie a classe MeuFragment conforme abaixo:
public class MeuFragment extends Fragment {

  public static MeuFragment newInstance(String s){
    Bundle args = new Bundle();
    args.putString("texto", s);
  
    MeuFragment f = new MeuFragment();
    f.setArguments(args);
    return f;
  }
 
  @Override
  public View onCreateView(LayoutInflater inflater, 
    ViewGroup container, Bundle savedInstanceState) {

    View layout = inflater.inflate(
      R.layout.meu_fragment, container, false);

    TextView txt = (TextView)
      layout.findViewById(R.id.textView1);

    txt.setText(getArguments().getString("texto"));
    return layout;
  }
}
O arquivo de layout do Fragment acima, só tem um RelativeLayout com um TextView centralizado (nada de mais, vocês conseguem :)
Agora, no clique de cada um dos itens, instanciamos esse fragment passando o texto da opção clicada.
@Override
public void onItemClick(AdapterView<?> adapterView, 
  View view, int position, long id) {

  String opcao = (String)
    mList.getAdapter().getItem(position);
  
  MeuFragment f = MeuFragment.newInstance(opcao);
  
  FragmentManager fm = getSupportFragmentManager();
  // Opcional: isso removerá o fragment anterior 
  // da pilha.
  fm.popBackStack(); 
  
  fm.beginTransaction()
    .replace(R.id.rightPane, f, "frag1")
    .addToBackStack(null)
    .commit();  
}
O método addToBackStack vai adicionar o Fragment a uma pilha, dessa forma, ao clicar no botão back do aparelho, o fragment será removido automaticamente e anterior será exibido.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

Fonte: http://androidtrainningcenter.blogspot.com.br/2013/06/slidingpanelayout-android-making.html

quarta-feira, 22 de maio de 2013

Android: Dicas 9

Olá povo,

Segue mais um post da série "Dicas de Android". Aproveitem!

Dica 1 - Driver ADB universal para Windows

Essa dica foi dada pelo Pedro Borba, meu aluno da Unibratec. Muitas vezes quando conectamos alguns devices menos populares e eles não são reconhecidos pelo Windows, e às vezes é até complicado achar esses drivers na internet. O site abaixo disponibiliza uma versão do driver ADB, que funciona em muitos desses aparelhos. Vale a pena conferir.

http://adbdriver.com/

Dica 2 - Imagem de background repetida
Como nas velhas páginas HTML, às vezes é interessante colocar uma imagem de background repetida lado a lado, dando a impressão que é uma imagem maior. A solução é criar um arquivo XML (meu_bg.xml por exemplo) na pasta drawable com o seguinte conteúdo.
<bitmap 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:src="@drawable/minha_imagem_que_repete"
  android:tileMode="repeat" />
Depois é só colocar @drawable/meu_bg como sua imagem de background.

Dica 3 - Vários núcleos do processador para o Emulador
Essa dica foi dada por Daniel Sant'Ana, meu aluno do TECDAM. Quando executamos o emulador,  apenas 1 núcleo do processador da máquina é ativado. Para permitir que o emulador utilize os outros núcleos do processador, abra o gerenciador de tarefas do windows, clique em processos, e encontre o processo referente ao emulador. Em seguida, clique com o botão direito nele, e escolha a opção afinidade, então marque todos os núcleos. Se quiser, você ainda pode definir a prioridade do processo do emulador como alta. Feito isso, é possível ganhar um pouco de desempenho no emulador.

Outra opção, em fase experimental, pode ser vista aqui, na seção "Configuring Virtual Machine Acceleration". Ou aqui, no post escrito pelo meu colega Eric Cavalcanti.

Dica 4 - HttpURLConnection ou HTTPClient
A maioria das aplicações Android utiliza HTTP para enviar e receber dados. O Android tem duas APIs para essa tarefa: HTTPClient da Apache e HttpURLConnection do próprio Java.
Conforme podemos ver aqui e aqui, o pessoal da Google está recomendando utilizar o HttpURLConnection para aplicações voltadas para Android 2.3 ou superior. Nas versões 2.2 e inferiores, essa API tinha uma série de bugs que foram corrigidos a partir da versão seguinte. Além disso, alguns recursos como compressão e cache.

Dica 5 - DumbleDroid e WebCachedImageView
Meu amigo Leocádio Tiné, um dos caras que mais conhece de Android que eu conheço, acabou de disponibilizar para a comunidade essas duas bibliotecas interessantíssimas. A primeira faz o download de arquivos XML e JSON e autoMAGICAmente cria as classes Java que representam essas estruturas.
Perguntei ao Leocádio qual a diferença entre essa biblioteca e a GSON (da Google) e o Simple XML. E a resposta foi:

"A diferença básica do Dumbledroid pra essas 2 libraries é que o Dumbledroid faz caching automático em memória e em disco, e roda código específico pra Android. Por exemplo: ele usa as classes de JSON incluídas no Android SDK, e não as do Java SDK, como o GSON. As diferenças específicas: GSON: Usando GSON, você tem que carregar o JSON manualmente. Usando Dumbledroid, basta passar a URL que ele faz o carregamento, parsing e caching. É mais simples. SimpleXML: Usando SimpleXML, além de ter que carregar o XML manualmente, você tem que escrever annotations nas classes pra mapear os campos aos nós do XML. No Dumbledroid, isso não é preciso."

Além disso o DumbleDroid já tem um plugin para Eclipse que facilita ainda mais o seu uso.
Já o WebCachedImageView é bem similar ao ImageView nativo, com o benefício de passarmos apenas a URL da imagem que desejamos e ainda fazer o cache da mesma.

Segue os links para download das libs:
https://github.com/leocadiotine/Dumbledroid
https://github.com/leocadiotine/WebCachedImageView

Vou tentar fazer um post só pra essas duas libs em breve.

Dica 6 - Detectando JavaScript da WebView na Activity
Um recurso que pode trazer grandes possibilidades é conseguirmos capturar funções Java Script que são executadas dentro de uma WebView. Criem o arquivo meu.html dentro da pasta assets do projeto e deixe-o conforme abaixo.
<html>
<header>
<script type="text/javascript">
function showAndroidToast(s, t) {
  window.nglauber.showToast(s, t);
} 
</script>
</header>
<body>
  <H1>Formulario em HTML</h1>
  <form name="meuForm">
    Nome: 
    <input type="text" name="txtNome"/><br>
    Idade: 
    <input type="text" name="txtIdade"/><br>
    <input type="button" 
      onclick="showAndroidToast(txtNome.value, txtIdade.value);" 
      value="Enviar">
  </form>
</body>
</html>
É possível interceptar a função showAndroidToast e chamar o método showToast dentro da nossa Activity. Dessa forma, quando o usuário pressionar o botão do HTML, o código da Activity será executado.
public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  
    WebView wv = (WebView)findViewById(R.id.webView1);
    WebSettings settings = wv.getSettings();
    settings.setJavaScriptEnabled(true);
    wv.addJavascriptInterface(this, "nglauber");
    wv.loadUrl("file:///android_asset/meu.html");
  }

  @JavascriptInterface
  public void showToast(String s, String t) {
    Toast.makeText(this, 
      "Nome:"+ s + " Idade:"+ t, 
      Toast.LENGTH_SHORT).show();
  }
}
Apesar de não ser algo que não encorajo, essa solução pode ser útil para alguns tipos de tela. Disponibilizamos a interface "nglauber" para o código JavaScript chamar. Notem que no JavaScript ficou window.nglauber.showToast. Outro detalhe aqui é que existe um bug do Android 2.3 que impede que esse código funcione nessa versão. Outro detalhe é a Annotation @JavascriptInterface que só é necessária a partir do Android 4.2, em versões anteriores, basta o método ser público.

Dica 7 - Definindo uma orientação fixa via código
É possível definir uma orientação fixa para um Activity através do arquivo AndroidManifest.xml através da propriedade screenOrientation.
<activity name=".MinhaActivity"   
  android:screenOrientation="landscape"/>
Mas e se quisermos que essa configuração seja feita dependendo de alguma condição? Podemos usar o código abaixo.
setRequestedOrientation(
  ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)

Dica 8 - Versão do Android do Aparelho
Obtendo a versão do Android que está rodando no aparelho.
int apiLevel = Build.VERSION.SDK_INT;
if (apiLevel < Build.VERSION_CODES.HONEYCOMB){
  // Usando Android 2.3 ou inferior
} else if (apiLevel < 
  Build.VERSION_CODES.ICE_CREAM_SANDWICH){
  // Android 3.x
} else {
  // ICS ou superior
}

4br4ç05,
nglauber