Como corrigir problemas de notifyDataSetChanged / ListView no Android wrapper dinâmico do adaptador

Resumo: Tentando adicionar dinamicamente linhas de cabeçalho a um ListView por meio de um wrapper de adaptador personalizado. O ListView está com problemas para manter a posição de rolagem sincronizada. Projeto de demonstração executável fornecido.

Gostaria de adicionar itens dinamicamente a uma lista com base nos valores em um CursorAdapter, várias posições à frente do que o usuário está visualizando no momento. Para fazer isso, eu tenho um adaptador que envolve o CursorAdapter e mantém o novo conteúdo indexado em um SparseArray. O ListView precisa ser atualizado quando itens são adicionados ao adaptador personalizado, mas eu encontrei muitas armadilhas tentando fazer isso funcionar e gostaria de alguns conselhos.

O projeto de demonstração pode ser baixado aqui:DynamicSectionedList.zip

Na demonstração, os títulos são adicionados dinamicamente, olhando para a frente em 10 locais para encontrar a posição correta em que os itens da lista mudam para a próxima letra. Cada implementação de notifyDataSetChanged tem problemas, conforme descrito:

Demo 1 Esta demonstração mostra a importância de notifyDataSetChanged (). Ao clicar em qualquer coisa, o aplicativo falhará. Isso ocorre devido a alguma verificação de integridade no ListView ...mItemCount != adapter.getItemCount(). Moral é, precisamos notificar a lista de que os dados foram alterados.

Demo 2 O próximo passo natural é notificar o ListView das alterações quando elas ocorrerem. Infelizmente, fazer isso enquanto o ListView está rolando interrompe firmemente toda interação por toque até que o aplicativo saia do modo de toque. Você precisará "arremessar a rolagem" o suficiente para gerar novos títulos para perceber isso. Tocar na tela não fará com que a rolagem pare e, uma vez parado, nenhum dos itens da lista será clicável. Isto é devido a algunsif (!mDataChanged) { /* do very important stuff */ } código em AbsListView.onTouchEvent ().

Demo 3 Para corrigir isso, a Demo 3 introduz um sinalizador pendenteChanges e o Adaptador personalizado obtém um notifyDataSetChangedIfNeeded () que pode ser chamado pelo ListView depois de entrar em um estado "seguro" para alterações. O primeiro ponto em que as alterações devem ser notificadas é em ListView.layoutChildren (), então substitui esse método para notificar primeiro as alterações, se necessário, e depois chamar. Passe rapidamente pelo menos um cabeçalho e clique em um item da lista.

Isso não funciona direito, embora eu não tenha muita certeza do porquê. Tocar ou selecionar um item com o teclado / trackball faz com que a lista seja atualizada sem sincronizar corretamente a posição antiga. Rola para o topo da lista, o que não é aceitável.

Demo 4 O problema de rolagem na Demo 3 pode ser vencido, pelo menos no modo de toque. Ao adicionar uma chamada para notifyDataSetChangedIfNeeded () ao tocar para baixo, a alteração dos dados ocorre em um momento em que todas as interações por toque funcionam conforme o esperado e a posição da lista é sincronizada corretamente.

No entanto, não consigo encontrar um analógico para isso quando o dispositivo não está no modo de toque, sem mencionar o fato de que definitivamente parece um hack. A lista quase sempre rola de volta para o topo; não consigo descobrir o que faz com que ocasionalmente mantenha a posição correta.

Como o Android está lutando comigo a cada passo, sinto que deve haver uma abordagem melhor. Por favor, tente a demonstração, se alguma correção puder ser aplicada para fazê-la funcionar seria ótimo!

Muito obrigado a qualquer um que possa investigar isso, espero que, se conseguirmos que o código funcione, será útil para outros que tentam realizar a mesma otimização para listas com títulos.

questionAnswers(1)

yourAnswerToTheQuestion