ListView é um componente básico do Android para exibir listas verticais, disponível desde a API 1 (Android 1.0). O ListView cria uma lista de itens usando ArrayAdapter e suporta layouts de célula personalizados. Vamos entender o que é ListView, como configurá-lo e quais armadilhas este componente antigo, mas ainda usado, esconde. Na IT Sectr, encontramos ListView em projetos legados e mantemos compatibilidade com Android 4.4. Para comparação com a alternativa moderna, leia o artigo sobre RecyclerView.
Principais conclusões
ListView é um ViewGroup do pacote android.widget, projetado para exibir uma lista rolável de itens. O ListView implementa o padrão AdapterView: os dados são passados por meio de um adaptador (Adapter), que converte itens de dados em Views exibidas na tela. O ListView existe no Android SDK desde a primeira versão (API 1) e não requer bibliotecas adicionais.
A arquitetura do ListView é mais simples que a do RecyclerView: LayoutManager é integrado (apenas rolagem vertical), animações de alteração estão ausentes, divisores são definidos pelo atributo XML android:divider. O ListView adiciona automaticamente OverScrollView e destaque de foco, o que simplifica a prototipagem. De acordo com a Statista (2025), cerca de 15% dos aplicativos Android no top 100 da Play Store ainda contêm ListView — principalmente projetos legados com suporte mínimo.
O ListView foi o principal componente de lista no Android de 2008 a 2014, quando o RecyclerView foi apresentado no Google I/O 2014. Apesar de estar parcialmente obsoleto (soft-deprecated), o ListView permanece disponível em todas as versões do Android e não está marcado como @Deprecated no SDK API 35. Se seu projeto suporta Android 4.4 (API 19) e versões anteriores, o ListView é a única opção integrada, pois o RecyclerView requer AndroidX e dependências do Gradle.
ArrayAdapter é o adaptador mais popular para ListView. Ele recebe uma List<T> e um layout para cada item. Por padrão, o ArrayAdapter chama toString() em cada objeto e exibe o texto em simple_list_item_1 (um único TextView). Para exibição personalizada, você precisa substituir getView() ou passar um layout personalizado ao construtor.
// ListView com ArrayAdapter (texto simples)
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val countries = listOf(
"Rússia", "EUA", "Alemanha",
"França", "Japão", "China", "Brasil"
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val adapter = ArrayAdapter<String>(
this,
android.R.layout.simple_list_item_1,
countries
)
binding.listView.adapter = adapter
// Manipulação de clique
binding.listView.onItemClickListener =
AdapterView.OnItemClickListener { _, _, position, _ ->
Toast.makeText(this, countries[position], Toast.LENGTH_SHORT).show()
}
}
}
SimpleAdapter é projetado para listas de dados Map. Ele recebe uma List<Map<String, Any>> e uma matriz from/to para vincular chaves Map a IDs de widgets no layout. O SimpleAdapter é conveniente para protótipos rápidos quando uma classe de adaptador separada não é necessária. A desvantagem é a falta de tipagem segura e complexidade com lógica personalizada.
<!-- Layout com ListView -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@color/divider"
android:dividerHeight="1px"
android:scrollbars="vertical" />
</LinearLayout>
O adaptador personalizado para ListView substitui o método getView(), que o RecyclerView divide em onCreateViewHolder + onBindViewHolder. No ListView, ambas as etapas são combinadas, portanto, o desenvolvedor deve implementar independentemente o padrão ViewHolder por meio de convertView (View reutilizável) e do método setTag().
// Modelo de dados
data class Product(
name: String,
price: String,
imageResId: Int
)
// Adaptador personalizado com ViewHolder
class ProductAdapter(
context: Context,
private val products: List<Product>
) : ArrayAdapter<Product>(context, 0, products) {
// ViewHolder para cache
private class ViewHolder(view: View) {
val nameText: TextView = view.findViewById(R.id.tvName)
val priceText: TextView = view.findViewById(R.id.tvPrice)
val imageView: ImageView = view.findViewById(R.id.ivProduct)
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = convertView ?: LayoutInflater.from(context)
.inflate(R.layout.item_product, parent, false)
.also { it.tag = ViewHolder(it) }
val holder = view.tag as ViewHolder
val product = getItem(position)
holder.nameText.text = product.name
holder.priceText.text = product.price
holder.imageView.setImageResource(product.imageResId)
return view
}
}
Diferença principal do RecyclerView: no ListView, o método getView() é chamado toda vez que um item aparece na tela. Se convertView e ViewHolder não forem usados, cada rolagem cria um novo objeto View, causando vazamentos de memória e lentidão em listas de mais de 50 itens. ViewHolder com view.tag é uma otimização obrigatória para ListView com layouts personalizados.
ListView tem quatro limitações arquitetônicas que o Google corrigiu no RecyclerView: (1) apenas orientação vertical — sem grades, listas horizontais ou cascatas; (2) sem notificações granulares — qualquer alteração de dados aciona um redesenho completo via notifyDataSetChanged(), causando tremulação; (3) sem animação integrada de adicionar/remover itens; (4) sem separação de responsabilidades — LayoutManager é integrado, ItemAnimator está ausente.
<!-- item_product.xml — layout de item do ListView -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="12dp">
<ImageView
android:id="@+id/ivProduct"
android:layout_width="56dp"
android:layout_height="56dp"
android:scaleType="centerCrop" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingStart="12dp">
<TextView
android:id="@+id/tvName"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvPrice"
android:textSize="14sp"
android:textColor="@color/price" />
</LinearLayout>
</LinearLayout>
Implicações práticas: em listas de até 20 itens, o ListView funciona tão rápido quanto o RecyclerView. Com mais de 100 itens, a diferença se torna perceptível: o ListView começa a perder FPS devido à falta de cálculos de diff e ao ViewHolder forçado. Para listas com atualizações frequentes (chat, feed, logs), o ListView não é adequado — use RecyclerView com Paging 3. Na IT Sectr, migramos ListView para RecyclerView sempre que trabalhamos em telas legadas com mais de 50 itens.
ListView fornece um conjunto de atributos XML para configuração rápida sem código: android:divider (cor/drawable do divisor), android:dividerHeight (espessura), android:scrollbars (barras de rolagem), android:fastScrollEnabled (rolagem rápida por manipulador), android:choiceMode (modo de seleção: singleChoice, multipleChoice). O atributo android:entries permite preencher a lista a partir de uma matriz de recursos (string-array) sem adaptador.
| Atributo | Descrição | Exemplo de valor |
|---|---|---|
| android:divider | Cor ou drawable do divisor | @color/gray_light |
| android:dividerHeight | Espessura do divisor em px/dp | 1dp |
| android:entries | Matriz de strings de recursos | @array/countries |
| android:choiceMode | Modo de seleção de item | singleChoice |
| android:fastScrollEnabled | Rolagem rápida | true |
| android:overlapAnchor | Âncora para listas popup (API 21+) | true |
Modo de seleção: choiceMode="singleChoice" ativa o estilo RadioButton onde o item selecionado é destacado. choiceMode="multipleChoice" — caixas de seleção para vários itens. Os dados de seleção estão disponíveis via listView.checkedItemPosition ou listView.checkedItemIds. Importante: com choiceMode, o ListView usa StateListDrawable para o fundo — defina seu próprio seletor se o azul padrão não corresponder ao design.
A escolha entre ListView e RecyclerView depende dos requisitos do projeto. RecyclerView é o padrão moderno, mas ListView pode ser justificado para telas simples com custos mínimos de manutenção de código legado. Comparação em todos os parâmetros principais:
| Parâmetro | ListView | RecyclerView |
|---|---|---|
| Tamanho da biblioteca | Integrado ao SDK (≈60 KB) | AndroidX (≈300 KB com dependências) |
| Versão mínima | API 1 | API 14 (via AndroidX) |
| Orientação | Apenas vertical | Vertical, horizontal, grade, escalonada |
| ViewHolder | Implementação manual | Obrigatório por arquitetura |
| Animação | Apenas transições de fade | DefaultItemAnimator (add, remove, move, change) |
| Atualizações granulares | Não (apenas notifyDataSetChanged) | notifyItemInserted/Removed/Changed |
| Listener de clique | onItemClickListener (integrado) | Via ViewHolder (não integrado) |
| Desempenho | Suficiente para listas de até 50 itens | 60 FPS estáveis para mais de 1000 itens |
Conclusão: para novos projetos, use RecyclerView. Deixe ListView apenas em código legado onde a migração é injustificadamente cara. De acordo com as diretrizes do Material Design 3 (2026), listas de qualquer complexidade devem usar RecyclerView para atender aos padrões modernos de desempenho e acessibilidade.
Perguntas frequentes
ListView não está marcado como @Deprecated no Android SDK API 35, mas o Google não recomenda seu uso em novos projetos (soft-deprecated). O componente permanece funcional em todas as versões do Android. RecyclerView é a substituição oficial desde 2014. Se você está iniciando um novo projeto — escolha RecyclerView. Se está mantendo um existente — ListView continuará funcionando sem alterações.
ListView suporta os métodos addHeaderView(View, Object, boolean) e addFooterView(View, Object, boolean). Headers e footers são adicionados à lista geral de itens, mas não participam do adaptador — eles são sempre exibidos no início e no final da lista. Importante: adicione header/footer antes de definir o adaptador (setAdapter()), caso contrário, eles não serão incluídos. Após adicionar um header, os IDs de posição dos itens são deslocados pelo número de headers.
Tanto ListView quanto ScrollView manipulam gestos verticais, o que cria um conflito: o ScrollView externo intercepta a rolagem e o ListView não rola. Soluções: (1) não aninhe ListView dentro de ScrollView — use RecyclerView com NestedScrollingChild, (2) substitua ScrollView por NestedScrollView (API 21+), (3) use ListView com altura fixa (android:layout_height="...dp") dentro de ScrollView, mas isso quebrará a responsividade.
A limpeza de dados no ListView depende do tipo de adaptador. Para ArrayAdapter: adapter.clear() e adapter.addAll(newList) seguido de adapter.notifyDataSetChanged(). Para adaptador personalizado: atualize a lista de dados e chame notifyDataSetChanged(). Nota: listView.invalidateViews() apenas redesenha as Views, mas não atualiza os dados. Sempre altere os dados através do adaptador, não diretamente.
A altura de um item do ListView é definida no layout do item. Se o layout usar layout_height="wrap_content", a altura se adapta ao conteúdo. Para uma altura fixa para todos os itens, use um adaptador personalizado que define LayoutParams em getView(): view.layoutParams.height = resources.getDimensionPixelSize(R.dimen.itemHeight). Para alturas variadas, use RecyclerView com diferentes tipos de visualização.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também