Como o mecanismo de reciclagem do ListView funciona

146

Então, eu tenho esse problema que tinha antes e, naturalmente, pedi ajuda aqui . A resposta de Luksprog foi ótima, porque eu não tinha ideia de como o ListView e o GridView se otimizavam com a reciclagem de Views. Assim, com o conselho dele, pude mudar a maneira como adicionei Views ao meu GridView. O problema agora é que tenho algo que não faz sentido. Este é o meu getViewdo meu BaseAdapter:


public View getView(int position, View convertView, ViewGroup parent) {
        if(convertView == null) {
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            convertView = inflater.inflate(R.layout.day_view_item, parent, false);
        }
        Log.d("DayViewActivity", "Position is: "+position);
        ((TextView)convertView.findViewById(R.id.day_hour_side)).setText(array[position]);
        LinearLayout layout = (LinearLayout)convertView.findViewById(R.id.day_event_layout);

        //layout.addView(new EventFrame(parent.getContext()));

        TextView create = new TextView(DayViewActivity.this);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 62, getResources().getDisplayMetrics()), 1.0f);
        params.topMargin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics());
        params.bottomMargin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics());
        create.setLayoutParams(params);
        create.setBackgroundColor(Color.BLUE);
        create.setText("Test"); 
        //the following is my original LinearLayout.LayoutParams for correctly setting the TextView Height
        //new LinearLayout.LayoutParams(0, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60, getResources().getDisplayMetrics()), 1.0f)   
        if(position == 0) {
            Log.d("DayViewActivity", "This should only be running when position is 0. The position is: "+position);
            layout.addView(create);
        }

        return convertView;
    }

}

O problema é que quando eu rolar, isso acontece, e não na posição 0 ... Parece a posição 6 e a posição 8, além de colocar duas na posição 8. Agora, ainda estou tentando entender como usar o ListView e o GridView, o que faço não entendo por que isso está acontecendo. Uma das principais razões pelas quais estou fazendo essa pergunta é ajudar outras pessoas que provavelmente não sabem sobre a reciclagem do ListView e do GridView, ou a maneira como este artigo descreve o mecanismo ScrapView.

insira a descrição da imagem aqui

Mais tarde Editar

Adicionando link a uma conversa no IO do Google, basicamente é tudo o que você precisa para entender como o ListView funciona. Link estava morto em um dos comentários. Portanto, user3427079 foi bom o suficiente para atualizar esse link. Aqui está para fácil acesso.

Andy
fonte
Haha, obrigado por esse link. Começou a assistir-lo agora :)
Andy
Você não implementou completamente o exemplo de código da pergunta anterior. A ideia é que você sempre precise ter um código para reverter as alterações feitas em outras linhas (no seu caso, a posição 0).
Luksprog
Bem, eu entendo que @Luksprog Mas isso explica por que também está colocando o View nas outras posições? Se alguma coisa, eu esperava que apenas a posição 0 tivesse Views duplicadas. Idk, ainda tendo problemas para entender como o ListView está vinculando esse material: /
Andy
1
Reciclar significa que a exibição de linha que acabou de desaparecer (por exemplo, posição 0, depois de rolar uma linha para baixo) da tela pode ser usada mais tarde quando for ListViewnecessário exibir uma nova linha (como continuar a rolar para baixo / para cima). O problema é que a visão que acabou de desaparecer e que será usada em futuras posições necessárias TextViewjá foi acrescentada, esse é o problema. Para resolvê-lo, você deve removê-lo no getViewmétodo se o getViewmétodo for chamado para qualquer outra posição que não 0.
Luksprog
2
O link do post principal está morto, acho que é isso? youtube.com/watch?v=N6YdwzAvwOA
G_V

Respostas:

330

Inicialmente, eu também desconhecia a reciclagem de listview e o mecanismo de uso do convertview, mas depois de um dia inteiro de pesquisa eu entendo os mecanismos da exibição de lista consultando uma imagem do android.amberfog insira a descrição da imagem aqui

Sempre que o seu listview é preenchido por um adaptador, ele basicamente mostra o número de linhas que o listview pode mostrar na tela e o número de linhas não aumenta, mesmo quando você percorre a lista. Esse é o truque que o Android usa para que o listview funcione com mais eficiência e rapidez. Agora, a história interna do listview referente à imagem, como você pode ver, inicialmente o listview possui 7 itens visíveis; em seguida, se você rolar até o item 1 não ficar mais visível, getView () passa essa visualização (ou seja, item1) para o reciclador e você pode usar

System.out.println("getview:"+position+" "+convertView);

dentro do seu

public View getView(final int position, View convertView, ViewGroup parent)
{
    System.out.println("getview:"+position+" "+convertView);
    ViewHolder holder;
    View row=convertView;
    if(row==null)
    {
        LayoutInflater inflater=((Activity)context).getLayoutInflater();
        row=inflater.inflate(layoutResourceId, parent,false);
        
        holder=new PakistaniDrama();
        holder.tvDramaName=(TextView)row.findViewById(R.id.dramaName);
        holder.cbCheck=(CheckBox)row.findViewById(R.id.checkBox);
        
        row.setTag(holder);
        
    }
    else
    {
        holder=(PakistaniDrama)row.getTag();
    }
            holder.tvDramaName.setText(dramaList.get(position).getDramaName());
    holder.cbCheck.setChecked(checks.get(position));
            return row;
    }

Você notará no seu logcat que, inicialmente, o convertview é nulo para todas as linhas visíveis, porque inicialmente não havia visualizações (ou seja, itens) no reciclador; portanto, seu getView () cria uma nova visualização para cada um dos itens visíveis, mas o no momento em que você rolar para cima e o item 1 sair da tela, ele será enviado para o Reciclador com seu estado atual (por exemplo, o 'TextView' do TextView ou no meu caso, se a caixa de seleção estiver marcada, ela será associada à exibição e armazenado em reciclador).

Agora, quando você rolar para cima / para baixo, sua lista não criará uma nova exibição, ela usará a exibição que está no seu reciclador. No seu Logcat, você notará que o 'convertView' não é nulo, porque o seu novo item 8 será desenhado usando o convertview, ou seja, basicamente ele usa a visualização do item 1 do reciclador e infla o item 8 em seu lugar, e você pode observar isso no meu código. Se você tinha uma caixa de seleção e a marca na posição 0 (digamos que o item1 tinha uma caixa de seleção e você a marcou), então, quando você rolar para baixo, verá a caixa de seleção do item 8 já marcada, é por isso que o listview está usando a mesma exibição, não criando um novo para você devido à otimização do desempenho.

Coisas importantes

1 . Nunca definir o layout_heighte layout_widthdo seu listview de wrap_contentcomo getView()irá forçar o seu adaptador para obter alguma criança para medir a altura das vistas a ser desenhado na vista de lista e pode causar algum comportamento inesperado como voltar convertview mesmo a lista não é scrolled.always usar match_parentou fixo largura altura.

2 . Se você quiser usar algum esquema ou vista após a sua exibição de lista e pergunta poderia veio em sua mente, se eu definir o layout_heightque fill_parenta visão após visão de lista não vai aparecer como ele vai para baixo da tela, então o seu melhor para colocar o seu listview dentro de um Por exemplo, Linear Layout, defina a altura e a largura desse layout de acordo com os seus requisitos e faça com que o atributo height e width da sua lista seja o do seu layout (como se a largura do seu layout for 320 e a altura for 280), então a sua listview deve ter a mesma altura e largura. Isso informará ao getView () a altura e a largura exatas das visualizações a serem renderizadas, e o getView () não chamará repetidas vezes algumas linhas aleatórias e outros problemas como retornar a exibição de conversão mesmo antes que a rolagem não aconteça. eu mesmo, a menos que meu listview estivesse dentro do lineaLayout, também estava tendo problemas como repetir a chamada de visualização e converter a visualização como, colocar o Listview no LinearLayout funcionava como mágica para mim. (não sabia o porquê)

01-01 14:49:36.606: I/System.out(13871): getview 0 null
01-01 14:49:36.636: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.636: I/System.out(13871): getview 1 android.widget.RelativeLayout@406082c0
01-01 14:49:36.646: I/System.out(13871): getview 2 android.widget.RelativeLayout@406082c0
01-01 14:49:36.646: I/System.out(13871): getview 3 android.widget.RelativeLayout@406082c0
01-01 14:49:36.656: I/System.out(13871): getview 4 android.widget.RelativeLayout@406082c0
01-01 14:49:36.666: I/System.out(13871): getview 5 android.widget.RelativeLayout@406082c0
01-01 14:49:36.666: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.696: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.706: I/System.out(13871): getview 1 null
01-01 14:49:36.736: I/System.out(13871): getview 2 null
01-01 14:49:36.756: I/System.out(13871): getview 3 null
01-01 14:49:36.776: I/System.out(13871): getview 4 null

Mas agora está resolvido, eu sei, não sou tão bom em explicar, mas como eu dediquei o dia inteiro para entender, pensei que outros iniciantes como eu pudessem obter ajuda da minha experiência e espero que agora vocês tenham um pouco de compreensão da estrutura do ListView como ele funciona, pois é realmente confuso e complicado, então os iniciantes encontraram muitos problemas para entendê-lo

Muhammad Babar
fonte
36
"Eu sei que não sou tão bom em explicar" >>> Os +37 votos nesta resposta me dizem que você fez um excelente trabalho ao explicar. Por favor, continue compartilhando seu conhecimento! ;)
2Dee
1
Grande resposta raja ji +1 de mim .. :)
Ehsan Sajjad
2
Obrigado pela imagem. Depois de ter muitos problemas com o ListView quando comecei com o Android, eu já conhecia o princípio da reciclagem depois de lutar muito com ele. Ainda assim, tirou esta foto e a frase: if you had a checkbox and if you check it at position 0(let say item1 has also a checkbox and you checked it) so when you scroll down you will see item 8 checkbox is already checked,this is why listview is re using the same view not creating a new for you due to performance optimization.para eu perceber meu erro. Melhor publicação sobre a reciclagem do Android ListView que me deparei!
Kevin Cruijssen
1
@MuhammadBabar Sim, eu tenho postado Pergunta: stackoverflow.com/q/30122027/1318946
Pratik Butani
1
@MuhammadBabar awesome explanation man! Isso economiza meu tempo. Só quero saber, o RecyclerViewtambém funciona no mesmo mecanismo? Na minha pergunta stackoverflow.com/questions/47897770/… , eu sei que a tarefa é silenciosamente impossível listView. Devo tentar com recyclerView?
Shambhu
8

Tome cuidado, no padrão Holder, se você definir a posição em seu objeto Holder, você deve defini-la sempre, por exemplo:

@Override
public final View getView(int position, View view, ViewGroup parent) {
    Holder holder = null;
    if (view == null) {
        LayoutInflater inflater = (LayoutInflater) App.getContext()
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        view = inflater.inflate(getContainerView(), parent, false);
        holder = getHolder(position, view, parent);
        holder.setTag(tag);
        view.setTag(holder);
    } else {
        holder = (Holder) view.getTag();
    }
    holder.position = position;
    draw(holder);
    return holder.getView();
}

este é um exemplo de uma classe abstrata, em que

getHolder(position, view, parent);

realiza todas as operações de configuração para o

ImageViews, TextViews, etc..
Ahmed Adel Ismail
fonte
1
Não percebi que todo esse tempo eu estava fazendo. parent.setTag(holder);em vez da maneira correta, que é, #view.setTag(holder);
ArtiomLK #
2

Usando o padrão Holder, você pode conseguir o que deseja:

Você pode encontrar uma descrição desse padrão aqui:

A reciclagem da exibição em lista acontece quando você rola a tela para baixo e os itens acima estão ocultos. Eles são reutilizados para mostrar novos itens de exibição de lista.

Krishna
fonte