簡體   English   中英

RecyclerView 在 notifyDatasetChanged() 之后閃爍

[英]RecyclerView blinking after notifyDatasetChanged()

我有一個 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);
}

這是適配器的實現:

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,它按預期工作。 沒有眨眼。

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

有人遇到過這樣的問題嗎? 可能是什么原因?

嘗試在 RecyclerView.Adapter 中使用穩定的 ID

setHasStableIds(true)並覆蓋getItemId(int position)

沒有穩定的 ID,在notifyDataSetChanged() ,ViewHolders 通常分配到不同的位置。 這就是我眨眼的原因。

你可以在這里找到一個很好的解釋。

根據這個問題頁面....這是默認的recycleview項目更改動畫...您可以將其關閉..試試這個

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

這很簡單:

recyclerView.getItemAnimator().setChangeDuration(0);

我在從某些 url 加載圖像然后 imageView 閃爍時遇到了同樣的問題。 通過使用解決

notifyItemRangeInserted()    

代替

notifyDataSetChanged()

這避免了重新加載那些未更改的舊數據。

試試這個來禁用默認動畫

ItemAnimator animator = recyclerView.getItemAnimator();

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

這是禁用動畫的新方法,因為 android 支持 23

這種舊方法適用於舊版本的支持庫

recyclerView.getItemAnimator().setSupportsChangeAnimations(false)

Recyclerview 使用 DefaultItemAnimator 作為默認動畫制作器。 正如您從下面的代碼中看到的,它們會在項目更改時更改視圖持有者的 alpha:

@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 條 alpha 線。

要使用新的動畫師,只需在 RecyclerView 上調用 setItemAnimator():

mRecyclerView.setItemAnimator(new MyItemAnimator());

假設mItems是支持您的Adapter的集合,為什么要刪除所有內容並重新添加? 您基本上是在告訴它一切都發生了變化,因此 RecyclerView 重新綁定了所有視圖,而不是我假設圖像庫無法正確處理它,即使它是相同的圖像 url,它仍然會重置視圖。 也許他們有一些針對 AdapterView 的解決方案,以便它在 GridView 中正常工作。

而不是調用將導致重新綁定所有視圖的notifyDataSetChanged ,而是調用粒度通知事件(通知添加/刪除/移動/更新),以便 RecyclerView 將僅重新綁定必要的視圖,並且沒有任何閃爍。

在 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()
    // ...
}

科特林解決方案:

(recyclerViewIdFromXML.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false

就我而言,上述任何一個問題的答案以及其他具有相同問題的 stackoverflow 問題的答案都不起作用。

好吧,每次單擊項目時我都使用自定義動畫,為此我調用 notifyItemChanged(int position, Object Payload) 將有效負載傳遞給我的 CustomAnimator 類。

注意,在 RecyclerView Adapter 中有 2 個 onBindViewHolder(...) 方法可用。 具有 3 個參數的 onBindViewHolder(...) 方法將始終在具有 2 個參數的 onBindViewHolder(...) 方法之前調用。

通常,我們總是覆蓋具有 2 個參數的 onBindViewHolder(...) 方法,問題的主要根源是我在做同樣的事情,因為每次調用 notifyItemChanged(...) 時,我們的 onBindViewHolder(...) 方法都會被調用,其中我使用 Picasso 在 ImageView 中加載我的圖像,這就是它再次加載的原因,無論是從內存還是從互聯網。 在加載之前,它向我展示了占位符圖像,這是每當我單擊 itemview 時閃爍 1 秒的原因。

后來,我還覆蓋了另一個具有 3 個參數的 onBindViewHolder(...) 方法。 這里我檢查payloads列表是否為空,然后返回這個方法的超類實現,否則如果有payloads,我只是將holder的itemView的alpha值設置為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);
        }
    }

這是我在 onCreateViewHolder(...) 的 viewHolder 的 itemView 的 onClickListener 中調用的方法代碼:

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(...) 調用 viewHolder 的 getAdapterPosition() 方法來獲取此位置。

我還重寫了 getItemId(int position) 方法,如下所示:

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

並調用setHasStableIds(true); 在活動中的我的適配器對象上。

如果以上答案都不起作用,希望這會有所幫助!

在我的情況下,有一個更簡單的問題,但它看起來/感覺非常像上面的問題。 我使用 Groupie 將 ExpandableListView 轉換為 RecylerView(使用 Groupie 的 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" />

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

在 Kotlin 中試試這個

binding.recyclerView.apply {
    (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}

嘿@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 單例類構造函數中添加以下代碼段以初始化 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;
}

希望它會幫助你。

對我來說recyclerView.setHasFixedSize(true); 工作過

我有類似的問題,這對我有用你可以調用這個方法來設置圖像緩存的大小

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.5 alpha 並從 0.5 開始新視圖 alpha 來解決它。 這創建了更柔和的淡入淡出過渡,而不會使視圖完全消失。

不幸的是,由於私有實現,我無法子類化 DefaultItemAnimator 以進行此更改,因此我不得不克隆代碼並進行以下更改

在 animateChange 中:

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

在 animateChangeImpl 中:

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

使用適當的 recyclerview 方法更新視圖將解決此問題

首先,在列表中進行更改

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

然后通知使用

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

希望這會有所幫助!!

嘗試在回收者視圖中使用 stableId。 下面這篇文章簡單解釋一下

https://medium.com/@hanru.yeh/recyclerviews-views-are-blinking-when-notifydatasetchanged-c7b76d5149a2

就我而言,我將 SwipeRefresh 和 RecycleView 與視圖模型綁定一起使用,並面臨閃爍。 解決了 -> 使用submitList()保持列表更新,因為 DiffUtils 已經完成了這項工作,否則列表重新加載完全參考 CodeLab https://developer.android.com/codelabs/kotlin-android-training-diffutil-databinding# 4

使用 livedata 時我用 diffUtil 解決了這就是我將 diffUtill 和數據綁定與我的適配器結合的方式

class ItemAdapter(
    private val clickListener: ItemListener
) :
    ListAdapter<Item, ItemAdapter.ViewHolder>(ItemDiffCallback()) {

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(clickListener, getItem(position)!!)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder.from(parent)
    }

    class ViewHolder private constructor(val binding: ListItemViewBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(
            clickListener: ItemListener,
            item: Item) {

            binding.item = item
            binding.clickListener = clickListener
            binding.executePendingBindings()
        }

        companion object {
            fun from(parent: ViewGroup): ViewHolder {
                val layoutInflater = LayoutInflater.from(parent.context)
                val view = ListItemViewBinding
                    .inflate(layoutInflater, parent, false)

                return ViewHolder(view)
            }
        }
    }

    class ItemDiffCallback :
        DiffUtil.ItemCallback<Item>() {
        override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
            return oldItem.itemId == newItem.itemId
        }

        override fun getChangePayload(oldItem: Item, newItem: Item): Any? {
            return newItem
        }

        override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
            return oldItem == newItem
        }
    }
}

class ItemListener(val clickListener: (item: Item) -> Unit) {
    fun onClick(item: Item) = clickListener(item)
}

在我的情況下(一個圖像到更高分辨率圖像之間的共享元素轉換),我向項目裝飾器添加了一些延遲以使其不那么明顯:

(yourRv.itemAnimator as? DefaultItemAnimator)?.changeDuration = 2000

我自己的問題是一個非常具體的問題,我將在這里分享一些見解,盡管我還沒有完全理解它。

基本上我有一個帶有 RecyclerView 的可重復使用的片段,這樣我就可以有一個帶有嵌套菜單的菜單。 在 RecyclerView 中選擇一個項目,它會打開另一個具有更多選項的片段。 在布局 xml 中使用 JetPack 導航組件和使用 LiveData 進行數據綁定,所有這些都得到了促進。

無論如何,這就是我如何解決 RecyclerView 更改時項目閃爍的問題(盡管值得牢記,這只是外觀,因為它每次都是“新”RecyclerView)。 為了更新項目的 LiveData 列表(在我的例子中,一些視圖模型代表菜單項的對象)我使用LiveData.value = new items 將其更改為postValue(new items)解決了這個問題,但我還不確定為什么。

我已經閱讀了值(Java 中的 setValue)和 PostValue 之間的區別,我知道它們與使用主線程或后台線程有關,而后者僅在主線程准備就緒時應用一次。 但除此之外,我不確定為什么會在我的 RecyclerView 中固定閃爍。 也許有人有一些見識? 無論如何,希望這能幫助遇到與我類似問題的人。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM