RecyclerView аварийно завершает работу, когда «удаленные или прикрепленные представления не могут быть переработаны»


116

Я использую простую реализацию, RecyclerViewвзятую с веб-сайта Android, с помощью, StaggeredGridLayoutManagerи я продолжаю получать эту ошибку, которая приводит к сбою моего приложения:

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)  

Проще говоря, я буквально имею в виду ту же реализацию, что и на этой странице на их веб-сайте , с той лишь разницей, что макет моего элемента сетки - это ImageViewи пара TextViews, поэтому я не буду утруждать себя репостом своего кода.

Кто-нибудь еще получает эту ошибку и знает, как с ней бороться?


Есть какое-нибудь решение?
Pratik Butani,

Ответы:


191

Эта ошибка возникает, если в вашем XML вы android:animateLayoutChangesустановили значение true и вызываете notifyDataSetChanged()адаптер RecyclerView в коде Java.

Поэтому просто избегайте использования android:animateLayoutChangesс RecyclerViews.


22
тогда как можно использовать функцию animateLayoutChanges в recyclerview?
dhuma1981

Чего вы пытаетесь достичь? Анимация предметов? Если это так, API RecyclerView поддерживает это - взгляните на документацию: developer.android.com/reference/android/support/v7/widget/…
Кеннет,

4
@ dhuma1981, если аниматор элемента установлен через mRecyclerView.setItemAnimator (новый DefaultItemAnimator ()); тогда animateLayoutChanges не обязательно должно быть правдой
Рич Эмер 01

RecyclerViewиспользует DefaultItemAnimatorпо умолчанию.
Бенджамин

-, - У меня эта проблема именно так, как вы ее описали
Ninja Coding

52

Мне тоже приходилось иметь дело с этой аварией, и в моем случае это не имело никакого отношения android:animateLayoutChanges.

В том, что RecyclerViewмы строили, было больше одного вида видов, и некоторые из них были EditTextв них. Через некоторое время мы решили, что проблема связана с фокусом. Эта ошибка возникает при переработке EditTexts, и одна из них сфокусирована.

Естественно, мы пытались очистить фокус, когда новые данные привязываются к переработанному представлению, но это не сработало, пока android:focusableInTouchMode="true"не было включено RecycleView. Фактически, это единственное изменение, которое потребовалось, чтобы эта проблема исчезла.


2
Фантастически решено несколько проблем, связанных с фокусом, которые у меня возникли при использовании EditTexts в RecyclerView. Спасибо!
Rabie Jradi

1
У меня был ACET в recyclerview, и он давит. Этот пост меня спас.
Кай Ван

И у меня нет текстов для редактирования в элементах, но есть флажки. Следует ли мне попробовать, android:focusableInTouchMode="true"потому что это случается только иногда на некоторых устройствах (редко), и я предполагаю, что это не связано с моей проблемой, но трассировка стека для сбоя почти такая же.
Шиванш

Это был мой случай, но настройка android:focusableInTouchMode="true"мне совершенно не помогла. Поэтому я снял фокус с onViewDetachedFromWindowобратного вызова. public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) { if (holder instanceof ItemViewHolder) { ItemViewHolder item = (ItemViewHolder) holder; if (item.editText.isFocused()) item.editText.clearFocus(); } }
artman

24

Я удалил android:animateLayoutChangesсвойство из макета, и проблема была решена.


Я начал получать эту аварию, когда я также поместил android:animateLayoutChangesна свой автофургон.
Mauker

2
Если бы этот флаг был установлен на родительском контейнере (относительный макет). Исправлена ​​проблема.
1911z

@ 1911z, вы говорите, что у вас есть флаг на родительском контейнере, и удаление его оттуда устранило проблему?
RamPrasadBismil

14

Среди причин, по которым кто-либо может столкнуться с этой проблемой, проверьте, установили ли вы атрибут android:animateLayoutChanges="true"RecyclerView. Это приведет к сбою повторного использования и повторного подключения элементов RecyclerView. Удалите его и назначьте атрибут родительскому контейнеру RecyclerView, например LinearLayout / RelativeLayout, и вы увидите, что проблема исчезла.


Я видел этот сбой, даже когда я установил атрибут в родительском контейнере RV.
RamPrasadBismil

@RamPrasadBismil Пожалуйста, опубликуйте свой код, и, может быть, мы сможем его посмотреть?
Рам Айер

12

Это заняло у меня два дня, но я не мог обойти это, в конце концов, мне пришлось отключить предварительную выборку элементов.

При настройке менеджера компоновки вы можете просто вызвать

mGridLayoutManager.setItemPrefetchEnabled(false);

Это заставило меня устранить ошибку. Надеюсь, это будет кому-то полезно.


Работал у меня. Спасибо.
Vicky

1
Меня действительно беспокоит, что люди примут это решение. Вы многое теряете, отключая этот флаг, а ошибка все еще находится в другом месте -
Фелипе Кастильос

9

Я также столкнулся с той же ошибкой при прокрутке RecyclerView: затем я удалил animateLayoutChanges="true"файл макета, чтобы RecyclerViewвсе работало.


8

При использовании липких заголовков slimfit я столкнулся с этой ошибкой. Это было вызвано неправильной установкой первой позиции. Я получил ответ здесь

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

}

просто убедитесь, что вы передаете правильное значение для mSectionFirstPosition


Добро пожаловать в StackOverflow. Не могли бы вы дать полный ответ вместо ссылки?
slfan

что itemздесь?
Ошибки случаются

Это элемент списка, который должен отображаться в представлении ресайклера. Так что в основном я сохраняю первую позицию раздела для каждого элемента списка.
Джаспиндер Каур

8

Я столкнулся с этой проблемой сегодня утром, но не сталкиваюсь с той же причиной, о которой говорилось выше.

С помощью отладки я обнаружил, что представление элемента в моем ViewHolder имеет mParent значения NULL, что в нормальном случае не должно быть пустым (в журнале говорится, что «прикрепленное представление не может быть переработано», я думаю, это означает, что если дочернее представление уже прикреплен к родительскому элементу, это каким-то образом может вызвать сбой при переработке.)

Но я не каждый раз прикреплял дочерний вид вручную. И я обнаружил, что это происходит, когда я пытаюсь раздуть дочернее представление в моем ViewHolder, что-то вроде:

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

И последний параметр attachToRootдолжен быть ложным.

После того, как я изменил его на false, я решил свою проблему.

Кстати, я вижу, что этот сбой произошел только тогда, когда я обновляю свою библиотеку поддержки до последней версии 25.0.0. Раньше я использовал версию 23.4.0 и не видел, чтобы эта проблема возникала. Думаю, в последней библиотеке поддержки нужно что-то изменить.

Надеюсь на эту помощь.


6

В моем случае это произошло из-за того, что у меня была Transitionошибка при попытке изменить размер RecyclerView, потому что программная клавиатура собиралась показать.

Я исправил это, исключив RecyclerView из Transitionс помощью Transition.excludeTarget(R.id.recyclerview, true);


6

Это исключение вызывается по ряду причин. В моем случае это произошло из-за запущенной анимации, поэтому представления все еще прикреплены и не могут быть удалены из представления. Только когда анимация будет завершена, вид можно будет удалить и переработать.

Есть два типа анимации, которые могут повлиять на переработку recyclerview.

1) Том RecyclerView.ItemAnimator- это не должно быть проблемой. Это должно быть в значительной степени безопасным в использовании, поскольку оно проверяет наличие прикрепленных и удаленных видов и правильно обрабатывает переработку.

2) android:animateLayoutChanges="true"или TransitionManager.beginDelayedTransition()или TransitionManager.go () и т. Д. - Эти анимации запускаются сами по себе и захватывают элементы для анимации. Это приводит к принудительному прикреплению представлений до завершения анимации. Reclerview ничего не знает об этих анимациях, так как это выходит за рамки его возможностей. Поэтому они recyclerviewмогут попытаться переработать элемент, полагая, что он может быть переработан должным образом, но проблема в том, что эти API-интерфейсы все еще удерживают представления, пока анимация не завершится.

Если вы используете android:animateLayoutChanges="true"or TransitionManager.beginDelayedTransition()или TransitionManager.go () и т. Д., Просто удалите RecyclerViewи его дочерние элементы из анимации.

Вы можете просто сделать это, взяв Transitionи позвонив

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

Примечание:

Обратите внимание, что важно использовать Transition.excludeChildren()для исключения всех Recyclerviewдочерних элементов из анимации, а не только ее Recyclerviewсамого.


Спасибо! TransitionManager.beginDelayedTransition () был причиной проблемы в моем случае. Вы можете обновить свой пример кода, добавив больше деталей о том, как его использовать Transition.excludeChildren. Вы создаете экземпляр объекта перехода, например:, val transition = AutoTransition()вызываете excludeChildren(recyclerView, true)этот объект и передаете его beginDelayedTransaction() as the second parameter.
Данило Прадо,

5

Я тоже получал эту ошибку всякий раз, когда у меня был animateLayoutChanges = "true" в файле макета для RecyclerView. Удалите этот атрибут и ошибка исчезнет!


Обратите внимание, что это вопрос с 2014 года, и свойства, возможно, уже изменились.
Корашен

2
Нет, это еще не изменилось
Санджай Кушва

4

Хотя в моем случае он удалялся animateOnLayoutChangeиз recyclerView, который исправил сбой, мне по-прежнему нужна была возможность анимировать изменения макета в viewHolder. Чтобы это работало, для LinearLayout' in the view holder needs theanimateOnLayoutChange 'значение true, но мне нужно было notifyItemChangedдля адаптера. Это затем позволило запустить обе анимации layoutTransition (для расширения и свертывания viewHolder), а также позволило избежать исключения исключения. Итак, да, не помещайте animateOnLayoutChange в recylcerView и используйте различные методы уведомления, чтобы включить анимацию по умолчанию при изменении размера представления.


Я пытаюсь сделать то же самое, чтобы оживить расширение элемента, но вызов notifyItemChanged в адаптере заставляет элемент мигать после изменения (анимация работает)! Как это предотвратить?
Flyview

В нашем случае мы заменили наш собственный код с помощью animateOnLayoutChange на расширяемый макет. Он выполняет то же самое, что и мы, но более гибко. github.com/chuross/expandable-layout
kingargyle

3

Решаю эту проблему, удалив parent.addView()вonCreateViewHolder

Это мой код

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

Функция android.support.v7.widget.RecyclerViewRecycler.recyclerViewHolderinternal()проверки, есть ли у моей кнопки родительский элемент или нет. Что, если мы добавим кнопку к родительскому элементу, она также будет назначена RecyclerViewего mParentпеременной.


Я думаю, что это новое требование, я прикрепил к родительскому элементу в версии 24 поддержки библиотеки, после обновления до 25 я получил сбой.
Кирилл Кулаков

1

Я видел, как это произошло со мной, когда я использовал настраиваемый объект ViewHolderдля RecyclerViewадаптера.

Чтобы решить эту проблему, я очистил настраиваемый объект, который в моем случае был таймером onViewRecycled(ViewHolder holder)для адаптера, как показано ниже:

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

Это исправило ошибку.


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: удалить данные из списка.

2 、notifyDataSetChanged: notifyDataSetChanged ();

3 、notifyItemRemoved: показать анимацию.

4 、notifyItemRangeChanged: размер просмотра диапазона и перерисоватьviewHolders(onBindViewHolder methods)


я сделал notifyItemRemovedпри удаленииfooter и сбое приложения, измените его на, notifyDataSetChangedи теперь он работает нормально. спасибо
Сергей

1

В моем случае я использовал TransitionManager.beginDelayedTransition()перед добавлением представления поверх recyclerView. Я удалил TransitionManager.beginDelayedTransition()и никаких сбоев.


1

Я решил эту проблему, позвонив

setHasStableIds(true);

в конструкторе адаптера и переопределение getItemIdв адаптере:

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


0

я использую com.squareup.picasso.RequestCreator

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

для динамического изменения размера ImageView после загрузки изображения из Интернета и сохраняет измененные ширину и высоту, чтобы сохранить размер представления. Я получил это исключение, потому что я сохранил LayoutParamsего Map, а в моем onBindViewHolder я извлек его и напрямую установил в свой ImageView. Я исправляю это, ImmutablePair<Integer, Integer>сохраняя только размер ImageView, а не множество других состояний, и использую следующий код для его восстановления.

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

0

Для меня такая же ошибка, вызванная LayoutTransition на более высоком уровне ViewGroup.


0

Позвольте мне добавить еще одно возможное решение этой проблемы, пожалуйста. У меня была такая же проблема с библиотекой superSlim для липких заголовков RecyclerView. Раньше я MatrixCursorустанавливал данные в RecyclerViewCursorAdapter. Причина этой проблемы заключалась в том, что столбцы идентификаторов равны 0для всех заголовков. Надеюсь, что это поможет кому-то сэкономить пару дней на отладке.


0

В моем случае проблема была из-за неправильной реализации этого метода public long getItemId(int position)(переопределено из RecyclerView.Adapterметода).

Старый код получит два разных идентификатора для одного и того же элемента (в моем случае это элемент нижнего колонтитула), после исправления реализации проблема исчезла.


0

Обходное решение, если причиной исключения является родительский элемент itemView. В коде, где у вас есть notifyItemRemoved (position), удалите itemView из RecyclerView:

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

0

Своеобразный случай, который произошел для меня, заключался в том, что у меня был элемент представления в адаптере, и я был ленив при создании экземпляра представления, которого нет необходимости делать с представлением корзины.

Это также противоречит принципам повторного использования представлений, которые в данном случае сохраняют ссылку на представление. Ниже я привожу краткий пример:

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

это исключение не является причиной

андроид: animateLayoutChanges

или

android: focusableInTouchMode

этот окончательный правильный ответ просто потому, что вы установили НЕПРАВИЛЬНЫЙ 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);

имяLP в порядке. имяLP2 происходит сбой. ошибка здесь.

Пробую все ответы на этой странице. Доверьтесь мне.


0

Я имел этот вопрос , потому что я перезаписать equals()и hashcode()метод ViewHolderиз RecyclerView.ViewHolder путем вычисления равенства данных и хэш - код, то логика рецикл не сделал работу и разбился, я просто удалить перезапись и фиксировано.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.