Nenhum comentário


Android Studio – Parte V

Nessa última parte da série de Android (continuaremos com mais artigos sobre o assunto, mas com assuntos específicos), vamos continuar conhecendo controles de telas, com exemplos de ListView agrupado, ExpandableView e SearchView.

ListView agrupado

Temos um controle especifico para isso (ExpandableListView), que veremos mais adiante. Mas quero mostrar aqui, uma maneira diferente para simular um agrupamento de ListView. Crie um novo layout e adicione um ListView.

Vamos criar o layout que será a indicação que temos um novo grupo na lista de itens. Crie um novo layout com o nome de “layout_header_listview.xml”. Adicione o seguinte código:

<RelativeLayout
   android:layout_height="wrap_content"
   android:layout_width="fill_parent"
   android:orientation="vertical"
   xmlns:android="http://schemas.android.com/apk/res/android">

   <ImageView
      android:id="@+id/thumbnail"
      android:layout_width="25dip"

      android:layout_height="25dip"

      android:src="@drawable/ic_action_group"

      android:scaleType="fitXY"/>

 

   <TextView

      android:id="@+id/name_group"

      android:layout_width="fill_parent"

      android:layout_height="wrap_content"

      android:layout_alignTop="@+id/thumbnail"
      android:layout_toRightOf="@+id/thumbnail"
      android:text="Nome do grupo"
      android:textStyle="bold"
      android:textSize="15dip"
      android:paddingLeft="5dp"/>
</RelativeLayout>

Adicionamos um ImageView e um TextView. A imagem já esta configurada, precisamos apenas mudar o nome do grupo quando estivermos preenchendo o ListView (no adapter). Vamos criar um novo Model, para configurarmos o grupo que desejamos.

Para não fazer o trabalho massante de criar a classe, podemos copiá-la com o mesmo conteúdo e nome diferente. Clique com o botão direito na classe Item e escolha Refactor>Copy. Digite “ItemGroup”. Uma nova classe é criada com o mesmo conteúdo da Item.

Vamos criar mais dois atributos:

private boolean bGrupo;
private String sNmGrupo;

Para gerar o get e set, use a mesma técnica mostrada anteriormente. Não esqueça de modificar o construtor para receber os dois novos atributos. Agora a parte mais importante, precisamos criar um novo adapter, é nele que vamos configurar o grupo.

Você pode fazer a mesma técnica de copiar a classe ListItem, dando o nome de “ListItemGroup”. Não esqueça de modificar a variável data para receber uma lista de ItemGroup, assim como o parâmetro do construtor. No getItemId, também devemos mudar o tipo do objeto (ItemGroup).

Vamos modificar o getView com o seguinte código:

View vi = convertView;
ItemGroup item = data.get(position);

if (item.isbGrupo()){
   //é um grupo
   vi = inflater.inflate(R.layout.layout_header_listview, null);
   vi.setOnClickListener(null);
   vi.setOnLongClickListener(null);
   vi.setLongClickable(false);

   TextView groupName = (TextView) vi.findViewById(R.id.name_group);
   groupName.setText(item.getsNmGrupo());
}
else{
   //não é grupo
   vi = inflater.inflate(R.layout.single_item_listview_group, null);
   ImageView imagem = (ImageView)vi.findViewById(R.id.thumbnail);
   TextView name = (TextView)vi.findViewById(R.id.item);
   TextView description = (TextView)vi.findViewById(R.id.descrption);

   name.setText(item.getsNmItem());
   description.setText(item.getsDSItem());
   imagem.setImageResource(item.getiLogo());
}

return vi;

Pegamos o dado e verificamos se é um grupo, assim, chamamos o respectivo layout e configuramos o controle para mostrar o nome do grupo. Se não, configuramos normalmente os itens de tela (repliquei o layout do item do ListView).

No código para preencher a lista de itens, precisamos apenas configurar o grupo e seus itens:

else if (mItem.id == "4"){
  rootView = inflater.inflate(R.layout.layout_listview_group, container, false);
  //ListView agrupado
  ListView lview = (ListView)rootView.findViewById(R.id.listView);
 

  List<ItemGroup> lista = new ArrayList<ItemGroup>();
  lista.add(new ItemGroup(1, "", true, "Controles de tela", "", 0));
  lista.add(new ItemGroup(2, "ProgressBar", false, "",
    "Controle para mostrar uma barra de progresso",
    R.drawable.ic_action_progressbar));

  lista.add(new ItemGroup(3, "TextView", false, "",
    "Controle para mostrar um texto", R.drawable.ic_action_textview));
  lista.add(new ItemGroup(4, "ImageView", false, "",
    "Controle para mostrar uma imagem", R.drawable.ic_action_imageview));
  lista.add(new ItemGroup(5, "", true, "Containers", "", 0));
  lista.add(new ItemGroup(6, "TabHost", false, "",
    "Controle para mostrar abas", R.drawable.ic_action_tabhost));
  lista.add(new ItemGroup(7, "ListView", false, "",
    "Controle para mostrar dados em forma de lista",
    R.drawable.ic_action_listview));

  lview.setAdapter(new ListItemGroup(getActivity(), lista));
}

Indicamos em cada item, se o mesmo é grupo ou não (terceiro parâmetro). Executa a aplicação e veja o resultado na Figura 1.

 

 

Figura 1. Agrupamento com ListView

Para deixar os controles dos itens um pouco para a direita, coloquei o seguinte código na imagem do single_item_listview_group (layout replicado):

android:layout_marginLeft="20dp"

Esse exemplo é bom para quando você precisa que o grupo fique sempre visível. Talvez não seja a melhor opção, mas valeu a pena aprender. Para que você tenha grupos que possam ser expandidos e recolhidos, veremos como funciona com o ExpandableListView.

ExpandableListView

Esse controle, cria grupos no ListView. Crie um novo layout (“layout_expandable_listview.xml”) e adicione esse controle. Precisamos criar um adapter, que herda de BaseExpandableListAdapter. Crie uma nova classe na pasta ListAdapter com o nome de “ListExpandable”.

Herde de BaseExpandableListAdapter e usando o editor, crie os métodos (semelhante ao que já fizemos). Note que vários métodos foram adicionados. Temos que passar para esse adapter, uma lista com os grupos e uma lista com o nome do grupo e seus itens. Declare as seguintes variáveis:

private Activity activity;
private static LayoutInflater inflater = null;
private HashMap<String, List<Item>> list;
private List<String> listGroup;

Para o grupo, temos uma lista de strings. Para os dados, temos um HashMap, que nada mais é do que um conjunto de chave valor. Assim, teremos como chave, o nome do grupo e o valor, serão os itens. Assim, podemos associar (retornar) a lista de grupos com a lista de dados.

Crie o construtor com o seguinte código:

public ListExpandable(Activity activity, HashMap<String,
  List<Item>> data, List<String> groups) {
   this.activity = activity;
   this.list = data;
   this.listGroup = groups;
   inflater = (LayoutInflater)activity.getSystemService(
      Context.LAYOUT_INFLATER_SERVICE);
}

Semelhante ao criado em adapters anteriores, única diferença que temos duas listas agora. Os métodos que retornam a quantidade do grupo e itens, é de fácil implementação. O getChild, que devemos implementar com o seguinte código:

return list.get(listGroup.get(groupPosition)).get(childPosition);

Passamos o código do grupo e do item para retornar o objeto. Agora, temos dois getView: getGroupView e getChildView, um para cada tipo. Vamos usar o mesmo layout usado no exemplo anterior, para os itens.

Para o grupo, faça uma cópia de layout_header_listview.xml (dei o nome de “layout_header_expandablelistview.xml”) e remova a imagem do grupo. O ExpandableListView possui uma imagem. Para o getGroupView, vamos usar o seguinte código:

HolderGroup holder;
if(convertView == null){

   convertView = inflater.inflate(
      R.layout.layout_header_expandablelistview, null);

   holder = new HolderGroup();
   convertView.setTag(holder);
   holder.name = (TextView) convertView.findViewById(R.id.name_group);
}
else{
   holder = (HolderGroup) convertView.getTag();
}

holder.name.setText(listGroup.get(groupPosition));
return convertView;

HolderGroup é apenas uma classe, que contém os controles de tela que vamos preencher (nesse caso, o nome do grupo). Implemente as classes, com o seguinte código:

class HolderGroup {
   TextView name;
}

class HolderItem {
   ImageView imagem;
   TextView name;
   TextView description;
}

Voltando ao getGroupView, carregamos o layout do grupo e preenchemos o TextView com o nome do grupo. Semelhante ao que fizemos no exemplo anterior. Para o getChildView, o código é semelhante:

HolderItem holder;

Item item = (Item)getChild(groupPosition, childPosition);

if(convertView == null){
   convertView = inflater.inflate(
      R.layout.single_item_listview_group, null);

     holder = new HolderItem();
   convertView.setTag(holder);

   holder.name = (TextView) convertView.findViewById(R.id.item);
   holder.description = (TextView)convertView.findViewById(R.id.descrption);
   holder.imagem = (ImageView)convertView.findViewById(R.id.thumbnail);
}
else{
   holder = (HolderItem) convertView.getTag();
}

holder.name.setText(item.getsNmItem());
holder.description.setText(item.getsDSItem());
holder.imagem.setImageResource(item.getiLogo());
return convertView;

Nada muito diferente do que já fizemos. Precisamos agora, preencher as listas e passar para o adapter. Volte ao ItemDetailFragment e use o seguinte código:

else if (mItem.id == "5"){
   rootView = inflater.inflate(R.layout.layout_expandable_listview, container, false);

   ExpandableListView lview = (ExpandableListView)rootView.
      findViewById(R.id.expandableListView);
   List<String> group = new ArrayList<String>();
   HashMap<String, List<Item>> itens = new HashMap<String, List<Item>>();

   group.add("Controles de tela");
   group.add("Containers");

   List<Item> aux = new ArrayList<Item>();
   aux.add(new Item(2, "ProgressBar",
      "Controle para mostrar uma barra de progresso",
      R.drawable.ic_action_progressbar));
   aux.add(new Item(3, "TextView",
      "Controle para mostrar um texto", R.drawable.ic_action_textview));
   aux.add(new Item(4, "ImageView",
      "Controle para mostrar uma imagem", R.drawable.ic_action_imageview));
   itens.put(group.get(0), aux);
   

   aux = new ArrayList<Item>();
   aux.add(new Item(2, "TabHost",
      "Controle para mostrar abas", R.drawable.ic_action_tabhost));
   aux.add(new Item(3, "ListView",
      "Controle para mostrar dados em forma de lista",
      R.drawable.ic_action_listview));
   itens.put(group.get(1), aux);
   lview.setAdapter(new ListExpandable(getActivity(), itens, group));
}

Criamos duas listas, onde preenchemos o nome dos grupos, e depois preenchemos os itens, adicionando o grupo criado. Por fim, chamamos o ListExpandable, passando as listas por parâmetro. Rode a aplicação e veja o resultado (Figura 2).

 


Figura 2. Criando grupos com o ExpandableListView

Os grupos abrem recolhidos, basta selecionar o mesmo, que será mostrado os itens. Existe um ícone para quando o grupo esta recolhido e outro para aberto.

SearchView

Esse controle abre uma busca para usarmos na aplicação. Com ele podemos pesquisar em um ListView, por exemplo, ou abrir uma nova activity com resultado de uma pesquisa. Nesse exemplo, vamos pesquisar em um ListView.

Crie um novo layout (“layout_searchview.xml”) e adicione um ListView e um SearchView. Nesse primeiro exemplo, o ListView será preenchido com um array de resource e assim, vamos usar um ArrayAdapter, sem criar um novo.

Abra o arquivo strings.xml na pasta values e digite o seguinte código:

<string-array name="paises">
   <item>Brasil</item>
   <item>Argentina</item>
   <item>USA</item>
   <item>Paraguai</item>
   <item>Rússia</item>
   <item>Espanha</item>
   <item>Portugal</item>
   <item>França</item>
</string-array>

No ItemDetailFragment, vamos configurar o SearchView e o ListView, usando o seguinte código:

else if (mItem.id == "6"){
   rootView = inflater.inflate(R.layout.layout_searchview,
      container, false);
   final SearchView search = (SearchView)rootView.
      findViewById(R.id.searchView);
   ListView lview = (ListView)rootView.findViewById(R.id.listView);

   Resources res = getResources();
   String[] paises = res.getStringArray(R.array.paises);
 

   final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
      getActivity(),
      android.R.layout.simple_list_item_activated_1,
      android.R.id.text1,
      paises);

   lview.setAdapter(adapter);
   search.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
      @Override
      public boolean onQueryTextSubmit(String query) {
      if (!query.isEmpty()){
         search.clearFocus();
         adapter.getFilter().filter(query);
      }

      return false;
      }

      @Override
      public boolean onQueryTextChange(String newText) {
      if (newText.isEmpty()){
         adapter.getFilter().filter("");
      }

      return false;
      }
  });
}

Como sempre, carregamos o layout e os controles que vamos manipular. Usamos uma variável para carregar o array de resource. Criamos um ArrayAdapter e configuramos no ListView. O SearchView possui um Listener com dois métodos. O onQueryTextSubmit é executado quando o usuário aperta o botão de pesquisar, assim, vamos realizar a pesquisa.

No adapter, temos um método para pesquisar nos valores do mesmo. getFilter().filter, recebe o valor digitado (query). Implementamos o onqueryTextChange, para verificar se o que foi digitado esta vazio, assim, retornamos os dados, passando um valor vazio para o filter.

O onQueryTextChange, será chamado, quando o usuário clicar no X para limpar o que foi digitado ou quando usar a tecla que limpa o valor do SearchView. Execute a aplicação e digite valores para pesquisar no ListView (Figura 3).

 


Figura 3. Filtrando o ListView com o SearchView

Note que o filtro é feito pelo inicio do texto digitado, não é pesquisado por qualquer letra dos itens do ListView. Para os outros exemplos, criamos um adapter para preencher o ListView, assim, o getFilter, não esta disponível, precisamos implementá-lo.

Vamos adaptar a classe ListItem (se preferir, crie uma nova), para criar esse filtro. Primeiro, devemos implementar Filterable no adapter:

public class ListItem extends BaseAdapter implements Filterable

Vamos criar alguns atributos:

private List<Item> originalData;
private List<Item> filteredData;
public boolean isFilter = false;

Preencha originalData e filteredData no construtor. Teremos essas duas variáveis para trabalhar com os dados filtrados e os dados “normais”. No getView, precisamos adaptar para saber de onde vamos pegar os dados para preencher os controles de tela. Por isso, temos isFilter, assim podemos adaptar para o seguinte código:

Item itemObject;

if (isFilter) {
   itemObject = filteredData.get(position);
} else {
   itemObject = data.get(position);
}

Se estivermos filtrando, precisamos pegar os dados do filteredData, senão, pegamos do objeto que já trabalhamos. Por fim, precisamos implementar o getFilter, usando o código:

@Override
public Filter getFilter()
{
   return new Filter()
   {
      @Override
      protected FilterResults performFiltering(CharSequence charSequence)
      {
         FilterResults results = new FilterResults();
         if(charSequence == null || charSequence.length() == 0)
         {
            results.values = originalData;
            results.count = originalData.size();
         }
         else
         {
            List<Item> filterResultsData = new ArrayList<Item>();
            String name = "";

            for(Item data : originalData) {
            name = data.getsNmItem().toUpperCase();

            if (name.contains(charSequence.toString().toUpperCase())) {
               filterResultsData.add(data);
         }
      }
 

      results.values = filterResultsData;
      results.count = filterResultsData.size();
   }

   return results;
   }

   @Override
   protected void publishResults(CharSequence charSequence,
      FilterResults filterResults)
   {
      filteredData = (List<Item>)filterResults.values;
      isFilter = (filteredData != originalData);
      notifyDataSetChanged();
   }
   };
}

Verificamos se temos valores digitados para filtrar (charSequence), atribuindo para as variáveis o valor atual. Senão, percorremos os dados originais (com a variável auxiliar), e verificamos se o valor digitado é compatível com os dados. No publishResults é onde indicamos se estamos filtrando o adapter, comparando as variáveis.

Se for, colocamos em filterResultsData e depois retornamos o mesmo (usando results). Por fim, precisamos indicar no getCount a quantidade de registros que temos (que podem ser filtrados ou não), para isso, use o seguinte código:

if (isFilter) {
   return filteredData.size();
} else {
   return data.size();
}

Crie um novo layout e adicione um ListView e um SearchView. Vamos pegar o mesmo exemplo que criamos o ListView customizado. Digite o seguinte código:

else if (mItem.id == "7"){
   rootView = inflater.inflate(R.layout.layout_searchview_filter,
      container, false);
   final SearchView search = (SearchView)rootView.
      findViewById(R.id.searchView);
   ListView lview = (ListView)rootView.findViewById(R.id.listView);

   List<Item> lista = new ArrayList<Item>();
   ...

   final ListItemFilter adapter = new ListItemFilter(getActivity(), lista);
   lview.setAdapter(adapter);

   search.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
      @Override
      public boolean onQueryTextSubmit(String query) {
         if (!query.isEmpty()){
            search.clearFocus();

            adapter.getFilter().filter(query);
         }
         return false;
     }
 

     @Override
     public boolean onQueryTextChange(String newText) {

     if (newText.isEmpty()){
        adapter.getFilter().filter("");
     }
     return false;
  }
});
}

 

Note que a implementação do filter, é igual ao exemplo anterior, pois implementamos o getFilter. Experimente acessar getFilter no exemplo anterior, não temos. Execute a aplicação, e pesquisa registros no ListView (Figura 4).

 


Figura 4. Pesquisando registros no ListView com Filter do Adapter

Conclusões

Vimos nessa série, o básico para o desenvolvimento Android. Conhecemos desde o inicio da ferramenta, até controles comuns em aplicações Android. Aprendemos como customizá-los para que nossas aplicações fiquem o mais profissional possível.

Espero ter passado o conhecimento básico para você se aprofundar na programação Android.