सूचना के बाद पलक झपकते RecyclerView ()


113

मेरे पास एक RecyclerView है जो एपीआई से कुछ डेटा लोड करता है, जिसमें एक छवि url और कुछ डेटा शामिल है, और मैं नेटवर्कइमेज व्यू का उपयोग आलसी लोड छवि के लिए करता हूं।

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

यहाँ एडाप्टर के लिए कार्यान्वयन है:

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

समस्या यह है कि जब हम पुनर्नवीनीकरण दृश्य में ताज़ा होते हैं, तो यह शुरुआत में बहुत कम समय के लिए धुंधला हो जाता है जो अजीब लगता है।

मैंने इसके बजाय केवल 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);

किसी को भी इस तरह की समस्या का सामना करना पड़ा? क्या कारण हो सकता है?


जवाबों:


126

अपने RecyclerView.Adcape में स्थिर आईडी का उपयोग करने का प्रयास करें

setHasStableIds(true)और ओवरराइड करें getItemId(int position)

स्थिर आईडी के बिना, के बाद notifyDataSetChanged(), ViewHolders आमतौर पर एक ही स्थिति के लिए नहीं सौंपा। यही मेरे मामले में पलक झपकने का कारण था।

आप यहाँ एक अच्छी व्याख्या पा सकते हैं


1
मैंने setHasStableIds (ट्रू) को जोड़ा, जिसने झिलमिलाहट को हल किया, लेकिन मेरे ग्रिडलाइउट में, आइटम अभी भी स्क्रॉलिंग में जगह बदलते हैं। मुझे getItemId () में ओवरराइड क्या करना चाहिए? धन्यवाद
बालाज़्स ओर्बन

3
हमें आईडी बनाने के बारे में कोई विचार करना चाहिए?
मौकर

आप कमाल के है! Google नहीं है। Google या तो इस बारे में नहीं जानते हैं या वे जानते हैं और हमें नहीं जानना चाहते हैं! आपको धन्यवाद
MBH

1
आइटम स्थिति को गड़बड़ाना, आइटम फायरबेस डेटाबेस से सही क्रम में आता है।
MuhammadAliJr

1
@Mauker यदि आपकी ऑब्जेक्ट में एक अद्वितीय संख्या है जिसका आप उपयोग कर सकते हैं या यदि आपके पास एक अद्वितीय स्ट्रिंग है तो आप object.hashCode () का उपयोग कर सकते हैं। यह मेरे लिए पूरी तरह से ठीक काम करता है
शमौन Schubert

106

इस समस्या पृष्ठ के अनुसार .... यह डिफ़ॉल्ट रीसायकल आइटम परिवर्तन एनीमेशन है ... आप इसे बंद कर सकते हैं .. यह प्रयास करें

recyclerView.getItemAnimator().setSupportsChangeAnimations(false);

नवीनतम संस्करण में बदलें

Android डेवलपर ब्लॉग से उद्धृत :

ध्यान दें कि यह नया एपीआई पिछड़ा संगत नहीं है। यदि आपने पहले एक ItemAnimator लागू किया है, तो आप इसके बजाय SimpleItemAnimator का विस्तार कर सकते हैं, जो नए API को रैप करके पुराने API प्रदान करता है। आप यह भी देखेंगे कि कुछ तरीकों को पूरी तरह से ItemAnimator से हटा दिया गया है। उदाहरण के लिए, यदि आप recyclerView.getItemAnimator ()। SetSupportsChangeAnimations (false) कह रहे थे, तो यह कोड अब संकलित नहीं होगा। आप इसे बदल सकते हैं:

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

5
@sabeer अभी भी एक ही मुद्दा है। यह मुद्दे को हल नहीं कर रहा है।
श्रेयांश महाजन

3
@Delive मुझे बताएं कि क्या आपको इसका कोई हल मिला है
श्रेयांश महाजन

मैं पिकासो का उपयोग कर रहा हूं, इसकी कुछ, झपकी नहीं दिखाई दे रही है।

8
कोटलिन: (recyclerView.itemAnimator as? SimpleItemAnimator )
पेड्रो पाउलो एमोरिम

यह काम कर रहा है, लेकिन अन्य मुद्दा आ रहा है (NPE) java.lang.NullPointerException: फ़ील्ड से पढ़ने का प्रयास 'int android.support.v7.widget.RecyclerView $ ItemAnimator' ItemHolderInfo.left 'एक अशक्त वस्तु संदर्भ पर किसी भी तरह से है। इसे हल करने के लिए?
नवस

46

यह बस काम किया:

recyclerView.getItemAnimator().setChangeDuration(0);

यह codeItemAnimator एनिमेटर = recyclerView.getItemAnimator () का एक अच्छा विकल्प है ; if (एनिमेटर इन्स्टॉफ़ SimpleItemAnimator) {((SimpleItemAnimator) एनिमेटर) .setSupportsChangeAnimations (false); }code
ziniestro

1
सभी एनिमेशन ADD और REMOVE
MBH

11

मेरे पास कुछ उरलों से लोड करने की छवि समान है और फिर 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 सभी विचारों को याद दिलाता है क्योंकि मुझे लगता है कि छवि पुस्तकालय इसे ठीक से नहीं संभालता है जहां यह अभी भी दृश्य को रीसेट करता है, भले ही यह एक ही छवि यूआरएल हो। हो सकता है कि उन्होंने AdapterView के समाधान में कुछ बेक किया हो ताकि यह GridView में ठीक काम करे।

कॉल करने के बजाय, notifyDataSetChangedजो सभी दृश्यों को फिर से बाइंड करने का कारण बनेगा, बारीक सूचित घटनाओं (सूचित जोड़ा / हटा / स्थानांतरित / अपडेट) को कॉल करें, ताकि RecyclerView केवल आवश्यक विचारों को रिबंड करेगा और कुछ भी नहीं टिमटिमाएगा।


3
या शायद यह RecyclerView में सिर्फ एक "बग" है? जाहिर है अगर यह पिछले 6 वर्षों के लिए AbsListView के साथ ठीक काम करता है और अब यह RecyclerView के साथ नहीं है, इसका मतलब है कि RecyclerView के साथ कुछ ठीक नहीं है, है ना? :) इसमें त्वरित रूप से पता चलता है कि जब आप सूची दृश्य और ग्रिड दृश्य में डेटा ताज़ा करते हैं तो वे दृश्य + स्थिति पर नज़र रखते हैं, इसलिए जब आप ताज़ा करते हैं तो आपको ठीक उसी दृश्यधारक मिलेगा। जबकि RecyclerView दृश्य धारकों को फेरबदल करता है, जिससे टिमटिमाता है।
वोवकाब

ListView पर काम करने का मतलब यह नहीं है कि यह RecyclerView के लिए सही है। इन घटकों में अलग-अलग आर्किटेक्चर हैं।
यिगित

1
सहमत हूं, लेकिन कभी-कभी यह जानना बहुत मुश्किल है कि किन चीजों को बदल दिया जाता है, उदाहरण के लिए यदि आप कर्सर का उपयोग करते हैं या आप सिर्फ आपको पूरे डेटा को ताज़ा करते हैं। तो recyclerview को इस मामले को भी सही ढंग से संभालना चाहिए।
वोवकाब

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 अल्फा लाइनों को हटा दिया।

नए एनिमेटर का उपयोग करने के लिए बस कॉल करें। ItemAnimator () को अपने पुनर्नवीनीकरण दृश्य पर:

mRecyclerView.setItemAnimator(new MyItemAnimator());

इसने पलक को हटा दिया लेकिन इसने किसी कारण से एक चंचल प्रभाव डाला।
मोहम्मद मेधात

4

Kotlin में आप 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

अरे @Ali यह देर से फिर से खेलना हो सकता है। मैंने इस मुद्दे का भी सामना किया और नीचे दिए गए समाधान के साथ हल किया, इससे आपको जाँच करने में मदद मिल सकती है।

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 एकल वर्ग [अनुप्रयोग का विस्तार करता है] कोड के नीचे जोड़ा गया

VolleyClient सिंगलटन क्लास कंस्ट्रक्टर में इमेजप्लेडर को इनिशियलाइज़ करने के लिए स्निपेट के नीचे जोड़ें

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

मैंने LruBitmapCache को वापस करने के लिए getLruBitmapCache () विधि बनाई

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

आशा है कि आपकी मदद करने जा रहा है।


आपके उत्तर के लिए धन्यवाद। यही मैंने अपने VollyClient.java में वास्तव में किया है। पर एक नज़र रखना: वोलेक्लिएंट.जावा
अली

बस LruCache <स्ट्रिंग, बिटमैप> वर्ग का उपयोग करने के साथ एक बार जांच लें, मुझे लगता है कि यह आपकी समस्या को हल करने वाला है। पर एक नजर डालें LruCache
एंड्रॉयड शिक्षार्थी

क्या आपने एक बार उस कोड की जांच की जो मैंने आपके साथ टिप्पणी में साझा किया था? आपकी कक्षा में ऐसा क्या है जो मैं वहां से चूक गया?
अली

आपको LruCache <String, Bitmap> class और overriding sizeOf () तरीका याद आ रहा है जैसे मैंने कैसे किया, शेष सभी मेरे लिए ठीक हैं।
Android शिक्षार्थी

ठीक है, मैं इसे बहुत जल्द एक कोशिश देता हूं, लेकिन क्या आप मुझे समझा सकते हैं कि आपने वहां क्या किया है जिसने आपके लिए जादू किया और समस्या को हल किया? sourcecode मेरे लिए ऐसा लगता है कि आपके ओवरराइड साइज का ओफ़्फ़ सोर्स कोड में होना चाहिए।
अली


1

मेरे मामले में, न तो उपरोक्त में से किसी ने और न ही अन्य स्टैकओवरफ़्लो प्रश्नों के उत्तर में समान समस्याएं होने पर काम किया।

खैर, मैं हर बार आइटम पर क्लिक किए जाने के बाद कस्टम एनीमेशन का उपयोग कर रहा था, जिसके लिए मैं अपने CustomAnimator वर्ग को पेलोड पास करने के लिए InformItemChanged (int स्थिति, ऑब्जेक्ट पेलोड) बुला रहा था।

ध्यान दें, RecyclerView Adapter में 2 onBindViewHolder (...) विधियाँ उपलब्ध हैं। onBindViewHolder (...) विधि 3 पैरामीटर वाले हमेशा 2 मापदंडों वाले onBindViewHolder (...) विधि से पहले कॉल किए जाएंगे।

आम तौर पर, हम हमेशा 2 मापदंडों वाले onBindViewHolder (...) विधि को ओवरराइड करते हैं और समस्या की मुख्य जड़ मैं यही कर रहा था, जैसा कि हर बार InformItemChanged (...) को कहा जाता है, हमारे onBindViewHolder (...) विधि होगी कहा जाता है, जिसमें मैं पिकासो का उपयोग करके ImageView में अपनी छवि लोड कर रहा था, और यही कारण था कि इसकी मेमोरी या इंटरनेट से परवाह किए बिना इसे फिर से लोड किया जा रहा था। लोड होने तक, यह मुझे प्लेसहोल्डर छवि दिखा रहा था, जो कि जब भी मैं आइटम पर क्लिक करता हूं, तो 1 सेकंड के लिए ब्लिंक करने का कारण था।

बाद में, मैं भी 3 मापदंडों वाले एक और onBindViewHolder (...) विधि को ओवरराइड करता हूं। यहां मैं जांच करता हूं कि यदि पेलोड की सूची खाली है, तो मैं इस पद्धति का सुपर क्लास कार्यान्वयन वापस करता हूं, अन्यथा यदि पेलोड हैं, तो मैं धारक के आइटम व्यू के अल्फा मान को केवल 1 पर सेट कर रहा हूं।

और हाँ, मुझे एक पूरा दिन व्यर्थ में बर्बाद करने के बाद मेरी समस्या का हल मिल गया!

यहाँ onBindViewHolder (...) विधियों के लिए मेरा कोड है:

2 params के साथ onBindViewHolder (...):

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

3 params के साथ onBindViewHolder (...):

@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 के आइटम व्यू पर 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);
    }

ध्यान दें: आप onCreateViewHolder (...) से getAdapterPosition () अपने viewHolder की विधि को कॉल करके इस स्थिति को प्राप्त कर सकते हैं।

मैंने getItemId (int स्थिति) विधि को भी इस प्रकार से ओवरराइड किया है:

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

और setHasStableIds(true);गतिविधि में मेरे एडाप्टर ऑब्जेक्ट पर कॉल किया गया।

आशा है कि यह मदद करता है अगर काम के ऊपर जवाब में से कोई भी!


1

मेरे मामले में एक बहुत सरल समस्या थी, लेकिन यह ऊपर की समस्या की तरह लग सकता है / महसूस कर सकता है। मैंने Groupie के साथ एक ExpandableListView को RecylerView में बदल दिया था (Groupie's ExpandableGroup सुविधा का उपयोग करके)। मेरे प्रारंभिक लेआउट में इस तरह का एक अनुभाग था:

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

लेआउट_हाइट के साथ "रैप_ कॉन्टेंट" पर सेट करें, विस्तारित समूह से एनीमेशन को ढह गए समूह में ऐसा महसूस होता है कि यह फ्लैश होगा, लेकिन यह वास्तव में "गलत" स्थिति (इस थ्रेड में अधिकांश सिफारिशों की कोशिश करने के बाद भी) से एनिमेशन कर रहा था।

वैसे भी, इस तरह लेआउट_हाइट को मैच_परेंट में बदलना इस समस्या को ठीक करता है।

<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

मेरे आवेदन के लिए, मेरे पास कुछ डेटा बदलने के लिए था, लेकिन मैं नहीं चाहता था कि पूरे दृश्य को पलक झपकाए।

मैंने इसे केवल 0.5 अल्फा से नीचे के पुराने पड़ाव को मिटाकर और 0.5 पर नए दृश्य के अल्फा को शुरू करके हल किया। इसने दृश्य को पूरी तरह से गायब किए बिना एक नरम फीका संक्रमण पैदा किया।

दुर्भाग्य से, निजी कार्यान्वयन के कारण, मैं इस बदलाव को करने के लिए DefaultItemAnimator को उप-वर्ग नहीं कर सका, इसलिए मुझे कोड को क्लोन करना था और निम्नलिखित परिवर्तन करने थे

चेतन में:

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

चेतन में परिवर्तनइम्प्ल:

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

0

विचारों को अद्यतन करने के लिए उपयुक्त recyclerview विधियों का उपयोग करना इस समस्या को हल करेगा

सबसे पहले, सूची में बदलाव करें

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

फिर उपयोग करके सूचित करें

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

आशा है कि यह मदद करेगा !!


बिलकुल नहीं। यदि आप सेकंड (मेरे मामले में BLE स्कैनिंग) द्वारा कई अपडेट कर रहे हैं, तो यह बिल्कुल भी काम नहीं करता है। मैं एक दिन इस गंदगी RecyclerAdapter को अद्यतन करने के लिए बिताया ... ArrayAdapter रखने के लिए बेहतर है। यह शर्म की बात है कि MVC पैटर्न का उपयोग नहीं किया जा रहा है, लेकिन कम से कम यह लगभग प्रयोग करने योग्य है।
गोझिर


हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.