RecyclerView กะพริบหลังจากแจ้งเตือน


114

ฉันมี RecyclerView ซึ่งโหลดข้อมูลบางส่วนจาก API รวมถึง URL รูปภาพและข้อมูลบางส่วนและฉันใช้ networkImageView เพื่อโหลดรูปภาพ

@Override
public void onResponse(List<Item> response) {
   mItems.clear();
   for (Item item : response) {
      mItems.add(item);
   }
   mAdapter.notifyDataSetChanged();
   mSwipeRefreshLayout.setRefreshing(false);
}

นี่คือการใช้งานสำหรับ Adapter:

public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) {
        if (isHeader(position)) {
            return;
        }
        // - get element from your dataset at this position
        // - replace the contents of the view with that element
        MyViewHolder holder = (MyViewHolder) viewHolder;
        final Item item = mItems.get(position - 1); // Subtract 1 for header
        holder.title.setText(item.getTitle());
        holder.image.setImageUrl(item.getImg_url(), VolleyClient.getInstance(mCtx).getImageLoader());
        holder.image.setErrorImageResId(android.R.drawable.ic_dialog_alert);
        holder.origin.setText(item.getOrigin());
    }

ปัญหาคือเมื่อเรารีเฟรชใน RecyclerView มันผิดไปชั่วขณะในช่วงเริ่มต้นซึ่งดูแปลก ๆ

ฉันเพิ่งใช้ GridView / ListView แทนและทำงานได้ตามที่ฉันคาดไว้ ไม่มีการตำหนิ

การกำหนดค่าสำหรับ RecycleView ในonViewCreated of my Fragment:

mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
        // use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        mRecyclerView.setHasFixedSize(true);

        mGridLayoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager();
        mGridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                return mAdapter.isHeader(position) ? mGridLayoutManager.getSpanCount() : 1;
            }
        });

        mRecyclerView.setAdapter(mAdapter);

ใครประสบกับปัญหาดังกล่าว? อะไรคือสาเหตุ?


คำตอบ:


127

ลองใช้ ID ที่เสถียรใน RecyclerView.Adapter ของคุณ

setHasStableIds(true)getItemId(int position)และแทนที่

หากไม่มี ID ที่เสถียรหลังจากnotifyDataSetChanged()นั้น ViewHolders มักจะกำหนดให้ไม่อยู่ในตำแหน่งเดียวกัน นั่นคือเหตุผลของการกะพริบในกรณีของฉัน

คุณสามารถหาคำอธิบายที่ดีได้ที่นี่


1
ฉันได้เพิ่ม setHasStableIds (true) ซึ่งแก้ไขการกะพริบ แต่ใน GridLayout ของฉันรายการยังคงเปลี่ยนตำแหน่งเมื่อเลื่อน ฉันควรแทนที่อะไรใน getItemId () ขอบคุณ
BalázsOrbán

3
มีความคิดอย่างไรที่เราจะสร้าง ID?
Mauker

คุณสุดยอดมาก! Google ไม่ใช่ Google ไม่รู้เรื่องนี้หรือพวกเขารู้และไม่ต้องการให้เรารู้! THANK YOU MAN
MBH

1
ทำให้ตำแหน่งรายการยุ่งเหยิงรายการมาจากฐานข้อมูล firebase ตามลำดับที่ถูกต้อง
MuhammadAliJr

1
@Mauker หากวัตถุของคุณมีหมายเลขเฉพาะคุณสามารถใช้สิ่งนั้นหรือถ้าคุณมีสตริงที่ไม่ซ้ำกันคุณสามารถใช้ object.hashCode () นั่นใช้งานได้ดีสำหรับฉัน
Simon Schubert

106

ตามหน้าปัญหานี้ .... มันเป็นภาพเคลื่อนไหวการเปลี่ยนรายการรีไซเคิลเริ่มต้น ... คุณสามารถปิดได้.. ลองทำเช่นนี้

recyclerView.getItemAnimator().setSupportsChangeAnimations(false);

เปลี่ยนในเวอร์ชันล่าสุด

อ้างจากบล็อกนักพัฒนา Android :

โปรดทราบว่า API ใหม่นี้เข้ากันไม่ได้ หากคุณติดตั้ง ItemAnimator ก่อนหน้านี้คุณสามารถขยาย SimpleItemAnimator ซึ่งให้ API เก่าได้โดยการตัด API ใหม่ คุณจะสังเกตเห็นว่าวิธีการบางอย่างถูกลบออกทั้งหมดจาก ItemAnimator ตัวอย่างเช่นหากคุณกำลังเรียกใช้งาน RecyclerView.getItemAnimator () setSupportsChangeAnimations (false) โค้ดนี้จะไม่รวบรวมอีกต่อไป คุณสามารถแทนที่ด้วย:

ItemAnimator animator = recyclerView.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
  ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}

5
@sabeer ยังคงเป็นปัญหาเดิม ไม่สามารถแก้ไขปัญหาได้
Shreyash Mahajan

3
@delive แจ้งให้เราทราบหากคุณพบวิธีแก้ปัญหานี้
Shreyash Mahajan

ฉันใช้ปิกัสโซเป็นบางอย่างไม่ปรากฏกะพริบ

8
Kotlin: (RecyclerView.itemAnimator เป็น? SimpleItemAnimator)? supportChangeAnimations = false
Pedro Paulo Amorim

มันใช้งานได้ แต่ปัญหาอื่น ๆ กำลังตามมา (NPE) java.lang.NullPointerException: พยายามอ่านจากฟิลด์ 'int android.support.v7.widget.RecyclerView $ ItemAnimator $ ItemHolderInfo.left' ในการอ้างอิงวัตถุว่างมีวิธีใดบ้าง เพื่อแก้ปัญหานี้?
Navas pk

46

สิ่งนี้ใช้งานได้ง่าย:

recyclerView.getItemAnimator().setChangeDuration(0);

มันเป็นทางเลือกที่ดีสำหรับcodeItemAnimator animator =cyclelerView.getItemAnimator (); if (animator instanceof SimpleItemAnimator) {((SimpleItemAnimator) animator) .setSupportsChangeAnimations (false); }code
ziniestro

2
หยุดภาพเคลื่อนไหวทั้งหมด ADD และ REMOVE
MBH

11

ฉันมีปัญหาเดียวกันในการโหลดรูปภาพจาก URL บางรายการแล้ว imageView กะพริบ แก้ไขได้โดยใช้

notifyItemRangeInserted()    

แทน

notifyDataSetChanged()

ซึ่งหลีกเลี่ยงการโหลดข้อมูลเก่าที่ไม่มีการเปลี่ยนแปลงเหล่านั้นซ้ำ


9

ลองทำเช่นนี้เพื่อปิดใช้งานภาพเคลื่อนไหวเริ่มต้น

ItemAnimator animator = recyclerView.getItemAnimator();

if (animator instanceof SimpleItemAnimator) {
  ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}

นี่เป็นวิธีใหม่ในการปิดการใช้งานแอนิเมชั่นตั้งแต่ Android รองรับ 23

วิธีเก่านี้จะใช้ได้กับไลบรารีการสนับสนุนเวอร์ชันเก่า

recyclerView.getItemAnimator().setSupportsChangeAnimations(false)

4

สมมติว่าmItemsเป็นคอลเลกชันที่อยู่เบื้องหลังของคุณAdapterทำไมคุณถึงลบทุกอย่างและเพิ่มใหม่? โดยพื้นฐานแล้วคุณกำลังบอกว่าทุกอย่างเปลี่ยนไปดังนั้น RecyclerView จึงย้อนกลับมุมมองทั้งหมดมากกว่าที่ฉันคิดว่าไลบรารีรูปภาพไม่ได้จัดการอย่างถูกต้องโดยที่มันยังคงรีเซ็ตมุมมองแม้ว่าจะเป็น URL ของรูปภาพเดียวกันก็ตาม บางทีพวกเขาอาจมีโซลูชันสำหรับ AdapterView เพื่อให้ทำงานได้ดีใน GridView

แทนที่จะเรียกnotifyDataSetChangedซึ่งจะทำให้เกิดการเชื่อมโยงมุมมองทั้งหมดซ้ำให้เรียกเหตุการณ์การแจ้งเตือนแบบละเอียด (แจ้งเพิ่ม / ลบ / ย้าย / อัปเดต) เพื่อให้ RecyclerView rebind เฉพาะมุมมองที่จำเป็นและจะไม่มีอะไรกะพริบ


3
หรืออาจจะเป็นแค่ "บั๊ก" ใน RecyclerView? เห็นได้ชัดว่าถ้ามันใช้งานได้ดีในช่วง 6 ปีที่ผ่านมากับ AbsListView และตอนนี้มันไม่ได้อยู่ใน RecyclerView หมายความว่ามีบางอย่างไม่ตกลงกับ RecyclerView ใช่ไหม? :) การตรวจสอบอย่างรวดเร็วจะแสดงให้เห็นว่าเมื่อคุณรีเฟรชข้อมูลใน ListView และ GridView พวกเขาจะติดตามมุมมอง + ตำแหน่งดังนั้นเมื่อคุณรีเฟรชคุณจะได้รับมุมมองเดียวกันทุกประการ ในขณะที่ RecyclerView สับเปลี่ยนผู้ถือมุมมองซึ่งทำให้เกิดการกะพริบ
vovkab

การทำงานกับ ListView ไม่ได้หมายความว่าจะถูกต้องสำหรับ RecyclerView ส่วนประกอบเหล่านี้มีสถาปัตยกรรมที่แตกต่างกัน
yigit

1
เห็นด้วย แต่บางครั้งก็ยากที่จะทราบว่ามีการเปลี่ยนแปลงรายการใดบ้างเช่นหากคุณใช้เคอร์เซอร์หรือเพียงแค่รีเฟรชข้อมูลทั้งหมด ดังนั้นผู้รีไซเคิลควรจัดการกรณีนี้อย่างถูกต้องด้วย
vovkab

4

Recyclerview ใช้ DefaultItemAnimator เป็นแอนิเมเตอร์เริ่มต้น ดังที่คุณเห็นจากรหัสด้านล่างพวกเขาเปลี่ยนอัลฟาของผู้ถือมุมมองเมื่อเปลี่ยนรายการ:

@Override
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY) {
    ...
    final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
    ...
    ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
    if (newHolder != null) {
        ....
        ViewCompat.setAlpha(newHolder.itemView, 0);
    }
    ...
    return true;
}

ฉันต้องการเก็บภาพเคลื่อนไหวที่เหลือไว้ แต่ลบ "การสั่นไหว" ออกดังนั้นฉันจึงโคลน DefaultItemAnimator และลบบรรทัดอัลฟ่า 3 บรรทัดด้านบน

ในการใช้แอนิเมเตอร์ใหม่เพียงแค่เรียกใช้ setItemAnimator () บน RecyclerView ของคุณ:

mRecyclerView.setItemAnimator(new MyItemAnimator());

มันลบการกะพริบ แต่ทำให้เกิดการกะพริบด้วยเหตุผลบางประการ
Mohamed Medhat

4

ใน Kotlin คุณสามารถใช้ 'class extension' สำหรับ RecyclerView:

fun RecyclerView.disableItemAnimator() {
    (itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false
}

// sample of using in Activity:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
    // ...
    myRecyclerView.disableItemAnimator()
    // ...
}

1

เฮ้ @ อาลีมันอาจจะเล่นซ้ำช้า ฉันประสบปัญหานี้และแก้ไขด้วยวิธีแก้ปัญหาด้านล่างนี้อาจช่วยคุณได้โปรดตรวจสอบ

คลาสLruBitmapCache.javaถูกสร้างขึ้นเพื่อรับขนาดแคชรูปภาพ

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import com.android.volley.toolbox.ImageLoader.ImageCache;

public class LruBitmapCache extends LruCache<String, Bitmap> implements
        ImageCache {
    public static int getDefaultLruCacheSize() {
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        final int cacheSize = maxMemory / 8;

        return cacheSize;
    }

    public LruBitmapCache() {
        this(getDefaultLruCacheSize());
    }

    public LruBitmapCache(int sizeInKiloBytes) {
        super(sizeInKiloBytes);
    }

    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight() / 1024;
    }

    @Override
    public Bitmap getBitmap(String url) {
        return get(url);
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        put(url, bitmap);
    }
}

VolleyClient.javaคลาส singleton [ขยายแอปพลิเคชัน] เพิ่มด้านล่างโค้ด

ในตัวสร้างคลาสเดี่ยวของ VolleyClient เพิ่มข้อมูลโค้ดด้านล่างเพื่อเริ่มต้น ImageLoader

private VolleyClient(Context context)
    {
     mCtx = context;
     mRequestQueue = getRequestQueue();
     mImageLoader = new ImageLoader(mRequestQueue,getLruBitmapCache());
}

ฉันสร้างเมธอด getLruBitmapCache () เพื่อส่งคืน LruBitmapCache

public LruBitmapCache getLruBitmapCache() {
        if (mLruBitmapCache == null)
            mLruBitmapCache = new LruBitmapCache();
        return this.mLruBitmapCache;
}

หวังว่ามันจะช่วยคุณได้


ขอบคุณสำหรับคำตอบ. นั่นคือสิ่งที่ฉันได้ทำไปแล้วใน VollyClient.java ของฉัน ดูที่: VolleyClient.java
Ali

เพียงตรวจสอบครั้งเดียวโดยใช้คลาส LruCache <String, Bitmap> ฉันคิดว่ามันจะช่วยแก้ปัญหาของคุณได้ ดูLruCache
ผู้เรียน Android

คุณเคยตรวจสอบรหัสที่ฉันแชร์กับคุณในความคิดเห็นหรือไม่? คุณมีอะไรในชั้นเรียนที่ฉันพลาดไป
อาลี

คุณขาดการขยายคลาส LruCache <String, Bitmap> และการลบล้างเมธอด sizeOf () เช่นเดียวกับที่ฉันทำไปส่วนที่เหลือทั้งหมดดูเหมือนจะโอเคสำหรับฉัน
ผู้เรียน Android

โอเคฉันจะลองเร็ว ๆ นี้ แต่คุณช่วยอธิบายหน่อยได้ไหมว่าคุณทำอะไรที่นั่นซึ่งเวทมนตร์ให้คุณและแก้ปัญหาได้หรือไม่? ซอร์สโค้ดสำหรับฉันดูเหมือนว่าเมธอด sizeOf ของคุณควรอยู่ในซอร์สโค้ด
อาลี


1

ในกรณีของฉันไม่มีข้อใดข้างต้นหรือคำตอบจากคำถาม stackoverflow อื่น ๆ ที่มีปัญหาเดียวกัน

ฉันใช้แอนิเมชั่นที่กำหนดเองทุกครั้งที่มีการคลิกรายการซึ่งฉันกำลังเรียกใช้ alertItemChanged (ตำแหน่ง int, Object Payload) เพื่อส่งผ่าน payload ไปยังคลาส CustomAnimator ของฉัน

สังเกตว่ามีเมธอด onBindViewHolder (... ) 2 วิธีที่มีอยู่ใน RecyclerView Adapter onBindViewHolder (... ) เมธอดที่มี 3 พารามิเตอร์จะถูกเรียกก่อนเมธอด onBindViewHolder (... ) ที่มี 2 พารามิเตอร์เสมอ

โดยทั่วไปเราจะแทนที่เมธอด onBindViewHolder (... ) ที่มีพารามิเตอร์ 2 ตัวเสมอและรากหลักของปัญหาคือฉันทำแบบเดียวกันทุกครั้งที่มีการเรียกใช้ alertItemChanged (... ) เมธอด onBindViewHolder (... ) ของเราจะ ถูกเรียกว่าซึ่งฉันกำลังโหลดรูปภาพของฉันใน ImageView โดยใช้ Picasso และนี่คือเหตุผลที่มันโหลดอีกครั้งไม่ว่าจะมาจากหน่วยความจำหรือจากอินเทอร์เน็ตก็ตาม จนกระทั่งโหลดเสร็จมันกำลังแสดงภาพตัวยึดตำแหน่งซึ่งเป็นสาเหตุของการกะพริบเป็นเวลา 1 วินาทีเมื่อใดก็ตามที่ฉันคลิกที่มุมมองรายการ

ต่อมาฉันยังแทนที่เมธอด onBindViewHolder (... ) อื่นที่มีพารามิเตอร์ 3 ตัว ที่นี่ฉันตรวจสอบว่ารายการเพย์โหลดว่างหรือไม่จากนั้นฉันจะส่งคืนการใช้งานระดับซูเปอร์คลาสของวิธีนี้มิฉะนั้นหากมีน้ำหนักบรรทุกฉันแค่ตั้งค่าอัลฟาของ itemView ของผู้ถือเป็น 1

และฉันก็ได้วิธีแก้ปัญหาหลังจากเสียเวลาหนึ่งวันเต็มไปอย่างน่าเศร้า!

นี่คือรหัสของฉันสำหรับเมธอด onBindViewHolder (... ):

onBindViewHolder (... ) พร้อม 2 พารามิเตอร์:

@Override
public void onBindViewHolder(@NonNull RecyclerAdapter.ViewHolder viewHolder, int position) {
            Movie movie = movies.get(position);

            Picasso.with(context)
                    .load(movie.getImageLink())
                    .into(viewHolder.itemView.posterImageView);
    }

onBindViewHolder (... ) ที่มี 3 พารามิเตอร์:

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
        if (payloads.isEmpty()) {
            super.onBindViewHolder(holder, position, payloads);
        } else {
            holder.itemView.setAlpha(1);
        }
    }

นี่คือรหัสของวิธีการที่ฉันเรียกใน onClickListener ของ viewHolder itemView ใน onCreateViewHolder (... ):

private void onMovieClick(int position, Movie movie) {
        Bundle data = new Bundle();
        data.putParcelable("movie", movie);

        // This data(bundle) will be passed as payload for ItemHolderInfo in our animator class
        notifyItemChanged(position, data);
    }

หมายเหตุ: คุณสามารถรับตำแหน่งนี้ได้โดยเรียกเมธอด getAdapterPosition () ของ viewHolder ของคุณจาก onCreateViewHolder (... )

ฉันยังได้แทนที่เมธอด getItemId (ตำแหน่ง int) ดังนี้:

@Override
public long getItemId(int position) {
    Movie movie = movies.get(position);
    return movie.getId();
}

และเรียกsetHasStableIds(true);วัตถุอะแดปเตอร์ของฉันในกิจกรรม

หวังว่านี่จะช่วยได้หากคำตอบข้างต้นไม่ได้ผล!


1

ในกรณีของฉันมีปัญหาที่ง่ายกว่ามาก แต่มันสามารถดู / รู้สึกเหมือนกับปัญหาข้างต้นมาก ฉันได้แปลง ExpandableListView เป็น RecylerView ด้วย Groupie แล้ว (โดยใช้คุณสมบัติ ExpandableGroup ของ Groupie) เค้าโครงเริ่มต้นของฉันมีส่วนดังนี้:

<androidx.recyclerview.widget.RecyclerView
  android:id="@+id/hint_list"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:background="@android:color/white" />

เมื่อตั้งค่า layout_height เป็น "wrap_content" ภาพเคลื่อนไหวจากกลุ่มที่ขยายไปยังกลุ่มที่ยุบจะรู้สึกเหมือนจะกะพริบ แต่จริงๆแล้วเป็นเพียงการเคลื่อนไหวจากตำแหน่งที่ "ไม่ถูกต้อง" (แม้ว่าจะลองคำแนะนำส่วนใหญ่ในชุดข้อความนี้แล้วก็ตาม)

อย่างไรก็ตามเพียงแค่เปลี่ยน layout_height เป็น match_parent เช่นนี้จะช่วยแก้ปัญหาได้

<androidx.recyclerview.widget.RecyclerView
  android:id="@+id/hint_list"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@android:color/white" />

0

ฉันมีปัญหาที่คล้ายกันและสิ่งนี้ใช้ได้ผลสำหรับฉันคุณสามารถเรียกวิธีนี้เพื่อกำหนดขนาดสำหรับแคชรูปภาพ

private int getCacheSize(Context context) {

    final DisplayMetrics displayMetrics = context.getResources().
            getDisplayMetrics();
    final int screenWidth = displayMetrics.widthPixels;
    final int screenHeight = displayMetrics.heightPixels;
    // 4 bytes per pixel
    final int screenBytes = screenWidth * screenHeight * 4;

    return screenBytes * 3;
}

0

สำหรับแอปพลิเคชันของฉันฉันมีข้อมูลบางอย่างเปลี่ยนแปลงไป แต่ฉันไม่ต้องการให้มุมมองทั้งหมดกะพริบ

ฉันแก้ไขได้โดยการจาง oldview ลง 0.5 alpha และเริ่ม newview alpha ที่ 0.5 สิ่งนี้สร้างการเปลี่ยนแปลงที่นุ่มนวลขึ้นโดยไม่ทำให้มุมมองหายไปอย่างสมบูรณ์

น่าเสียดายเนื่องจากการใช้งานส่วนตัวฉันไม่สามารถซับคลาส DefaultItemAnimator เพื่อทำการเปลี่ยนแปลงนี้ได้ดังนั้นฉันจึงต้องโคลนโค้ดและทำการเปลี่ยนแปลงต่อไปนี้

ใน animateChange:

ViewCompat.setAlpha(newHolder.itemView, 0);  //change 0 to 0.5f

ใน animateChangeImpl:

oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() { //change 0 to 0.5f

0

การใช้วิธีการดูรีไซเคิลที่เหมาะสมเพื่ออัปเดตมุมมองจะช่วยแก้ปัญหานี้ได้

ขั้นแรกทำการเปลี่ยนแปลงในรายการ

mList.add(item);
or mList.addAll(itemList);
or mList.remove(index);

แล้วแจ้งโดยใช้

notifyItemInserted(addedItemIndex);
or
notifyItemRemoved(removedItemIndex);
or
notifyItemRangeChanged(fromIndex, newUpdatedItemCount);

หวังว่านี่จะช่วยได้ !!


ไม่ได้อย่างแน่นอน. หากคุณทำการอัปเดตหลาย ๆ วินาที (การสแกน BLE ในกรณีของฉัน) จะไม่ทำงานเลย ฉันใช้เวลาหนึ่งวันในการอัปเดต RecyclerAdapter อึนี้ ... ดีกว่าที่จะเก็บ ArrayAdapter เป็นเรื่องน่าเสียดายที่ไม่ได้ใช้รูปแบบ MVC แต่อย่างน้อยก็เกือบจะใช้งานได้
Gojir4


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.