RecyclerView mengalami error saat "tampilan yang dihapus atau dilampirkan mungkin tidak dapat didaur ulang"


115

Saya menggunakan implementasi sederhana yang RecyclerViewdiambil dari situs web Android menggunakan a StaggeredGridLayoutManagerdan saya terus mendapatkan kesalahan ini yang membuat aplikasi saya mogok:

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)  

Sederhananya, maksud saya itu adalah implementasi yang sama yang diambil dari halaman ini di situs web mereka , satu-satunya perbedaan adalah bahwa tata letak item grid saya adalah ImageViewdan beberapa TextView, jadi saya tidak akan repot-repot memposting ulang kode saya.

Adakah orang lain yang mendapatkan kesalahan ini dan tahu cara mengatasinya?


Anda punya solusi?
Pratik Butani

Jawaban:


191

Kesalahan ini terjadi jika dalam XML Anda telah android:animateLayoutChangesdisetel ke true dan Anda memanggilnotifyDataSetChanged() adaptor RecyclerView dalam kode Java.

Jadi, hindari saja penggunaan android:animateLayoutChangesdengan RecyclerViews.


22
lalu bagaimana cara menggunakan fitur animateLayoutChanges di recyclerview?
dhuma1981

Apa yang ingin Anda capai? Animasi item? Jika demikian, RecyclerView API mendukung ini - lihat dokumentasinya: developer.android.com/reference/android/support/v7/widget/…
Kenneth

4
@ dhuma1981 jika animator item disetel melalui mRecyclerView.setItemAnimator (new DefaultItemAnimator ()); maka animateLayoutChanges tidak harus benar
Rich Ehmer

RecyclerViewdigunakan DefaultItemAnimatorsecara default.
Benjamin

-, - Saya punya masalah ini persis seperti yang Anda gambarkan
Ninja Coding

52

Saya harus berurusan dengan kecelakaan ini juga dan dalam kasus saya itu tidak ada hubungannya dengan android:animateLayoutChanges .

Bangunan yang RecyclerViewkami bangun memiliki lebih dari satu jenis pemandangan di dalamnya dan beberapa ada EditTextdi dalamnya. Setelah beberapa saat kami menyematkan masalah menjadi fokus terkait. Bug ini terjadi saat daur ulangEditText dan salah satunya difokuskan.

Secara alami kami mencoba membersihkan fokus saat data baru sedang terikat ke tampilan daur ulang, tetapi itu tidak berfungsi hingga android:focusableInTouchMode="true"diaktifkan RecycleView. Sebenarnya hanya itu perubahan yang diperlukan pada akhirnya agar masalah ini bisa hilang.


2
Fantastis, memecahkan beberapa masalah terkait fokus yang saya alami saat menggunakan EditTexts di RecyclerView. Terima kasih!
Rabie Jradi

1
Saya punya ACET di recyclerview, dan itu hancur. Posting ini menyelamatkan saya.
Kai Wang

Dan saya tidak memiliki teks editan di item, saya memiliki kotak centang. harus saya coba android:focusableInTouchMode="true"karena itu hanya terjadi kadang-kadang di beberapa perangkat (jarang) dan saya kira itu tidak berhubungan dengan masalah saya tetapi jejak tumpukan untuk crash hampir sama.
Shivansh

Ini adalah kasus saya, tetapi pengaturan android:focusableInTouchMode="true"tidak membantu saya sama sekali. Jadi saya membersihkan fokus dalam onViewDetachedFromWindowpanggilan balik. public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) { if (holder instanceof ItemViewHolder) { ItemViewHolder item = (ItemViewHolder) holder; if (item.editText.isFocused()) item.editText.clearFocus(); } }
seniman

24

Saya menghapus android:animateLayoutChangesdari properti tata letak dan masalah telah diselesaikan.


Saya mulai mengalami kecelakaan ini ketika saya meletakkannya android:animateLayoutChangesdi RV saya juga.
Mauker

2
Apakah bendera ini disetel pada penampung induk (Tata Letak Relatif). Memperbaiki masalah.
1911z

@ 1911z apakah Anda mengatakan Anda memiliki bendera pada wadah induk dan menghapusnya dari sana memperbaiki masalah?
RamPrasadBismil

14

Di antara alasan siapa pun dapat menghadapi masalah ini, periksa apakah Anda telah menyetel atribut android:animateLayoutChanges="true"ke RecyclerView. Ini akan menyebabkan daur ulang dan pemasangan kembali item RecyclerView gagal. Hapus dan tetapkan atribut ke container induk RecyclerView, seperti LinearLayout / RelativeLayout dan Anda akan melihat masalahnya hilang.


Saya melihat kecelakaan ini bahkan ketika saya telah menyetel atribut pada wadah induk RV.
RamPrasadBismil

@RamPrasadBismil Harap posting kode Anda dan mungkin kami dapat melihatnya?
Ram Iyer

12

Saya butuh waktu dua hari tetapi tidak bisa menyiasatinya, pada akhirnya, saya harus menonaktifkan prefetch item.

Saat mengatur manajer tata letak, Anda cukup memanggil

mGridLayoutManager.setItemPrefetchEnabled(false);

Itu membuat kesalahan pergi untuk saya. Semoga bermanfaat bagi seseorang.


Bekerja untuk saya. Terima kasih.
Vicky

1
Saya sangat khawatir tentang orang-orang yang mengadopsi solusi ini. Anda kehilangan banyak jika menonaktifkan bendera ini dan bug masih ada di tempat lain -
Felipe Castilhos

8

Saat menggunakan header lengket slimfit, saya mengalami kesalahan ini. Hal itu disebabkan karena salah setting posisi pertama. Saya mendapat jawabannya di sini

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

}

cukup pastikan Anda memberikan nilai yang benar untuk mSectionFirstPosition


Selamat datang di StackOverflow. Bisakah Anda memberikan jawaban lengkap, bukan hanya tautan?
slfan

ada apa itemdisini?
Bug Terjadi

Ini adalah item daftar yang akan ditampilkan dalam tampilan pendaur ulang. Jadi pada dasarnya saya menyimpan posisi pertama bagian terhadap setiap item daftar.
Jaspinder Kaur

8

Saya menemui masalah ini pagi ini, tetapi saya tidak menghadapi alasan yang sama seperti yang disebutkan di atas.

Melalui debug saya menemukan bahwa tampilan item di ViewHolder saya memiliki mParent dan itu bukan null, yang dalam kasus normal seharusnya tidak ada (itulah yang dikatakan log, "tampilan terlampir tidak dapat didaur ulang", saya pikir itu berarti jika tampilan anak sudah terpasang ke induk, itu akan menyebabkan kegagalan saat mendaur ulang.)

Tapi saya tidak melampirkan tampilan anak setiap kali secara manual. Dan saya menemukan bahwa itu selesai ketika saya mencoba untuk memekarkan tampilan anak di ViewHolder saya, seperti:

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

Dan parameter terakhir attachToRoot harus salah.

Setelah saya mengubahnya menjadi false, saya memperbaiki masalah saya.

Omong-omong, saya hanya melihat kerusakan ini terjadi saat saya meningkatkan pustaka dukungan saya ke versi terbaru 25.0.0. Sebelumnya saya menggunakan versi 23.4.0 dan saya tidak melihat masalah ini terjadi. Saya kira harus ada sesuatu yang berubah di pustaka dukungan terbaru.

Semoga bantuan ini.


8

Saya juga mengalami kesalahan yang sama saat menggulir di RecyclerView: lalu saya menghapus animateLayoutChanges="true"file tata letak agar RecyclerViewsemuanya berfungsi.


6

Dalam kasus saya, itu terjadi karena saya Transitionmenjalankan ketika mencoba mengubah ukuran RecyclerView karena keyboard perangkat lunak akan ditampilkan.

Saya memperbaikinya dengan mengecualikan RecyclerView dari Transitiondengan menggunakan Transition.excludeTarget(R.id.recyclerview, true);


6

Ada sejumlah alasan mengapa pengecualian ini disebut. Dalam kasus saya, itu karena animasi yang berjalan, itulah mengapa tampilan masih terpasang dan tidak dapat dihapus ke tampilan. Hanya setelah animasi selesai maka tampilan dapat dihapus dan didaur ulang.

Ada dua jenis animasi yang dapat memengaruhi daur ulang recyclerview.

1) Apakah RecyclerView.ItemAnimator - ini seharusnya tidak menjadi masalah. Ini seharusnya cukup aman untuk digunakan karena memeriksa tampilan yang dilampirkan dan dihapus serta menangani daur ulang dengan benar.

2) android:animateLayoutChanges="true"atau TransitionManager.beginDelayedTransition()atau TransitionManager.go (), dll. - Animasi ini berjalan sendiri dan memegang item yang akan dianimasikan. Hasil ini memaksa tampilan untuk dilampirkan sampai animasi selesai. Recyclerview tidak memiliki pengetahuan tentang animasi ini karena berada di luar cakupannya. Oleh karena itu, recyclerviewmungkin mencoba untuk mendaur ulang item yang berpikir itu dapat didaur ulang dengan benar tetapi masalahnya adalah bahwa API ini masih memegang tampilan sampai animasi selesai.

Jika Anda menggunakan android:animateLayoutChanges="true"atau TransitionManager.beginDelayedTransition()atau TransitionManager.go (), dll. Cukup hapus RecyclerViewdan turunannya dari animasi.

Anda cukup melakukan ini dengan memegang Transitiondan memanggil

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

catatan:

Perhatikan bahwa penting untuk menggunakan Transition.excludeChildren()mengecualikan semuaRecyclerview anak dari animasi dan bukan hanya Recyclerviewdirinya sendiri.


Terima kasih! TransitionManager.beginDelayedTransition () adalah penyebab masalah dalam kasus saya. Anda dapat memperbarui contoh kode Anda dengan detail lebih lanjut tentang cara menggunakan Transition.excludeChildren. Anda membuat instance objek transisi seperti:, val transition = AutoTransition()panggil excludeChildren(recyclerView, true)objek ini dan teruskan ke beginDelayedTransaction() as the second parameter.
Danilo Prado

5

Saya juga mendapatkan kesalahan ini setiap kali saya memiliki animateLayoutChanges = "true" dalam file layout untuk RecyclerView. Hapus atribut ini dan kesalahan akan hilang!


Harap dicatat bahwa pertanyaan ini berasal dari tahun 2014 dan propertinya mungkin sudah berubah sekarang.
Korashen

2
Tidak, itu belum berubah
Sanjay Kushwah

4

Sementara dalam kasus saya itu menghapus animateOnLayoutChangedari recyclerView yang memperbaiki crash, saya masih membutuhkan kemampuan untuk menganimasikan perubahan tata letak dalam viewHolder. Agar ini bekerja, LinearLayout' in the view holder needs theanimateOnLayoutChange 'menjadi true, tetapi saya perlu ke notifyItemChangedadaptor. Ini kemudian memungkinkan kedua animasi layoutTransition untuk dimulai (untuk meluaskan dan menciutkan viewHolder), dan juga menghindari pengecualian yang dihapus. Jadi ya, hindari meletakkan animateOnLayoutChange di recylcerView dan gunakan berbagai metode notify untuk mengaktifkan animasi default pada perubahan ukuran tampilan.


Saya mencoba melakukan hal yang sama untuk menganimasikan perluasan item, tetapi memanggil notifyItemChanged di adaptor membuat item berkedip setelah perubahan (animasi berfungsi)! Bagaimana Anda mencegahnya?
Flyview

Untuk kasus penggunaan kami, kami akhirnya menukar kode khusus kami menggunakan animateOnLayoutChange, untuk menggunakan tata letak yang dapat diperluas. Itu menyelesaikan hal yang sama seperti yang kami coba capai tetapi jauh lebih fleksibel. github.com/chuross/expandable-layout
kingargyle

3

Aku memecahkan masalah ini dengan menghapus parent.addView()dionCreateViewHolder

Ini kode saya

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

Berfungsi di android.support.v7.widget.RecyclerViewRecycler.recyclerViewHolderinternal()cek apakah tombol saya sudah menjadi orang tua atau belum. Yang mana jika kita menambahkan tombol ke induk, itu juga akan ditetapkan RecyclerViewke mParentvariabelnya.


Saya pikir ini adalah persyaratan baru, saya melampirkan ke induk dalam versi 24 dukungan perpustakaan, setelah diperbarui ke 25 saya mengalami crash.
Kirill Kulakov

1

Saya melihat ini terjadi pada saya ketika saya menggunakan objek khusus di ViewHolderuntukRecyclerView adaptor.

Untuk memperbaiki masalah ini saya membersihkan objek khusus yang dalam kasus saya adalah pengatur waktu onViewRecycled(ViewHolder holder)untuk adaptor seperti di bawah ini:

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

Ini memperbaiki bug.


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 : hapus data dari daftar.

2 、notifyDataSetChanged : notifyDataSetChanged ();

3 、notifyItemRemoved: tampilkan animasi.

4 、notifyItemRangeChanged: rentang ukuran tampilan dan gambar ulangviewHolders(onBindViewHolder methods)


Saya sudah selesai notifyItemRemovedsaat menghapus footerdan aplikasi macet, ubah ke notifyDataSetChangeddan sekarang berfungsi dengan baik. terima kasih
Siarhei

1

Dalam kasus saya, saya menggunakan TransitionManager.beginDelayedTransition()sebelum menambahkan tampilan di atas recyclerView. Saya menghapus TransitionManager.beginDelayedTransition()dan tidak ada crash.


1

Saya memecahkan masalah ini dengan menelepon

setHasStableIds(true);

di konstruktor adaptor dan menimpa getItemIddi adaptor:

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

1

Hapus android:animateLayoutChanges="true"dari recycleview atau setandroid:animateLayoutChanges="false"


0

saya menggunakan com.squareup.picasso.RequestCreator

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

untuk mengubah ukuran ImageView secara dinamis setelah mendownload gambar dari Internet, dan menyimpan lebar dan tinggi yang diubah ukurannya untuk mempertahankan ukuran tampilan. Saya mendapatkan Pengecualian ini karena saya menyimpannya LayoutParamsdi Map, dan di onBindViewHolder, saya mengambilnya dan langsung mengaturnya ke milik saya ImageView. Saya memperbaikinya dengan menggunakan ImmutablePair<Integer, Integer>hanya untuk menyimpan ukuran ImageView daripada banyak negara lain, dan menggunakan kode berikut untuk memulihkannya.

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

0

Bagi saya, bug yang sama disebabkan oleh LayoutTransition pada ViewGroup level yang lebih tinggi.


0

Izinkan saya menambahkan kemungkinan perbaikan lain untuk masalah semacam ini. Saya memiliki masalah yang sama dengan pustaka superSlim untuk header tempel RecyclerView. Saya biasa MatrixCursormengatur data ke RecyclerViewCursorAdapter. Alasan untuk masalah ini adalah kolom ID sama dengan 0untuk semua tajuk. Harapan itu akan membantu seseorang untuk menghemat beberapa hari debugging.


0

Dalam kasus saya, masalahnya adalah karena penerapan metode ini salah public long getItemId(int position)(diganti dari RecyclerView.Adaptermetode).

Kode lama akan mendapatkan dua id berbeda untuk item yang sama (dalam kasus saya ini adalah item footer), setelah memperbaiki implementasi, masalah telah hilang.


0

Solusi solusi jika alasan Exception adalah itemView memiliki induk. Dalam kode, di mana Anda memiliki notifyItemRemoved (position), hapus itemView dari RecyclerView:

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

0

Kasus aneh yang terjadi pada saya adalah bahwa saya memiliki anggota tampilan di adaptor dan saya malas memberi contoh tampilan yang tidak perlu dilakukan dengan tampilan daur ulang.

Ini juga bertentangan dengan prinsip tampilan daur ulang yang sebagai referensi Anda untuk menyimpan tampilan dalam kasus ini. Saya berikan contoh singkat di bawah ini:

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

pengecualian ini bukan penyebab

android: animateLayoutChanges

atau

android: focusableInTouchMode

jawaban terakhir yang benar ini hanya karena Anda menetapkan LayoutParams yang SALAH .

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

nameLP-nya OK. nameLP2 terjadi crash. bug di sini.

Saya mencoba semua jawaban dari halaman ini. percayalah kepadaku.


0

Saya memiliki masalah ini karena saya menimpa equals()dan hashcode()metode ViewHolderdari RecyclerView.ViewHolder dengan menghitung kesetaraan data dan kode hash, maka logika recycle tidak bekerja dan jatuh, saya hanya menghapus menimpa dan tetap.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.