Noções básicas sobre o RecyclerView setHasFixedSize

136

Estou tendo problemas para entender setHasFixedSize(). Eu sei que ele é usado para otimização quando o tamanho de RecyclerViewnão muda, a partir dos documentos.

O que isso significa? Nos casos mais comuns, um ListViewquase sempre tem um tamanho fixo. Em que casos não seria um tamanho fixo? Isso significa que o espaço real que ocupa na tela cresce com o conteúdo?

SIr Codealot
fonte
Achei esta resposta útil e muito fácil de entender [StackOverflow - rv.setHasFixedSize (true); ] ( stackoverflow.com/questions/28827597/… )
Mustafa Hasan

Respostas:

115

Uma versão muito simplificada do RecyclerView possui:

void onItemsInsertedOrRemoved() {
   if (hasFixedSize) layoutChildren();
   else requestLayout();
}

Este link descreve por que as chamadas requestLayoutpodem ser caras. Basicamente, sempre que os itens são inseridos, movidos ou removidos, o tamanho (largura e altura) do RecyclerView pode mudar e, por sua vez, o tamanho de qualquer outra exibição na hierarquia da exibição. Isso é particularmente problemático se itens forem adicionados ou removidos com frequência.

Evite passes de layout desnecessários, definindo setHasFixedSizecomo true quando a alteração do conteúdo do adaptador não altera sua altura ou largura.


Atualização: O JavaDoc foi atualizado para descrever melhor o que o método realmente faz.

O RecyclerView pode executar várias otimizações se souber com antecedência que o tamanho do RecyclerView não é afetado pelo conteúdo do adaptador. O RecyclerView ainda pode alterar seu tamanho com base em outros fatores (por exemplo, o tamanho do pai), mas esse cálculo não depende do tamanho dos filhos ou do conteúdo do adaptador (exceto o número de itens no adaptador).

Se seu uso do RecyclerView se enquadra nessa categoria, defina-o como {@code true}. Isso permitirá que o RecyclerView evite invalidar todo o layout quando o conteúdo do adaptador mudar.

@param hasFixedSize true se as alterações do adaptador não puderem afetar o tamanho do RecyclerView.

LukaCiko
fonte
162
O tamanho do RecyclerView muda sempre que você adiciona algo, não importa o quê. O que setHasFixedSize faz é garantir que (por entrada do usuário) essa alteração de tamanho do RecyclerView seja constante. A altura (ou largura) do item não será alterada. Cada item adicionado ou removido será o mesmo. Se você não definir isso, ele verificará se o tamanho do item mudou e isso é caro. Apenas esclarecendo, porque esta resposta é confusa.
Arnold Balliu 25/05
9
@ ArnoldB excelente esclarecimento. Eu diria mesmo que isso é uma resposta independente.
Young_souvlaki
4
@ ArnoldB - Eu ainda estou confuso. Você está sugerindo que devemos definir hasFixedSize como true se as larguras / alturas de todos os filhos forem constantes? Se sim, e se houver a possibilidade de que algumas crianças possam ser removidas em tempo de execução (eu tenho um deslize para descartar o recurso) - tudo bem se for verdade?
Jaguar
1
Sim. Porque a largura e a altura do item não estão mudando. Ele está sendo adicionado ou removido. Adicionar ou remover itens não altera seu tamanho.
Arnold Balliu
3
@ ArnoldB Eu não acho que o tamanho (largura / altura) do item seja um problema aqui. Também não verificará o tamanho do item. Apenas informa ao RecyclerView para ligar requestLayoutou não após a atualização do dataSet.
Kimi Chiu
22

Pode confirmar setHasFixedSizese refere ao próprio RecyclerView, e não ao tamanho de cada item adaptado a ele.

Agora você pode usar android:layout_height="wrap_content"um RecyclerView, que, entre outras coisas, permite que um CollapsingToolbarLayout saiba que ele não deve recolher quando o RecyclerView estiver vazio. Isso funciona apenas quando você usa setHasFixedSize(false)o RecylcerView.

Se você usar setHasFixedSize(true)no RecyclerView, esse comportamento para impedir o CollapsingToolbarLayout de colapsar não funcionará, mesmo que o RecyclerView esteja realmente vazio.

Se estiver setHasFixedSizerelacionado ao tamanho dos itens, não deverá ter efeito quando o RecyclerView não tiver itens.

Kevin
fonte
4
Acabei de ter uma experiência que aponta para a mesma direção. Usando um RecyclerView com um GridLayoutManager (3 itens por linha) e layout_height = wrap_content. Quando clico em um botão que adiciona 3 novos itens à lista, a exibição do reciclador não se expande para se ajustar aos novos itens. Em vez disso, ele mantém o mesmo tamanho, e a única maneira de ver os novos itens é rolá-lo. Mesmo que os itens tenham o mesmo tamanho, tive que removê setHasFixedSize(true)-los para expandi-los quando novos itens forem adicionados.
Mateus Gondim
Eu acho que você está certo. No documento, hasFixedSize: set to true if adapter changes cannot affect the size of the RecyclerView.mesmo que o tamanho do item seja alterado, você ainda pode configurá-lo como verdadeiro.
Kimi Chiu
12

O ListView tinha uma função nomeada semelhante que, na minha opinião, refletia informações sobre o tamanho das alturas individuais dos itens da lista. A documentação do RecyclerView afirma claramente que se refere ao tamanho do RecyclerView em si, não ao tamanho de seus itens.

No comentário de origem RecyclerView acima do método setHasFixedSize ():

 * RecyclerView can perform several optimizations if it can know in advance that changes in
 * adapter content cannot change the size of the RecyclerView itself.
 * If your use of RecyclerView falls into this category, set this to true.
dangVarmit
fonte
16
Mas como é definido o "tamanho" do RecyclerView? O tamanho é visível apenas na tela ou o tamanho total do RecyclerView, igual a (soma das alturas dos itens + preenchimento + espaçamento)?
Vicky Chijwani
2
Na verdade, isso precisa de mais informações. Se você remover itens e a vista da reciclagem diminuir, considera-se que o tamanho foi alterado?
Henrique de Sousa
4
Eu pensaria nisso como um TextView pode se apresentar. Se você especificar wrap_content, quando definir o texto, o TextView poderá solicitar uma passagem de layout e alterar a quantidade de espaço que ocupa na tela. Se você especificar match_parent ou uma dimensão fixa, o TextView não solicitará uma passagem de layout porque o tamanho é fixo e a quantidade de texto inserida nunca mudará a quantidade de espaço ocupado. RecyclerView é o mesmo. dicas setHasFixedSize () para RV, ele nunca precisará solicitar passes de layout com base nas alterações nos itens do adaptador.
dangVarmit
1
@dangVarmit boa explicação!
howerknea
6

Wen vamos definir setHasFixedSize(true)em RecyclerViewque os meios tamanho do reciclador é fixo e não é afetada pelo conteúdo do adaptador. E, neste caso, onLayoutnão é chamado de reciclador quando atualizamos os dados do adaptador (mas há uma exceção).

Vamos ao exemplo:

RecyclerViewpossui RecyclerViewDataObserver( encontre a implementação padrão neste arquivo ) com vários métodos, o principal importante é:

void triggerUpdateProcessor() {
    if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
        ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
    } else {
        mAdapterUpdateDuringMeasure = true;
        requestLayout();
    }
}

Este método é chamado se definir setHasFixedSize(true)e atualizar dados de um adaptador via: notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved or notifyItemRangeMoved. Nesse caso, não há chamadas para o reciclador onLayout, mas há chamadas para requestLayouta atualização de crianças.

Porém, se definirmos setHasFixedSize(true)e atualizarmos os dados de um adaptador via notifyItemChanged, haverá uma chamada para onChangeo padrão do reciclador RecyclerViewDataObservere nenhuma chamada para triggerUpdateProcessor. Nesse caso, o reciclador onLayouté chamado sempre que definirmos setHasFixedSize trueou false.

// no calls to triggerUpdateProcessor
@Override
public void onChanged() {
    assertNotInLayoutOrScroll(null);
     mState.mStructureChanged = true;

     processDataSetCompletelyChanged(true);
     if (!mAdapterHelper.hasPendingUpdates()) {
         requestLayout();
     }
}

// calls to triggerUpdateProcessor
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
    assertNotInLayoutOrScroll(null);
    if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
        triggerUpdateProcessor();
    }
}

Como verificar sozinho:

Crie personalizado RecyclerViewe substitua:

override fun requestLayout() {
    Log.d("CustomRecycler", "requestLayout is called")
    super.requestLayout()
}

override fun invalidate() {
    Log.d("CustomRecycler", "invalidate is called")
    super.invalidate()
}

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    Log.d("CustomRecycler", "onLayout is called")
    super.onLayout(changed, l, t, r, b)
}

Defina o tamanho da recicladora para match_parent(em xml). Tente atualizar os dados do adaptador usando replaceDatae replaceOne com a configuração setHasFixedSize(true)e, em seguida false.

// onLayout is called every time
fun replaceAll(data: List<String>) {
    dataSet.clear()
    dataSet.addAll(data)
    this.notifyDataSetChanged()
}

// onLayout is called only for setHasFixedSize(false)
fun replaceOne(data: List<String>) {
    dataSet.removeAt(0)
    dataSet.addAll(0, data[0])
    this.notifyItemChanged(0)
}

E verifique seu log.

Meu log:

// for replaceAll
D/CustomRecycler: requestLayout is called
D/CustomRecycler: onMeasure is called
D/CustomRecycler: onMeasure is called
D/CustomRecycler: onLayout
D/CustomRecycler: requestLayout is called
D/CustomRecycler: requestLayout is called
D/CustomRecycler: onDraw is called

// for replaceOne
D/CustomRecycler: requestLayout is called
D/CustomRecycler: onDraw is called
D/CustomRecycler: requestLayout is called
D/CustomRecycler: onDraw is called

Resumir:

Se definirmos setHasFixedSize(true)e atualizarmos os dados do adaptador notificando um observador de outra maneira que não a chamada notifyDataSetChanged, você terá algum desempenho, porque não há chamadas para o onLayoutmétodo recycler .

bitvale
fonte
Você testou com o RecyclerView de altura usando wrap_content ou match_parent?
Lubos Mudrak
6

Se tivermos um RecyclerViewcom match_parentas height / width , devemos adicionar, setHasFixedSize(true)pois o tamanho do RecyclerViewmesmo não altera a inserção ou exclusão de itens nele.

setHasFixedSize deve ser falsa, se temos um RecyclerView com wrap_contenta altura / largura , pois cada elemento inserido pelo adaptador pode mudar o tamanho do Recyclerdependendo dos itens inseridos / excluídos, por isso, o tamanho da Recyclerserá diferente cada vez que adicionar / excluir Itens.

Para ser mais claro, se usarmos

<android.support.v7.widget.RecyclerView
    android:id="@+id/my_recycler_view"
    android:scrollbars="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

Podemos usar my_recycler_view.setHasFixedSize(true)

<android.support.v7.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

Devemos usar my_recycler_view.setHasFixedSize(false), isso se aplica também se usarmos wrap_contentcomo largura

Gastón Saillén
fonte
3

setHasFixedSize (true) significa que o RecyclerView possui filhos (itens) com largura e altura fixas. Isso permite que o RecyclerView otimize melhor descobrindo a altura e a largura exatas de toda a lista com base no seu adaptador.

Calvin Park
fonte
5
Não foi isso que o @dangVarmit sugeriu.
strangetimes
5
Enganosa, é realmente o tamanho Recycler vista, não o tamanho do conteúdo
Benoit
0

Afeta as animações da visão de reciclagem, se for false.. as animações de inserção e remoção não serão exibidas. portanto, verifique se truevocê adicionou animação para a revisão de reciclagem.

Alaa AbuZarifa
fonte
0

Se o tamanho do RecyclerView (o próprio RecyclerView)

... não depende do conteúdo do adaptador:

mRecyclerView.setHasFixedSize(true);

... depende do conteúdo do adaptador:

mRecyclerView.setHasFixedSize(false);
Alok Singh
fonte