Ao desenvolevr interfaces que consomem dados locais no Android, a combinação de SQLite, Cursor, CursorAdapter e ListView é uma abordagem clássica para armazenar, manipular e exibir informações. Para implementar um mecanismo de paginação acionado por gestos de deslizamento, é fundamental compreender como o componente de lista gerencia sua área de visualização e os eventos de toque.
Em vez de calcular a direção do movimento comparando coordenadas de toque ou analisando o valor do primeiro registro, uma estratégia mais robusta consiste em avaliar o estado de renderização do ListView após uma atualização. Podemos classificar a visualização atual em quatro cenários distintos:
- O conteúdo não preenche a tela inteira (banco de dados com poucos registros ou exibição da última página).
- O conteúdo excede a tela e a visualização está ancorada no início da lista.
- O conteúdo excede a tela e a visualização está em uma posição intermediária.
- O conteúdo excede a tela e a visualização está no final dos registros da página atual.
Identificado o estado visual, cruzamos essa informação com o estado do gesto de deslizamento para decidir se uma nova página deve ser carregada. A lógica de paginação do banco de dados é abstraída neste contexto.
Para monitorar a posição da lista, sobrescrevemos o método onScroll da interfcae AbsListView.OnScrollListener:
lista.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScroll(AbsListView view, int primeiroItemVisivel, int quantidadeItensVisiveis, int totalDeItens) {
// primeiroItemVisivel: índice do primeiro item visível na tela (inicia em 0)
// quantidadeItensVisiveis: total de itens visíveis no momento (inclui itens parcialmente exibidos)
// totalDeItens: quantidade total de itens carregados no adapter
if (quantidadeItensVisiveis == totalDeItens) {
// Cenário 1: Os dados não preenchem a tela inteira
estadoAtual = ESTADO_LISTA_CURTA;
} else {
if (primeiroItemVisivel == 0) {
// Cenário 2: Exibindo o topo da lista
estadoAtual = ESTADO_TOPO;
} else if (primeiroItemVisivel + quantidadeItensVisiveis == totalDeItens) {
// Cenário 4: Exibindo o final da lista
estadoAtual = ESTADO_BASE;
} else {
// Cenário 3: Posição intermediária (não requer paginação imediata)
estadoAtual = ESTADO_MEIO;
}
}
}
@Override
public void onScrollStateChanged(AbsListView view, int estadoRolagem) {
// Implementação do estado de rolagem
}
});
Para capturar a intenção de deslizamento do usuário, o uso de setOnTouchListener não é recomendado. Esse método é acionado repetidamente em intervalos curtos durante o movimento e também responde a cliques simples nos itens, tornando a lógica de controle excessivamente complexa e verbosa.
A alternativa ideal é utilizar o callback onScrollStateChanged, que consolida a interação em estados bem definidos, tratando o ato de deslizar como uma única ação contínua.
lista.setOnScrollListener(new AbsListView.OnScrollListener() {
private int contadorDeslizamentos = 0;
@Override
public void onScrollStateChanged(AbsListView view, int estadoRolagem) {
switch (estadoRolagem) {
case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
// A rolagem parou completamente
contadorDeslizamentos++;
textViewStatus.setText("Rolagem finalizada: " + contadorDeslizamentos);
Log.d("LISTA_TAG", "Estado IDLE atingido. Contagem: " + contadorDeslizamentos);
// Aqui pode-se verificar o 'estadoAtual' definido no onScroll
// para disparar o carregamento da próxima ou anterior página.
break;
case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
// O usuário tocou e começou a arrastar
if (contadorDeslizamentos > 5) {
contadorDeslizamentos = 0; // Reset preventivo
}
Log.d("LISTA_TAG", "Estado TOUCH_SCROLL iniciado.");
break;
case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
// O usuário soltou o dedo e a lista continua rolando por inércia
Log.d("LISTA_TAG", "Estado FLING (inércia) ativo.");
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// Lógica de cálculo de estado visual
}
});
Durante os testes, observa-se um comportamento peculiar no Cenário 1 (lista curta): o contador de eventos de rolagem só é alterado se o gesto de deslizamento for iniciado diretamente sobre um item renderizado. Se o usuário deslizar o dedo sobre uma área vazia do ListView, nenhum evento é disparado.
Isso ocorre porque o onScrollStateChanged depende da interação com os filhos (children) do ListView. Esse detalhe também explica a discrepância comum entre os identificadores do banco de dados e os índices da interface: enquanto as chaves primárias no SQLite geralmente iniciam em 1, os índices de posição dos itens no ListView do Android são baseados em zero. Compreender essa distinção e a área de toque válida é crucial para evitar falhas de paginação quando a lista possui poucos elementos.