Como Criar um ListAdapter Customizado para o ListView do Android

O widget ListView é um componente poderoso e flexível do Android, normalmente utilizado para exibir uma simples lista de dados. Este post descreve como criar um ListAdapter para exibir várias informações e uma figura em cada linha do ListView.

Para demonstrar a criação do ListAdapter customizado foi criado um projeto para exibir em um ListView os estados do Brasil, juntamente com a abreviação, nome da capital, área em km2 e a imagem da bandeira do estado. O projeto rodando apresenta a seguinte tela:

device3

O projeto possui somente uma Activity que carrega o ListView no método OnCreate() demonstrado no arquivo Main.java.

public class Main extends ListActivity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        List<State> stateList = new ArrayList<State>();
        
        for (int i = 0; i < states.length; i++) {
            State state = new State();
            state.setState(states[i][0]);
            state.setAbbreviation(states[i][1]);
            state.setCapital(states[i][2]);
            state.setArea(Float.parseFloat(states[i][3]));
            state.setBanner(images[i]);
            
            stateList.add(state);
        }  
        
        setListAdapter(new StateAdapter(this, stateList));
    }
    
    private String[][] states = new String[][]{
           {"Acre", "AC", "Rio Branco", "152581.4"},
           {"Alagoas", "AL", "Maceió", "27767.7"},
           {"Amapá", "AP", "Macapá", "142814.6"},
           {"Amazonas", "AM", "Manaus", "1570745.7"},
           {"Bahia", "BA", "Salvador", "564692.7"},
           {"Ceará", "CE", "Fortaleza", "148825.6"},
           {"Distrito Federal", "DF", "Brasília", "5822.1"},
           {"Espírito Santo", "ES", "Vitória", "46077.5"},
           {"Goiás", "GO", "Goiânia", "340086.7"},
           {"Maranhão", "MA", "São Luís", "331983.3"},
           {"Mato Grosso", "MT", "Cuiabá", "903357.9"},
           {"Mato Grosso do Sul", "MS", "Campo Grande", "357125.0"},
           {"Minas Gerais", "MG", "Belo Horizonte", "586528.3"},
           {"Pará", "PA", "Belém", "1247689.5"},
           {"Paraíba", "PB", "João Pessoa", "56439.8"},
           {"Paraná", "PR", "Curitiba", "199314.9"},
           {"Pernambuco", "PE", "Recife", "98311.6"},
           {"Piauí", "PI", "Teresina", "251529.2"},
           {"Rio de Janeiro", "RJ", "Rio de Janeiro", "43696.1"},
           {"Rio Grande do Norte", "RN", "Natal", "52796.8"},
           {"Rio Grande do Sul", "RS", "Porto Alegre", "281748.5"},
           {"Rondônia", "RO", "Porto Velho", "237576.2"},
           {"Roraima", "RR", "Boa Vista", "224299.0"},
           {"Santa Catarina", "SC", "Florianópolis", "95346.2"},
           {"São Paulo", "SP", "São Paulo", "248209.4"},
           {"Sergipe", "SE", "Aracaju", "21910.3"},
           {"Tocantins", "TO", "Palmas", "277620.9"}
       };
    
    private int[] images = new int[]{
            R.drawable.acre,
            R.drawable.alagoas,
            R.drawable.amapa,
            R.drawable.amazonas,
            R.drawable.bahia,
            R.drawable.ceara,
            R.drawable.distritofederal,
            R.drawable.espiritosanto,
            R.drawable.goias,
            R.drawable.maranhao,
            R.drawable.matogrosso,
            R.drawable.matogrossosul,
            R.drawable.minasgerais,
            R.drawable.para,
            R.drawable.paraiba,
            R.drawable.parana,
            R.drawable.pernambuco,
            R.drawable.piaui,
            R.drawable.riojaneiro,
            R.drawable.riograndenorte,
            R.drawable.riograndesul,
            R.drawable.rondonia,
            R.drawable.roraima,
            R.drawable.santacatarina,
            R.drawable.saopaulo,
            R.drawable.sergipe,
            R.drawable.tocatins
    };
}


No método OnCreate() foi criado um ArrayList com as informações dos estados. Normalmente este tipo de informação é carregada de uma base de dados ou de alguma outra fonte de informação, mas como o objetivo é demonstrar a utilização do ListView, foi criado um array com as informações dos estados (states) e outro array com as imagens das bandeiras (images). As imagens foram obtidas dos arquivos colocados na pasta de resource res/drawable-mdpi.

Para montar o ArrayList foi utilizada a classe State, descrita no arquivo State.java:

public class State {
    private String state;
    private String abbreviation;
    private String capital;
    private float area;
    private int banner;
    
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
    public String getAbbreviation() {
        return abbreviation;
    }
    public void setAbbreviation(String shortening) {
        this.abbreviation = shortening;
    }
    public String getCapital() {
        return capital;
    }
    public void setCapital(String capital) {
        this.capital = capital;
    }
    public float getArea() {
        return area;
    }
    public void setArea(float area) {
        this.area = area;
    }
    public int getBanner() {
        return banner;
    }
    public void setBanner(int banner) {
        this.banner = banner;
    }
}


O cursor para as informações do ListView é montado utilizando o método setListAdapter(ListAdapter adapter) da classe ListActivity. No parâmetro adapter foi utilizada a classe StateAdapter que recebe como parâmetros o contexto da Activity e o ArrayList com as informações dos estados. O arquivo StateAdapter.java descreve os métodos que são necessários para montar o cursor do ListView. 

/** 
 * Adapter utilizado para exibir as informações dos Estados
 * no ListView.
 * @author Administrador
 *
 */
public class StateAdapter extends BaseAdapter {
    private Context context;
    private List<State> stateList;
    
    public StateAdapter(Context context, List<State> statelist){
        this.context = context;
        this.stateList = statelist;
    }
    
    @Override
    public int getCount() {
        return stateList.size();
    }
 
    @Override
    public Object getItem(int position) {
        return stateList.get(position);
    }
 
    @Override
    public long getItemId(int position) {
        return position;
    }
 
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // Recupera o estado da posição atual
        State state = stateList.get(position);
        
        // Cria uma instância do layout XML para os objetos correspondentes
        // na View
        LayoutInflater inflater = (LayoutInflater)
            context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View view = inflater.inflate(R.layout.listview_states, null);
        
        // Estado - Abreviação
        TextView textState = (TextView)view.findViewById(R.id.textState);
        textState.setText(state.getState() + " - " + state.getAbbreviation());
        
        // Capital
        TextView textCapital = (TextView)view.findViewById(R.id.textCapital);
        textCapital.setText(state.getCapital());
        
        // Área
        TextView textArea = (TextView)view.findViewById(R.id.textArea);
        textArea.setText(String.valueOf(state.getArea()));
        
        // Bandeira
        ImageView img = (ImageView)view.findViewById(R.id.imageState);
        img.setImageResource(state.getBanner());
 
        return view;
    }
}


A classe StateAdapter extende a classe BaseAdapter que deve implementar os métodos:
  •  getCount(): retorna o número de itens.
  • getItem(int position): retorna o item de uma posição específica. 
  •  getItemId(int position): retorna o Id de um item de uma posição específica.
  • getView(int position, View convertView, ViewGroup parent): retorna a View com as informações posicionadas de acordo com o layout montado no arquivo listview_states.xml
O arquivo listview_states.xml monta o layout que é utilizado em cada linha do ListView, para posicionar as informações em um formato customizado.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:padding="5dp"
  android:background="#cccccc"
  >
  
  <ImageView
      android:id="@+id/imageState"
      android:layout_width="60dp"
      android:layout_height="42dp"
      android:background="#ffffff"
      android:scaleType="centerCrop"
  />
  
  <TextView
      android:id="@+id/textState"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:layout_toRightOf="@+id/imageState"
      android:layout_alignTop="@+id/imageState"
      android:text="São Paulo - SP"
      android:textSize="20sp"    
      android:textColor="#333333"
      android:paddingLeft="5dp"  
  />
  
  <TextView
      android:id="@+id/textCapital"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_below="@+id/textState"
      android:layout_toRightOf="@+id/imageState"
      android:text="São Paulo"
      android:textColor="#333333"
      android:paddingLeft="5dp" 
  />
  
  <TextView
      android:id="@+id/textArea"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_below="@+id/textState"
      android:layout_alignParentRight="true"
      android:text="100000"
      android:textColor="#333333"
  />  
</RelativeLayout>


No método getView utilizando a classe LayoutInflater e a classe View podemos associar cada widget do layout à informação que deve ser exibida no ListView.

No exemplo deste post, quando um item do ListView recebe o foco a tradicional cor “Laranja” de fundo não aparece. Isto acontece porque o ListView possui uma cor de fundo opaca, neste caso “cinza”. Na verdade o foco “Laranja” é exibido, mas somente atrás do background do ListView.

Uma possível solução para exibir o fundo “Laranja” neste caso, é fazer uma pequena alteração da definição do layout da tela.

Primeiro precisamos criar um arquivo tipo StateList para definir as cores que serão apresentadas quando o item do ListView for pressionado, selecionado ou receber foco. Para isso foi criado o arquivo list_selector.xml, que deve ser colocado no diretório res/drawable

<?xml version="1.0" encoding="utf-8"?>
<selector
    xmlns:android="http://schemas.android.com/apk/res/android">
     <item
        android:state_pressed="true"
        android:drawable="@android:color/transparent" />
     <item
        android:state_selected="true"
        android:drawable="@android:color/transparent" />
     <item
        android:state_focused="true"
        android:drawable="@android:color/transparent" />
     <item
        android:drawable="@color/cinza" />
 </selector>


Os itens relativos aos estados pressionado, selecionado ou com focus devem ser configurados com o atributo android:drawable com cor transparente, somente o último item que representa o item sem mudança de estado deve ser configurado com a cor cinza. A cor cinza foi declarada no arquivo color.xml que deve ser colocado no diretório res/values.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="cinza">#cccccc</color>
</resources>


Para que o fundo “laranja” funcione altere no arquivo listview_states.xml o atributo android:drawable da tag RelativeLayout de android:background="#cccccc" para android:background="@drawable/list_selector".
Agora quando um item for clicado teremos a seguinte tela:

Como Usar uma Figura de Fundo em uma Tela com ListView no Android

Por padrão o Android apresenta a tela com cor de fundo escuro, e a maioria dos widgets possuem cor de fundo transparente. O widget ListView obedece a mesma regra, normalmente sendo utilizado em uma tela com cor de fundo sólida ou transparente. Caso seja utilizado em uma tela com cor de fundo diferente de uma cor sólida, como por exemplo usando uma figura, é necessário tomar um certo cuidado no projeto, para que o ListView no momento do scroll não apresente um fundo escuro no meio da figura.

Este post descreve como exibir uma figura de fundo em uma tela com ListView e como corrigir o problema ocasionado durante o scroll do ListView. Observe na figura abaixo uma tela com ListView e uma figura de fundo mostrando o efeito do scroll. A figura da esquerda mostra a tela exibida na iniciação da aplicação e a figura da direita mostra a tela no momento é quem é feito o scroll.

imagem1

Na figura onde é feito o scroll podemos observar que a figura deixa de ser exibida corretamente. Isto ocorre devido a uma otimização de exibição de tela no sistema do Android. Para solucionar este problema, no framework do Android foi criado o método setCacheColorHint(int) que pode ser configurado via código, ou podemos utilizar o atributo android:cacheColorHint no arquivo XML do layout.

Para demonstrar a utilização do ListView com figura de fundo foi criado um projeto para exibir a relação de estados do Brasil em um ListView cuja figura de fundo é o mapa do Brasil.

Para exibir a figura de fundo foi criado um arquivo theme.xml na pasta res/values que configura a figura de fundo na janela do Android.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Imagem de fundo da tela do listview -->
    <style name="MapBackground" parent="android:Theme">
        <item name="android:windowBackground">@drawable/mapa</item>
        <item name="android:windowNoTitle">true</item> 
    </style>
</resources>


Observe que foi criada a tag <style> com o nome MapBackground, e a janela do Android foi configurada com a figura do mapa através da tag:

<item name="android:windowBackground">@drawable/mapa</item>

Também foi configurado para que a janela não exiba título, através da tag:

<item name="android:windowNoTitle">true</item>

O tema deve ser aplicado na Activity que vai exibir o ListView. Esta configuração pode ser feita no arquivo AndroidManifest.xml.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="br.com.romar"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".Main"
                  android:label="@string/app_name"
                  android:theme="@style/MapBackground">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
 
    </application>
    <uses-sdk android:minSdkVersion="8" />
 
</manifest> 


No arquivo AndroidManifest.xml na tag <activity> da tela de iniciação (.Main) foi inserido o atributo android:theme="@style/MapBackground" que configura o tema da Activity para exibir a figura do mapa.

No arquivo Main.java no método OnCreate foi inserido o código para exibição dos estados no ListView e a configuração do ListView para corrigir o efeito do scroll.

public class Main extends ListActivity {
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        // Obtêm a relação de estados que está armazenado
        // no arquivo strings.xml
        String[] states = getResources().getStringArray(
                R.array.state_array);
        
        // Preenche o ListView com os estados
        setListAdapter(new ArrayAdapter<String>(this, 
                android.R.layout.simple_list_item_1, states));
        
        // Obtêm o ListView
        ListView lv = getListView();
        // Configurando o método setCacheColorHint para 
        // o valor 0 informamos que a cor de fundo do listview
        // possui não possui cor única, sólida ou opaca
        lv.setCacheColorHint(0);
    }
}


No método onCreate(Bundle savedInstanceState) foi utilizado como fonte de dados um array chamado state_array, configurado no arquivo strings.xml localizado na pasta res/values.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">ListViewI</string>
    <string-array name="state_array">
        <item>Acre</item>
        <item>Alagoas</item>
        <item>Amapá</item>
        <item>Amazonas</item>
        <item>Bahia</item>
        <item>Ceará</item>
        <item>Distrito Federal</item>
        <item>Espírito Santo</item>
        <item>Goiás</item>
        <item>Maranhão</item>
        <item>Mato Grosso</item>
        <item>Mato Grosso do Sul</item>
        <item>Minas Gerais</item>
        <item>Pará</item>
        <item>Paraíba</item>
        <item>Paraná</item>
        <item>Pernambuco</item>
        <item>Piauí</item>
        <item>Rio de Janeiro</item>
        <item>Rio Grande do Norte</item>
        <item>Rio Grande do Sul</item>
        <item>Rondônia</item>
        <item>Roraima</item>
        <item>Santa Catarina</item>
        <item>São Paulo</item>
        <item>Sergipe</item>
        <item>Tocatins</item>
    </string-array>
</resources>


Para exibir o ListView foi utilizado o layout padrão do sistema Android, simple_list_item_1, configurado no método setListAdapter().

Após criado o ListView, foi utilizado o método getListView()  para obter o ListView da Activity e  poder desabilitar a otimização da tela através do método setCacheColorHint(). O valor utilizado foi 0 que representa cor de fundo transparente.

Com a inserção do método setCacheColorHint() o scroll pode ser feito normalmente sem apresentar o defeito na figura de fundo.

Posts Relacionados:


Como Retornar Dados em Json com WebService .NET

O webservice .NET retorna as informações utilizando o protocolo SOAP e os dados em formato XML. Este artigo mostra como retornar os dados no formato JSON, utilizando um webservice na versão 3.5 do .NET Framework.

Para mostrar como retornar dados no formato JSON,  foi criado um webservice que retorna a relação de Estados do Brasil, juntamente com as informações de capital, área em km2 e abreviação de cada estado.

Para que o webservice retorne os dados em JSON é necessário inserir o atributo [System.Web.Script.Services.ScriptService] na classe do webservice (linha 4) e o atributo     [ScriptMethod(ResponseFormat=ResponseFormat.Json)] (linha 45) no método que vai retornar os dados, como demonstrado no código abaixo:

   1:  [WebService(Namespace = "http://tempuri.org/")]
   2:  [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
   3:  // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
   4:  [System.Web.Script.Services.ScriptService]
   5:  public class Service : System.Web.Services.WebService
   6:  {
   7:      private string[,] estados = new string[27, 4] 
   8:      {
   9:          {"Acre", "AC", "Rio Branco", "152581,4"},
  10:          {"Alagoas", "AL", "Maceió", "27767,7"},
  11:          {"Amapá", "AP", "Macapá", "142814,6"},
  12:          {"Amazonas", "AM", "Manaus", "1570745,7"},
  13:          {"Bahia", "BA", "Salvador", "564692,7"},
  14:          {"Ceará", "CE", "Fortaleza", "148825,6"},
  15:          {"Distrito Federal", "DF", "Brasília", "5822,1"},
  16:          {"Espírito Santo", "ES", "Vitória", "46077,5"},
  17:          {"Goiás", "GO", "Goiânia", "340086,7"},
  18:          {"Maranhão", "MA", "São Luís", "331983,3"},
  19:          {"Mato Grosso", "MT", "Cuiabá", "903357,9"},
  20:          {"Mato Grosso do Sul", "MS", "Campo Grande", "357125,0"},
  21:          {"Minas Gerais", "MG", "Belo Horizonte", "586528,3"},
  22:          {"Pará", "PA", "Belém", "1247689,5"},
  23:          {"Paraíba", "PB", "João Pessoa", "56439,8"},
  24:          {"Paraná", "PR", "Curitiba", "199314,9"},
  25:          {"Pernambuco", "PE", "Recife", "98311,6"},
  26:          {"Piauí", "PI", "Teresina", "251529,2"},
  27:          {"Rio de Janeiro", "RJ", "Rio de Janeiro", "43696,1"},
  28:          {"Rio Grande do Norte", "RN", "Natal", "52796,8"},
  29:          {"Rio Grande do Sul", "RS", "Porto Alegre", "281748,5"},
  30:          {"Rondônia", "RO", "Porto Velho", "237576,2"},
  31:          {"Roraima", "RR", "Boa Vista", "224299,0"},
  32:          {"Santa Catarina", "SC", "Florianópolis", "95346,2"},
  33:          {"São Paulo", "SP", "São Paulo", "248209,4"},
  34:          {"Sergipe", "SE", "Aracaju", "21910,3"},
  35:          {"Tocantins", "TO", "Palmas", "277620,9"}
  36:      };
  37:   
  38:      public Service () {
  39:   
  40:          //Uncomment the following line if using designed components 
  41:          //InitializeComponent(); 
  42:      }
  43:   
  44:      [WebMethod]
  45:      [ScriptMethod(ResponseFormat=ResponseFormat.Json)]
  46:      public string GetEstados() {
  47:          JavaScriptSerializer js = new JavaScriptSerializer();
  48:          
  49:          List<object> obj = new List<object>();
  50:   
  51:          for (int i = 0; i < estados.GetUpperBound(0); i++)
  52:          {
  53:              Estados estado = new Estados();
  54:              estado.Nome = estados[i, 0];
  55:              estado.Abreviacao = estados[i, 1];
  56:              estado.Capital = estados[i, 2];
  57:              estado.Area = estados[i, 3];
  58:   
  59:              obj.Add(estado);
  60:          }
  61:          
  62:          return js.Serialize(obj);
  63:      }
  64:   
  65:      private class Estados
  66:      {
  67:          public string Nome { get; set; }
  68:          public string Abreviacao { get; set; }
  69:          public string Capital { get; set; }
  70:          public string Area { get; set; }
  71:      }
  72:  }

No método GetEstados() foi utilizada a classe JavaScriptSerializer (linha 47) para criar um objeto que serializa no formato JSON, a lista de objetos Estados.
Na execução do webservice obtemos o seguinte retorno:
webservicejson1
Na figura podemos observar que os dados foram retornados em JSON, mas precisamos ter um cuidado no momento de utilizar o retorno deste webservice, porque mesmo o retorno dos dados estando no formato JSON o webservice na verdade retorna um XML com uma tag <string>, onde no valor da string são retornados os dados no formato JSON.

Para que aplicações possam utilizar os dados é necessário fazer a leitura somente da tag <string>. A aplicação que utilizar os dados deste webservice precisa ler o conteúdo da tag <string> para obter a informação em JSON.

Segue uma figura com os dados já formatados como Json.
webservicejson2