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, поэтому я не буду утруждать себя репостом своего кода.

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

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

Ответы:

191

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

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

StackOverflowMaster
источник
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свойство из макета, и проблема была решена.

Özer Özcan
источник
Я начал получать эту аварию, когда я также поместил 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все работало.

user8796389
источник
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 и не видел, чтобы эта проблема возникала. Думаю, в последней библиотеке поддержки нужно что-то изменить.

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

Anthonyeef
источник
6

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

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

Tunji_D
источник
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 и используйте различные методы уведомления, чтобы включить анимацию по умолчанию при изменении размера представления.

Kingargyle
источник
Я пытаюсь сделать то же самое, чтобы оживить расширение элемента, но вызов 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переменной.

эгон12
источник
Я думаю, что это новое требование, я прикрепил к родительскому элементу в версии 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);
    }

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

Androidrp
источник
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;
}
Килиан Бацнер
источник
1

Удалить android:animateLayoutChanges="true"из корзины или установитьandroid:animateLayoutChanges="false"

Раджеш Насит
источник
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);
cmicat
источник
0

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

Mattlaabs
источник
0

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

MistaGreen
источник
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);
Polurival
источник
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
Jonny2Plates
источник
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 происходит сбой. ошибка здесь.

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

Evin
источник
0

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

Ирвин
источник