RecyclerView在notifyDatasetChanged()后闪烁

153
我有一个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,它按照我的预期工作。 没有闪烁。

在我的Fragment的onViewCreated中配置RecycleView:

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的ItemAnimator中的onChange动画的重复问题。 - Apfelsaft23
22个回答

2
在我的情况下,以上所有方法和其他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(...)方法。在这里,我检查有效负载列表是否为空,然后返回此方法的超类实现,否则如果存在有效负载,则只需将持有者的itemView的alpha值设置为1。
很遗憾,我浪费了一整天的时间,但是我找到了解决问题的方法!
以下是我的onBindViewHolder(...)方法的代码:
onBindViewHolder(...) with 2 params:
@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个参数的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);
        }
    }

以下是我在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);方法。如果上述答案都不起作用,希望这可以帮到您!

2

在我的情况下,问题要简单得多,但它看起来/感觉起来非常像上面的问题。我已经使用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" />

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 是单例类[继承Application],在下面添加以下代码

在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] (https://bitbucket.org/ali-rezaei/twitzer/src/ba60b041fce1abfc271ae4a084381e205c972ee5/app/src/main/java/com/dynamo/android/client/volley/VolleyClient.java?at=master) - Ali
你需要扩展 LruCache<String, Bitmap> 类并像我这样覆盖 sizeOf() 方法,除此之外其他都看起来没问题。 - Android learner
好的,我很快就会尝试一下,但是你能解释一下你在那里做了什么,让你解决了问题吗?源代码 对我来说,听起来像是你重写的sizeOf方法应该在源代码中。 - Ali
我敢打赌,当recycleView被通知数据集发生变化时,你没有禁用动画效果。这就是为什么你看不到问题的原因。 - Ali
我非常感谢你与我分享解决方案,但如果您能够解释一下为什么您重写的sizeOf方法可以解决问题,而不是鼓励我只是复制您的代码,那我会更加高兴。这也是我提出这个问题的主要目的。 - Ali
显示剩余4条评论

1

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模式,但至少它几乎可以使用。 - Gojir4

0
对于我的应用程序,我有一些数据变化,但我不想让整个视图闪烁。
我解决了这个问题,只需将旧视图淡出0.5 alpha,并将新视图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
当使用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)
}

0

对我来说,recyclerView.setHasFixedSize(true); 起作用了。


我认为OP的项目没有固定大小。 - Irfandi D. Vendy
这在我的情况下帮了我。 - bst91

0

0

我的问题非常具体,我将在这里分享一些见解,尽管我还没有完全理解它。

基本上,我有一个可重复使用的带有RecyclerView的片段,以便我可以拥有一个带有嵌套菜单的菜单。在RecyclerView中选择一个项目,它会打开另一个带有更多选项的片段。所有这些都是使用JetPack导航组件和在布局xml中使用LiveData进行数据绑定来实现的。

无论如何,以下是我解决RecyclerView更改时项目闪烁问题的方法(尽管值得注意的是,这只是“外观”,因为每次都是一个“新”的RecyclerView)。为了更新项目的LiveData列表(在我的情况下,一些表示菜单项对象的视图模型),我正在使用LiveData.value = new items。将其更改为postValue(new items)解决了问题,尽管我还不确定原因。

我已经阅读了value(Java中的setValue)和PostValue之间的区别,并且我知道它们与使用主线程或后台线程有关,后者仅在准备好时在主线程上应用一次。但除此之外,我不确定为什么这可以解决我的RecyclerView闪烁问题。也许有人有一些见解?无论如何,希望这可以帮助面临类似问题的人。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接