RecyclerView ulega awarii, gdy „odrzucone lub dołączone widoki nie mogą zostać poddane recyklingowi”


115

Używam prostej implementacji RecyclerViewpobranej z witryny Android przy użyciu a StaggeredGridLayoutManageri ciągle otrzymuję ten błąd, który powoduje awarię mojej aplikacji:

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)  

Mówiąc prościej, dosłownie mam na myśli tę samą implementację wziętą z tej strony w ich witrynie internetowej , jedyną różnicą jest to, że układ mojego elementu siatki to ImageViewi kilka TextViews, więc nie będę zawracać sobie głowy ponownym publikowaniem kodu.

Czy ktoś jeszcze otrzymuje ten błąd i wie, jak sobie z nim radzić?


Masz jakieś rozwiązanie?
Pratik Butani

Odpowiedzi:


191

Ten błąd jest spowodowany, jeśli w kodzie XML android:animateLayoutChangesustawiono wartość true i wywołujesz notifyDataSetChanged()adapter RecyclerView w kodzie Java.

Dlatego po prostu unikaj używania android:animateLayoutChangesz RecyclerViews.


22
to jak można wykorzystać funkcję animateLayoutChanges w programie recyclinglerview?
dhuma1981

Co próbujesz osiągnąć? Animacja przedmiotu? Jeśli tak, to RecyclerView API to obsługuje - zajrzyj do dokumentacji: developer.android.com/reference/android/support/v7/widget/ ...
Kenneth,

4
@ dhuma1981, jeśli animator pozycji jest ustawiony przez mRecyclerView.setItemAnimator (new DefaultItemAnimator ()); wtedy animateLayoutChanges nie musi być prawdziwe
Rich Ehmer

RecyclerViewużywa DefaultItemAnimatordomyślnie.
Benjamin

-, - Mam ten problem dokładnie tak, jak go opisałeś
Ninja Coding

52

Miałem też do czynienia z tą awarią iw moim przypadku nie miało to nic wspólnego android:animateLayoutChanges.

RecyclerViewMy budulcowe miał więcej niż jeden typ poglądów w nim i niektóre były uwzględniając EditTextsw nich. Po chwili przypięliśmy ten problem do fokusu. Ten błąd występuje podczas recyklingu EditTexts, a jeden z nich jest skupiony.

Oczywiście próbowaliśmy wyczyścić fokus, gdy nowe dane są wiązane z odtworzonym widokiem, ale to nie zadziałało, dopóki nie android:focusableInTouchMode="true"zostanie włączone RecycleView. Właściwie to jedyna zmiana, która była potrzebna w końcu, aby ten problem zniknął.


2
Fantastyczne, rozwiązane wiele problemów związanych z fokusem, które miałem podczas używania EditTexts w RecyclerView. Dzięki!
Rabie Jradi

1
Miałem ACET w widoku recyklingu i miażdży. Ten post mnie uratował.
Kai Wang

I nie mam edycji w elementach, chociaż mam pola wyboru. powinienem spróbować, android:focusableInTouchMode="true"ponieważ zdarza się to tylko czasami na niektórych urządzeniach (rzadko) i domyślam się, że nie ma to związku z moim problemem, ale śledzenie stosu dla awarii jest prawie takie samo.
Shivansh

To był mój przypadek, ale ustawienie android:focusableInTouchMode="true"wcale mi nie pomogło. Więc wyczyściłem fokus w onViewDetachedFromWindowoddzwonieniu. public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) { if (holder instanceof ItemViewHolder) { ItemViewHolder item = (ItemViewHolder) holder; if (item.editText.isFocused()) item.editText.clearFocus(); } }
artman

24

Usunąłem właściwość android:animateLayoutChangesz układu i problem został rozwiązany.


Zacząłem mieć ten wypadek, kiedy umieściłem również android:animateLayoutChangesna moim RV.
Mauker

2
Gdy ta flaga została ustawiona w kontenerze nadrzędnym (układ względny). Naprawiono problem.
1911 z

@ 1911z Czy mówisz, że masz flagę na kontenerze nadrzędnym i usunięcie jej stamtąd rozwiązało problem?
RamPrasadBismil

14

Wśród powodów, dla których każdy może napotkać ten problem, sprawdź, czy ustawiłeś atrybut android:animateLayoutChanges="true"na RecyclerView. Spowoduje to niepowodzenie recyklingu i ponownego dołączania elementów RecyclerView. Usuń go i przypisz atrybut do kontenera nadrzędnego RecyclerView, takiego jak LinearLayout / RelativeLayout, a problem powinien zniknąć.


Widziałem tę awarię nawet wtedy, gdy ustawiłem atrybut w kontenerze nadrzędnym kampera.
RamPrasadBismil

@RamPrasadBismil Wyślij swój kod, a może uda nam się go obejrzeć?
Ram Iyer

12

Zajęło mi to dwa dni, ale nie mogłem tego obejść, w końcu musiałem wyłączyć wstępne pobieranie elementów.

Podczas ustawiania menedżera układu możesz po prostu zadzwonić

mGridLayoutManager.setItemPrefetchEnabled(false);

To sprawiło, że błąd zniknął dla mnie. Mam nadzieję, że komuś się przyda.


Pracował dla mnie. Dzięki.
Vicky,

1
Naprawdę martwię się, że ludzie przyjmą to rozwiązanie. Dużo tracisz wyłączając tę ​​flagę, a błąd nadal jest w innym miejscu -
Felipe Castilhos

8

Podczas korzystania z lepkich nagłówków slimfit napotkałem ten błąd. Było to spowodowane niewłaściwym ustawieniem pierwszej pozycji. Mam tutaj odpowiedź

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);

}

po prostu upewnij się, że przekazujesz poprawną wartość mSectionFirstPosition


Witamy w StackOverflow. Czy możesz podać pełną odpowiedź zamiast tylko linku?
slfan

co itemtu jest
Błędy wydarzyły się

Jest to pozycja listy, która ma być pokazana w widoku recyklera. Zasadniczo zapisuję pierwszą pozycję sekcji dla każdego elementu listy.
Jaspinder Kaur,

8

Spotkałem się z tym problemem dziś rano, ale nie mam tego samego powodu, co wspomniano powyżej.

Podczas debugowania stwierdziłem, że widok elementu w moim ViewHolder ma mParenti nie jest zerowy, co w normalnym przypadku powinno być brakiem (tak mówi dziennik: „dołączony widok nie może zostać ponownie przetworzony”, myślę, że oznacza to, że jeśli widok podrzędny jest już przywiązany do rodzica, spowodowałoby to niepowodzenie podczas recyklingu).

Ale nie za każdym razem ręcznie załączałem widok dziecka. I stwierdziłem, że jest to zrobione, gdy próbuję zawyżać widok dziecka w moim ViewHolder, na przykład:

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

Ostatni parametr attachToRootpowinien być fałszywy.

Po zmianie na falserozwiązałem problem.

Nawiasem mówiąc, ta awaria wystąpiła tylko wtedy, gdy zaktualizuję bibliotekę pomocy technicznej do najnowszej wersji 25.0.0. Wcześniej używałem wersji 23.4.0 i nie widzę tego problemu. Myślę, że w najnowszej bibliotece wsparcia powinno coś się zmienić.

Mam nadzieję, że to pomoże.


8

Napotkałem również ten sam błąd podczas przewijania RecyclerView: potem usunąłem animateLayoutChanges="true"plik układu, RecyclerViewa potem wszystko działało.


6

W moim przypadku stało się tak, ponieważ miałem Transitionuruchomiony podczas próby zmiany rozmiaru RecyclerView, ponieważ klawiatura oprogramowania miała się wyświetlić.

Naprawiłem to wykluczając RecyclerView z Transitionprzy użyciu Transition.excludeTarget(R.id.recyclerview, true);


6

Istnieje wiele powodów, dla których wywoływany jest ten wyjątek. W moim przypadku było to spowodowane uruchomieniem animacji, przez co widoki są nadal dołączone i nie można ich było usunąć z widoku. Dopiero po zakończeniu animacji widok można było usunąć i ponownie wykorzystać.

Istnieją dwa rodzaje animacji, które mogą wpływać na recykling w widoku recyklingu.

1) Czy RecyclerView.ItemAnimator- to nie powinno być problemem. Powinno to być całkiem bezpieczne w użyciu, ponieważ sprawdza załączone i złomowane widoki oraz prawidłowo obsługuje recykling.

2) android:animateLayoutChanges="true"lub TransitionManager.beginDelayedTransition()lub TransitionManager.go (), itd. - Te animacje działają samodzielnie i przejmują elementy do animacji. Powoduje to wymuszenie dołączania widoków do zakończenia animacji. Widok recyklingu nie ma żadnej wiedzy o tych animacjach, ponieważ znajduje się poza jego zakresem. W związku z tym recyclerviewmogą próbować przetworzyć element, myśląc, że można go prawidłowo poddać recyklingowi, ale problem polega na tym, że te interfejsy API nadal zachowują widoki, dopóki animacja nie zostanie zakończona.

Jeśli używasz android:animateLayoutChanges="true"lub TransitionManager.beginDelayedTransition()lub TransitionManager.go () itp., Po prostu usuń RecyclerViewi jego elementy podrzędne z animacji.

Możesz to po prostu zrobić, chwytając Transitioni dzwoniąc

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

Uwaga:

Zwróć uwagę, że ważne jest, Transition.excludeChildren()aby wykluczyć wszystkie Recyclerviewdzieci z animacji, a nie tylko Recyclerviewsamą.


Dzięki! TransitionManager.beginDelayedTransition () był przyczyną problemu w moim przypadku. Możesz zaktualizować przykładowy kod, dodając więcej szczegółów na temat używania Transition.excludeChildren. Tworzysz instancję obiektu przejścia, takiego jak val transition = AutoTransition():, wywołaj excludeChildren(recyclerView, true)ten obiekt i przekaż go do beginDelayedTransaction() as the second parameter.
Danilo Prado

5

Ja również otrzymywałem ten błąd za każdym razem, gdy miałem animateLayoutChanges = "true" w pliku układu dla RecyclerView. Usuń ten atrybut, a błąd zniknie!


Należy pamiętać, że to pytanie pochodzi z 2014 r., A właściwości mogły ulec zmianie.
Korashen

2
Nie, to jeszcze się nie zmieniło
Sanjay Kushwah

4

Podczas gdy w moim przypadku było to usuwanie animateOnLayoutChangez recyklinguView, które naprawiło awarię, nadal potrzebowałem możliwości animowania zmian układu w ramach elementu viewHolder. Aby to zadziałało, LinearLayout' in the view holder needs theanimateOnLayoutChange 'to true, ale potrzebowałem notifyItemChangedadaptera. Pozwoliło to na uruchomienie obu animacji layoutTransition (w celu rozwijania i zwinięcia elementu viewHolder), a także uniknięcie odrzuconego wyjątku. Więc tak, unikaj umieszczania animateOnLayoutChange w recylcerView i używaj różnych metod powiadamiania, aby włączyć domyślne animacje przy zmianach rozmiaru widoku.


Próbuję zrobić to samo, aby animować rozwinięcie elementu, ale wywołanie notifyItemChanged w adapterze powoduje, że element miga po zmianie (animacja działa)! Jak temu zapobiegasz?
Flyview

W naszym przypadku zakończyliśmy zamianę naszego niestandardowego kodu za pomocą animateOnLayoutChange, aby użyć rozwijanego układu. Osiąga to samo, co próbowaliśmy osiągnąć, ale jest o wiele bardziej elastyczny. github.com/chuross/expandable-layout
kingargyle

3

Rozwiązuję ten problem, usuwając parent.addView()wonCreateViewHolder

To jest mój kod

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

Funkcja przy android.support.v7.widget.RecyclerViewRecycler.recyclerViewHolderinternal()sprawdzaniu, czy mój przycisk ma już rodzica, czy nie. Który jeśli dodamy przycisk do rodzica, zostanie on również przypisany RecyclerViewdo jego mParentzmiennej.


Myślę, że to nowy wymóg, dołączyłem do rodzica w wersji 24 obsługi biblioteki, po aktualizacji do 25 dostałem awarię.
Kirill Kulakov

1

Widziałem to się stało dla mnie, kiedy stosowane niestandardowego obiektu w ViewHolderna RecyclerViewadapterze.

Aby rozwiązać problem, wyczyściłem niestandardowy obiekt, który w moim przypadku był zegarem w onViewRecycled(ViewHolder holder)adapterze, jak poniżej:

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

To naprawiło błąd.


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);
        }
    }

1

1 、remove: usuń dane z listy.

2 、notifyDataSetChanged: notifyDataSetChanged ();

3, notifyItemRemovedanimacja pokazu.

4 notifyItemRangeChanged: Zakres rozmiarów widok i przerysowanieviewHolders(onBindViewHolder methods)


zrobiłem notifyItemRemovedpodczas usuwania footeri awarii aplikacji, zmień to notifyDataSetChangedi teraz działa dobrze. dziękuję
Siarhei

1

W moim przypadku użyłem TransitionManager.beginDelayedTransition()przed dodaniem widoku na wierzchu recyclinglerView. Usunąłem TransitionManager.beginDelayedTransition()i bez awarii.


1

Rozwiązałem ten problem dzwoniąc

setHasStableIds(true);

w konstruktorze adaptera i nadpisywanie getItemIdw adapterze:

@Override
public long getItemId(int position) {
    return position;
}

1

Usuń android:animateLayoutChanges="true"z widoku do recyklingu lub zestawuandroid:animateLayoutChanges="false"


0

używam com.squareup.picasso.RequestCreator

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

aby dynamicznie zmienić rozmiar ImageView po pobraniu obrazu z Internetu i zapisać zmienioną szerokość i wysokość, aby zachować rozmiar widoku. Otrzymałem ten wyjątek, ponieważ zapisałem LayoutParamsw a Map, aw moim onBindViewHolder pobrałem go i bezpośrednio ustawiłem na plik ImageView. Naprawiłem to, używając ImmutablePair<Integer, Integer>tylko do przechowywania rozmiaru ImageView, a nie wielu innych stanów, i używam następującego kodu, aby go przywrócić.

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

0

Dla mnie ten sam błąd spowodowany przez LayoutTransition na wyższym poziomie ViewGroup.


0

Dodam jeszcze jedną możliwą poprawkę tego rodzaju problemu. Miałem ten sam problem z biblioteką superSlim dla lepkich nagłówków RecyclerView. Kiedyś MatrixCursorustawiałem dane na RecyclerViewCursorAdapter. Przyczyną tego problemu było to, że kolumny ID są równe 0dla wszystkich nagłówków. Mam nadzieję, że to pomoże komuś zaoszczędzić kilka dni na debugowaniu.


0

W moim przypadku problem wynikał z nieprawidłowej implementacji tej metody public long getItemId(int position)(nadpisana z RecyclerView.Adaptermetody).

Stary kod otrzyma dwa różne identyfikatory dla tej samej pozycji (w moim przypadku jest to pozycja stopki), po naprawieniu implementacji problem zniknął.


0

Rozwiązanie obejścia, jeśli przyczyną wyjątku jest element nadrzędny itemView. W kodzie, w którym masz notifyItemRemoved (pozycja), usuń itemView z RecyclerView:

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

0

Osobliwy przypadek, który wystąpił u mnie, polegał na tym, że miałem element widoku w adapterze i leniwie tworzyłem wystąpienie widoku, którego nie ma potrzeby robić z widokiem odtwarzania.

Jest to również sprzeczne z zasadami recyklingu poglądów, które przechowują odniesienie do widoku w tym przypadku. Poniżej podaję szybki przykład:

// 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

0

ten wyjątek nie jest przyczyną

android: animateLayoutChanges

lub

android: focusableInTouchMode

ta ostatnia prawidłowa odpowiedź jest taka, że ​​ustawiłeś WRONG LayoutParams .

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

nazwaLP jest OK. nazwaLP2 występuje awaria. błąd jest tutaj.

Próbuję wszystkich odpowiedzi na tej stronie. Zaufaj mi.


0

Miałem ten problem, bo nadpisać equals()i hashcode()metoda ViewHolderz RecyclerView.ViewHolder obliczając równości danych i hashcode, wtedy logika recyklingu nie działa i rozbił się, po prostu usunąć nadpisywania i stałe.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.