[英]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);
有人遇到過這樣的問題嗎? 可能是什么原因?
根據這個問題頁面....這是默認的recycleview項目更改動畫...您可以將其關閉..試試這個
recyclerView.getItemAnimator().setSupportsChangeAnimations(false);
最新版本的變化
請注意,此新 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。 下面這篇文章簡單解釋一下
就我而言,我將 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.