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: