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个回答

170

尝试在RecyclerView.Adapter中使用稳定的ID

setHasStableIds(true)并重写getItemId(int position)

如果没有稳定的ID,在notifyDataSetChanged()之后,ViewHolder通常不会分配给同一个位置。这是我遇到闪烁问题的原因。

您可以在此处找到很好的解释。


3
我添加了setHasStableIds(true),解决了闪烁问题,但在我的GridLayout中,滚动时项目仍然会改变位置。我应该在getItemId()中覆盖什么?谢谢。 - Balázs Orbán
4
我们如何生成ID有什么想法吗? - Mauker
你如何重写getitemposition? 我的意思是用什么代码来覆盖它? - John
1
混乱了项目的位置,项目来自Firebase数据库,需要按正确顺序排列。 - MuhammadAliJr
1
@Mauker 如果你的对象有一个唯一的数字,你可以使用它,或者如果你有一个唯一的字符串,你可以使用object.hashCode()。对我来说,这个方法完全有效。 - Simon Schubert
显示剩余6条评论

122

根据这个问题页面...它是默认的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);
}

6
@sabeer 仍然存在同样的问题。它没有解决这个问题。 - Shreyash Mahajan
4
了解,如果您找到了任何解决方案,请告诉我。 - Shreyash Mahajan
我正在使用Picasso,它很好用,不会出现闪烁的情况。 - user3402040
11
Kotlin代码翻译为中文:(recyclerView.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false将RecyclerView的itemAnimator强制转换为SimpleItemAnimator类型,如果可以转换成功,就将supportsChangeAnimations属性设置为false。 - Pedro Paulo Amorim
它正在工作,但是出现了其他问题(NPE)java.lang.NullPointerException:尝试从空对象引用的字段“int android.support.v7.widget.RecyclerView $ ItemAnimator $ ItemHolderInfo.left”读取有没有办法解决这个问题? - Navas pk

50

这很简单,就是有效的:

recyclerView.getItemAnimator().setChangeDuration(0);

这是一个很好的替代方案 code ItemAnimator animator = recyclerView.getItemAnimator(); if (animator instanceof SimpleItemAnimator) { ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false); } code - ziniestro
2
停止所有动画 ADD 和 REMOVE。 - MBH
解决背景选择器 XML 中闪烁问题。 - Suchith

16

我遇到了相同的问题,从某些URL加载图像时imageView会闪烁。通过使用以下方法解决:

notifyItemRangeInserted()    

而不是

notifyDataSetChanged()

这样可以避免重新加载那些未改变的旧数据。


12

尝试以下方法禁用默认动画

ItemAnimator animator = recyclerView.getItemAnimator();

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

这是禁用Android支持库23及以上版本动画的新方法

对于旧版本的支持库,这种旧方法仍然有效

recyclerView.getItemAnimator().setSupportsChangeAnimations(false)

6

Kotlin解决方案:

(recyclerViewIdFromXML.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false

5

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,并删除了上面的三行alpha代码。

要使用新的动画,只需在RecyclerView上调用setItemAnimator()方法:

mRecyclerView.setItemAnimator(new MyItemAnimator());

它去掉了闪烁,但由于某种原因引起了闪烁效果。 - Mohamed Medhat

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

2
禁用动画并不能解决问题。 - Jimale Abdi

4

尝试在Kotlin中使用这个

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

4
假设mItems是支持您的Adapter的集合,为什么要将所有内容删除并重新添加?这基本上告诉它所有内容都已更改,因此RecyclerView重新绑定了所有视图,我认为Image库不能正确处理它,即使是相同的图像URL,它仍然重置View。也许他们对AdapterView有一些内置的解决方案,所以在GridView中可以正常工作。

不要调用notifyDataSetChanged,这将导致重新绑定所有视图,请调用细粒度的通知事件(通知已添加/已删除/已移动/已更新),以便RecyclerView仅重新绑定必要的视图,而无需闪烁。


4
或许这只是RecyclerView中的一个“漏洞”?显然,如果AbsListView在过去六年中运作良好,而现在RecyclerView却不行,那么RecyclerView肯定存在问题,对吧? :)快速查看发现,在ListView和GridView中刷新数据时,它们会跟踪视图+位置,因此刷新后您将获得完全相同的viewholder。而RecyclerView会重新排列视图持有者,导致闪烁。 - vovkab
在ListView上工作并不意味着它适用于RecyclerView。这些组件具有不同的架构。 - yigit
1
同意,但有时候很难知道哪些项目已更改,例如如果您使用游标或者只是刷新整个数据。因此,recyclerview 也应正确处理这种情况。 - vovkab

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