O RecyclerView trava quando "vistas descartadas ou anexadas não podem ser recicladas"

114

Estou usando uma implementação simples de RecyclerViewtirada do site do Android usando um StaggeredGridLayoutManagere continuo recebendo este erro que trava meu aplicativo:

java.lang.IllegalArgumentException: Scrapped or attached views may not be recycled. isScrap:false isAttached:true
            at android.support.v7.widget.RecyclerView$Recycler.recycleViewHolderInternal(RecyclerView.java:3501)
            at android.support.v7.widget.RecyclerView$LayoutManager.scrapOrRecycleView(RecyclerView.java:5355)
            at android.support.v7.widget.RecyclerView$LayoutManager.detachAndScrapAttachedViews(RecyclerView.java:5340)
            at android.support.v7.widget.StaggeredGridLayoutManager.onLayoutChildren(StaggeredGridLayoutManager.java:572)
            at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:1918)
            at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:2155)
            at android.view.View.layout(View.java:14008)
            at android.view.ViewGroup.layout(ViewGroup.java:4373)
            at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1021)
            at android.view.View.layout(View.java:14008)
            at android.view.ViewGroup.layout(ViewGroup.java:4373)
            at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
            at android.view.View.layout(View.java:14008)
            at android.view.ViewGroup.layout(ViewGroup.java:4373)
            at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
            at android.view.View.layout(View.java:14008)
            at android.view.ViewGroup.layout(ViewGroup.java:4373)
            at android.support.v7.internal.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:502)
            at android.view.View.layout(View.java:14008)
            at android.view.ViewGroup.layout(ViewGroup.java:4373)
            at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
            at android.view.View.layout(View.java:14008)
            at android.view.ViewGroup.layout(ViewGroup.java:4373)
            at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1663)
            at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1521)
            at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
            at android.view.View.layout(View.java:14008)
            at android.view.ViewGroup.layout(ViewGroup.java:4373)
            at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
            at android.view.View.layout(View.java:14008)
            at android.view.ViewGroup.layout(ViewGroup.java:4373)
            at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:1892)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1711)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:989)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4351)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:749)
            at android.view.Choreographer.doCallbacks(Choreographer.java:562)
            at android.view.Choreographer.doFrame(Choreographer.java:532)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735)
            at android.os.Handler.handleCallback(Handler.java:725)
            at android.os.Handler.dispatchMessage(Handler.java:92)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:5041)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:511)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
            at dalvik.system.NativeStart.main(Native Method)  

Por simples, quero dizer literalmente que é a mesma implementação tirada desta página no site deles , a única diferença é que o layout do meu item de grade é um ImageViewe alguns TextViews, então não vou me preocupar em reenviar meu código.

Alguém mais recebendo esse erro e sabe como lidar com ele?

StackOverflowMaster
fonte
Você tem alguma solução?
Pratik Butani

Respostas:

191

Este erro é causado se em seu XML você android:animateLayoutChangesdefinir como verdadeiro e chamar notifyDataSetChanged()o adaptador do RecyclerView no código Java.

Portanto, apenas evite usar android:animateLayoutChangescom RecyclerViews.

StackOverflowMaster
fonte
22
então, como se pode usar o recurso animateLayoutChanges em recyclerview?
dhuma1981
O que você está tentando alcançar? Animação do item? Em caso afirmativo, a API RecyclerView suporta isso - dê uma olhada na documentação: developer.android.com/reference/android/support/v7/widget/…
Kenneth
4
@ dhuma1981 se o animador de item for definido via mRecyclerView.setItemAnimator (new DefaultItemAnimator ()); então animateLayoutChanges não precisa ser verdadeiro
Rich Ehmer
RecyclerViewusa DefaultItemAnimatorpor padrão.
Benjamin
-, - eu tenho esse problema exatamente como você o descreveu
Ninja Coding
52

Eu tive que lidar com esse acidente também e no meu caso não teve nada a ver com isso android:animateLayoutChanges.

O RecyclerViewque estávamos construindo tinha mais de um tipo de vistas e algumas EditTextexibiam nelas. Depois de um tempo, identificamos que o problema estava relacionado ao foco. Esse bug acontece durante a reciclagem de se EditTextum deles está focado.

Naturalmente, tentamos limpar o foco quando novos dados estavam sendo vinculados a uma visualização reciclada, mas isso não funcionou até que android:focusableInTouchMode="true"fosse ativado RecycleView. Na verdade, essa foi a única mudança necessária para que esse problema fosse embora.

Nemanja Kovacevic
fonte
2
Fantástico, resolvi vários problemas relacionados ao foco que tive ao usar EditTexts em um RecyclerView. Obrigado!
Rabie Jradi
1
Eu tinha ACET em exibição de reciclagem, e isso esmaga. Este post me salvou.
Kai Wang
E eu não tenho textos de edição nos itens, mas tenho caixas de seleção. devo tentar android:focusableInTouchMode="true"porque isso acontece apenas às vezes em alguns dispositivos (raramente) e estou supondo que não se relaciona ao meu problema, mas rastreamento de pilha para travamento é quase o mesmo.
Shivansh
Este foi o meu caso, mas a configuração android:focusableInTouchMode="true"não me ajudou em nada. Então, limpei o foco no onViewDetachedFromWindowretorno de chamada. public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) { if (holder instanceof ItemViewHolder) { ItemViewHolder item = (ItemViewHolder) holder; if (item.editText.isFocused()) item.editText.clearFocus(); } }
artman
24

Removi a android:animateLayoutChangespropriedade do layout e o problema foi resolvido.

Özer Özcan
fonte
Comecei a ter este acidente quando coloquei o android:animateLayoutChangesno meu RV também.
Mauker de
2
Este sinalizador foi definido no contêiner pai (Layout Relativo). Corrigido o problema.
1911z
@ 1911z você está dizendo que tinha o sinalizador no contêiner pai e removê-lo de lá corrigiu o problema?
RamPrasadBismil
14

Entre os motivos pelos quais qualquer pessoa pode enfrentar esse problema, verifique se você configurou o atributo android:animateLayoutChanges="true"para o RecyclerView. Isso causará falha na reciclagem e reconexão dos itens do RecyclerView. Remova-o e atribua o atributo ao contêiner pai do RecyclerView, como LinearLayout / RelativeLayout e você verá o problema desaparecer.

Ram Iyer
fonte
Eu vi essa falha mesmo depois de definir o atributo no contêiner pai do RV.
RamPrasadBismil
@RamPrasadBismil Por favor, poste seu código e talvez possamos dar uma olhada nele?
Ram Iyer
12

Levei dois dias, mas não consegui contornar isso, no final, tive que desativar a pré-busca do item.

Ao configurar o gerenciador de layout, você pode simplesmente chamar

mGridLayoutManager.setItemPrefetchEnabled(false);

Isso fez com que o erro desaparecesse para mim. Espero que seja útil para alguém.

Max
fonte
Funcionou para mim. Obrigado.
Vicky
1
Estou muito preocupado com as pessoas adotando essa solução. Você perde muito desabilitando este flag e o bug continua em outro lugar -
Felipe Castilhos
8

Ao usar cabeçalhos pegajosos slimfit, encontrei este erro. Foi causado por definir a primeira posição errada. Eu tenho a resposta aqui

public void onBindViewHolder(MainViewHolder holder, int position) {

  final View itemView = holder.itemView;
  final LayoutManager.LayoutParams params = LayoutManager.LayoutParams.from(itemView.getLayoutParams());

  params.setSlm(LinearSLM.ID);
  params.width = ViewGroup.LayoutParams.MATCH_PARENT;
  params.setFirstPosition(item.mSectionFirstPosition);
  itemView.setLayoutParams(params);

}

apenas certifique-se de estar passando o valor correto para mSectionFirstPosition

Jaspinder Kaur
fonte
Bem-vindo ao StackOverflow. Você poderia fornecer uma resposta completa em vez de apenas um link?
slfan
o que esta itemaqui?
Bugs acontecem em
É o item da lista que deve ser mostrado na visualização do reciclador. Basicamente, estou salvando a primeira posição da seção em relação a cada item da lista.
Jaspinder Kaur
8

Eu conheci esse problema esta manhã, mas não estou enfrentando o mesmo motivo mencionado acima.

Via debug descobri que a visão do item em meu ViewHolder tem mParente não é nula, o que em caso normal deveria ser nenhum (é o que o log diz, "visão anexada não pode ser reciclada", eu acho que significa que se a visão filho já está ligado a um dos pais, de alguma forma causaria falha ao reciclar.)

Mas eu não anexei a visualização infantil todas as vezes manualmente. E descobri que isso é feito quando tento aumentar a visualização filho em meu ViewHolder, algo como:

layoutInflater.inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

E o último parâmetro attachToRoot deve ser falso.

Depois de mudar para false, resolvi meu problema.

A propósito, só vejo que esse travamento aconteceu quando atualizo minha biblioteca de suporte para a versão mais recente 25.0.0. Antes eu estava usando a versão 23.4.0 e não vejo esse problema acontecendo. Eu acho que deve haver algo alterado na biblioteca de suporte mais recente.

Espero que esta ajuda.

Anthonyeef
fonte
8

Eu também encontrei o mesmo erro ao rolar no RecyclerView: then eu removi animateLayoutChanges="true"no arquivo de layout para RecyclerViewentão tudo funcionou.

user8796389
fonte
6

No meu caso aconteceu porque tive uma Transitioncorrida ao tentar redimensionar o RecyclerView porque o teclado do software estava prestes a aparecer.

Corrigi-lo ao excluir o RecyclerView do Transitionusando Transition.excludeTarget(R.id.recyclerview, true);

Tunji_D
fonte
6

Há vários motivos pelos quais essa exceção é chamada. No meu caso, foi devido às animações em execução, é por isso que as visualizações ainda estão anexadas e não puderam ser removidas da visualização. Somente quando a animação for concluída, a vista poderá ser removida e reciclada.

Existem dois tipos de animação que podem afetar a reciclagem da vista de reciclagem.

1) É o RecyclerView.ItemAnimator - este não deve ser o problema. Isso deve ser muito seguro de usar, uma vez que verifica se há pontos de vista anexados e descartados e trata a reciclagem de maneira adequada.

2) android:animateLayoutChanges="true"ou TransitionManager.beginDelayedTransition()ou TransitionManager.go (), etc. - Essas animações são executadas por conta própria e controlam os itens a serem animados. Isso resulta nas vistas sendo forçadas a serem anexadas até que a animação seja concluída. O Recyclerview não tem nenhum conhecimento dessas animações, pois está fora de seu escopo. Portanto, o recyclerviewpode tentar reciclar um item pensando que ele poderia ser reciclado corretamente, mas o problema é que essas APIs ainda estão segurando as visualizações até que a animação seja concluída.

Se você estiver usando android:animateLayoutChanges="true"ou TransitionManager.beginDelayedTransition()ou TransitionManager.go (), etc., basta remover o RecyclerViewe seus filhos da animação.

Você pode simplesmente fazer isso segurando o Transitione chamando

Transition.excludeChildren(yourRecyclerView, true)
Transition.excludeTarget(yourRecyclerView, true)

Nota:

Observe que é importante Transition.excludeChildren()excluir todos os Recyclerviewfilhos da animação e não apenas o Recyclerviewpróprio.

Archie G. Quiñones
fonte
Obrigado! TransitionManager.beginDelayedTransition () foi a causa do problema no meu caso. Você pode atualizar seu exemplo de código com mais detalhes sobre como usar Transition.excludeChildren. Você instancia um objeto de transição como:, val transition = AutoTransition()chama excludeChildren(recyclerView, true)esse objeto e o passa para beginDelayedTransaction() as the second parameter.
Danilo Prado,
5

Eu também recebia esse erro sempre que animateLayoutChanges = "true" no arquivo de layout do RecyclerView. Exclua este atributo e o erro desaparecerá!

rvd
fonte
Observe que esta pergunta é de 2014 e as propriedades podem ter mudado agora.
Korashen
2
Não, ainda não mudou
Sanjay Kushwah
4

Embora no meu caso fosse a remoção animateOnLayoutChangedo recyclerView que corrigiu o travamento, eu ainda precisava da capacidade de animar as alterações de layout dentro do viewHolder. Para fazer isso funcionar, o LinearLayout' in the view holder needs theanimateOnLayoutChange 'to true, mas eu precisava notifyItemChangedpara o adaptador. Isso permitiu que ambas as animações layoutTransition fossem iniciadas (para expandir e recolher o viewHolder) e também evitou a exceção descartada. Portanto, sim, evite colocar o animateOnLayoutChange no recylcerView e use os vários métodos de notificação para habilitar as animações padrão nas alterações de tamanho da visualização.

Kingargyle
fonte
Estou tentando fazer a mesma coisa para animar a expansão do item, mas chamar o notificarItemChanged no adaptador faz o item piscar após a alteração (a animação funciona)! Como você evita isso?
Flyview de
Para nosso caso de uso, acabamos trocando nosso código personalizado usando o animateOnLayoutChange, para usar um layout expansível. Ele realiza a mesma coisa que estávamos tentando realizar, mas de forma mais flexível. github.com/chuross/expandable-layout
kingargyle
3

Eu resolvo este problema removendo parent.addView() emonCreateViewHolder

Este é o meu código

public MyViewHolder onCreateViewwHolder(ViewGroup parent, int viewType)  {
    Button addButton = new Button(context);
    //parent.addView(addButton);
    return new MyViewHolder(addButton);
}

Função em android.support.v7.widget.RecyclerViewRecycler.recyclerViewHolderinternal() verificar se meu botão já tem um pai ou não. Que se adicionarmos o botão ao pai, ele também será atribuído RecyclerViewà sua mParentvariável.

egon12
fonte
Eu acho que é um novo requisito, eu anexei ao pai na versão 24 do suporte da biblioteca, uma vez atualizado para 25 eu tive o travamento.
Kirill Kulakov
1

Eu vi isso acontecer comigo quando usei um objeto personalizado no ViewHolderpara o RecyclerViewadaptador.

Para corrigir o problema, limpei o objeto personalizado que, no meu caso, era um temporizador no onViewRecycled(ViewHolder holder)adaptador, conforme abaixo:

    public void onViewRecycled(ViewHolder holder) {
        if(holder instanceof  EntityViewHolder) {
            if(((EntityViewHolder)holder).timer != null) {
                ((EntityViewHolder) holder).timer.cancel();
            }
        }
        super.onViewRecycled(holder);
    }

Isso corrigiu o bug.

Androidrp
fonte
1
    /**
     * Informs the recycler whether this item can be recycled. Views which are not
     * recyclable will not be reused for other items until setIsRecyclable() is
     * later set to true. Calls to setIsRecyclable() should always be paired (one
     * call to setIsRecyclabe(false) should always be matched with a later call to
     * setIsRecyclable(true)). Pairs of calls may be nested, as the state is internally
     * reference-counted.
     *
     * @param recyclable Whether this item is available to be recycled. Default value
     * is true.
     *
     * @see #isRecyclable()
     */
    public final void setIsRecyclable(boolean recyclable) {
        mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1;
        if (mIsRecyclableCount < 0) {
            mIsRecyclableCount = 0;
            if (DEBUG) {
                throw new RuntimeException("isRecyclable decremented below 0: " +
                        "unmatched pair of setIsRecyable() calls for " + this);
            }
            Log.e(VIEW_LOG_TAG, "isRecyclable decremented below 0: " +
                    "unmatched pair of setIsRecyable() calls for " + this);
        } else if (!recyclable && mIsRecyclableCount == 1) {
            mFlags |= FLAG_NOT_RECYCLABLE;
        } else if (recyclable && mIsRecyclableCount == 0) {
            mFl`enter code here`ags &= ~FLAG_NOT_RECYCLABLE;
        }
        if (DEBUG) {
            Log.d(TAG, "setIsRecyclable val:" + recyclable + ":" + this);
        }
    }
ZhangTengyuan
fonte
1

1 、remove: remove os dados da lista.

2 、notifyDataSetChanged: notificationDataSetChanged ();

3 、notifyItemRemoved: mostra animação.

4 、notifyItemRangeChanged: tamanho da visão do intervalo e redesenhar oviewHolders(onBindViewHolder methods)

ZhangTengyuan
fonte
Eu fiz notifyItemRemovedao remover footere travar o aplicativo, altere para notifyDataSetChangede agora ele funciona bem. obrigado
Siarhei
1

No meu caso, usei o TransitionManager.beginDelayedTransition()antes de adicionar uma visualização no topo do recyclerView. Tirei o TransitionManager.beginDelayedTransition()e sem travar.

Hai nguyen Thanh
fonte
1

Resolvi esse problema ligando para

setHasStableIds(true);

no construtor do adaptador e substituindo getItemIdno adaptador:

@Override
public long getItemId(int position) {
    return position;
}
Kilian Batzner
fonte
1

Remover android:animateLayoutChanges="true"de reciclar ou definirandroid:animateLayoutChanges="false"

Rajesh Nasit
fonte
0

eu uso com.squareup.picasso.RequestCreator

public void into(android.widget.ImageView target,
             Callback callback)

para redimensionar dinamicamente o tamanho do ImageView após baixar a imagem da Internet e salvar a largura e a altura redimensionadas para preservar o tamanho da visualização. Recebi essa exceção porque salvei LayoutParamsem um Map, e em meu onBindViewHolder, recuperei e defini diretamente como meu ImageView. Eu corrijo isso usando ImmutablePair<Integer, Integer>apenas o tamanho de ImageView, em vez de muitos outros estados, e uso o código a seguir para restaurá-lo.

ViewGroup.LayoutParams params = image.getLayoutParams();
params.width = widthAndHeight.getLeft();
params.height = widthAndHeight.getRight();
image.setLayoutParams(params);
cmicat
fonte
0

Para mim, o mesmo bug é causado por um LayoutTransition em um ViewGroup de nível superior.

mattlaabs
fonte
0

Deixe-me adicionar outra solução possível para esse tipo de problema, por favor. Eu tive o mesmo problema com a biblioteca superSlim para cabeçalhos pegajosos em RecyclerView. Eu costumava MatrixCursordefinir os dados para RecyclerViewCursorAdapter. O motivo desse problema era que as colunas de ID são iguais a 0para todos os cabeçalhos. Espero que isso ajude alguém a economizar alguns dias de depuração.

MistaGreen
fonte
0

No meu caso, o problema era devido à implementação incorreta desse método public long getItemId(int position)(substituído do RecyclerView.Adaptermétodo).

O código antigo obterá dois ids diferentes para o mesmo item (no meu caso é o item de rodapé), depois de consertar a implementação, o problema desapareceu.

Musa
fonte
0

Solução alternativa se o motivo da Exceção for o que itemView tem pai. No código, onde você notificouItemRemoved (posição), remova itemView de RecyclerView:

View itemView = mRecyclerView.getLayoutManager().findViewByPosition(position);
if (itemView != null && itemView.getParent() != null) {
    ((ViewGroup) itemView.getParent()).removeView(itemView);
}
notifyItemRemoved(position);
Polurival
fonte
0

Um caso peculiar que ocorreu para mim foi que eu tinha um membro de visualização no adaptador e estava preguiçoso instanciando uma visualização que não há necessidade de fazer com a visualização de reciclagem.

Também vai contra os princípios de reciclar visualizações, que são o armazenamento de uma referência para a visualização neste caso. Dou um exemplo rápido abaixo:

// typically we would do this in a grid view adapter:
View v;
// ...
if(v = null){
v = LayoutInflater.inflate ...;
}

// Now with recycle view there is NO need to store a reference to View
// and lazy instantiate. So get rid of your View v member
Jonny2Plates
fonte
0

esta exceção não é causa de

android: animateLayoutChanges

ou

android: focusableInTouchMode

esta resposta correta final é apenas porque você definiu um LayoutParams WRONG .

    nameLP = new LinearLayout.LayoutParams(context.getResources().getDisplayMetrics().widthPixels, LinearLayout.LayoutParams.WRAP_CONTENT);
    nameLP2 = new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT);

o nameLP está OK. o nameLP2 ocorre o crash .bug está aqui.

Eu tento todas as respostas desta página. Confie em mim.

evin
fonte
0

Eu tive esse problema porque eu substituir equals()e hashcode()método de ViewHolderde RecyclerView.ViewHolder calculando a igualdade de dados e hashcode, então a lógica de reciclagem não funcionou e caiu, eu só remover a sobrescrever e fixa.

Irwin
fonte