在notifyDatasetChanged()之后,RecyclerView闪烁


113

我有一个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以下位置配置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);

有人遇到这样的问题吗?可能是什么原因?


Answers:


126

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

setHasStableIds(true)并覆盖getItemId(int position)

如果没有稳定的ID,在之后notifyDataSetChanged(),ViewHolders通常分配给不同的位置。那就是我案件眨眼的原因。

您可以在这里找到很好的解释。


1
我添加了setHasStableIds(true),它解决了闪烁问题,但是在我的GridLayout中,滚动时项目仍然会更改位置。我应该在getItemId()中覆盖什么?谢谢
巴拉兹欧尔班

3
关于如何生成ID的任何想法?
莫克

你真棒!Google不是。Google要么对此一无所知,要么他们知道并且不希望我们知道!谢谢您
MBH

1
将物品位置弄乱,物品来自Firebase数据库的顺序正确。
MuhammadAliJr

1
@Mauker如果您的对象具有唯一编号,则可以使用该数字;如果您具有唯一字符串,则可以使用object.hashCode()。完美的作品对我很好
西蒙·舒伯特

106

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

5
@sabeer还是同样的问题。它不能解决问题。
Shreyash Mahajan

3
@delive告诉我您是否找到解决方案
Shreyash Mahajan

我使用的是毕加索,它有些东西,不会眨眼。
国内配送

8
Kotlin:(recyclerView.itemAnimator为?SimpleItemAnimator)?. supportsChangeAnimations = false
Pedro Paulo Amorim

它正在工作,但是其他问题来了(NPE)java.lang.NullPointerException:尝试从null对象引用上的字段'int android.support.v7.widget.RecyclerView $ ItemAnimator $ ItemHolderInfo.left'读取解决这个问题?
Navas PK

46

这很简单:

recyclerView.getItemAnimator().setChangeDuration(0);

这是codeItemAnimator 的一个很好的替代方案animator = recyclerView.getItemAnimator(); if(SimpleItemAnimator的动画实例){(((SimpleItemAnimator)动画师).setSupportsChangeAnimations(false); }code
ziniestro 2016年

1
停止所有动画的添加和删除
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重新绑定了所有视图,而不是我认为Image库无法正确处理它,即使它是相同的图像URL,它仍然会重置View。也许他们为AdapterView解决了一些问题,以便在GridView中正常工作。

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


3
还是只是RecyclerView中的“错误”?显然,如果它在AbsListView上可以正常使用6年,现在又不能在RecyclerView上使用,则意味着RecyclerView不能正常工作,对吗?:)快速查看它表明,当您在ListView和GridView中刷新数据时,它们会跟踪view + position,因此刷新时您将获得完全相同的viewholder。当RecyclerView随机播放视图持有人时,这会导致闪烁。
vovkab 2015年

在ListView 工作并不意味着它对RecyclerView是正确的。这些组件具有不同的体系结构。
yigit 2015年

1
同意,但是有时候很难知道更改了哪些项目,例如,如果您使用游标,或者只是刷新整个数据。因此,recyclerview也应该正确处理这种情况。
vovkab 2015年

4

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

它消除了闪烁,但是由于某种原因导致了闪烁效果。
Mohamed Medhat

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单例类构造函数中,添加以下代码片段以初始化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
阿里

只需使用LruCache <String,Bitmap>类检查一次,我认为它将解决您的问题。看看LruCache
Android学习者

您是否曾经在评论中检查过我与您共享的代码?你班上我错过了什么?
阿里

您缺少像我一样扩展LruCache <String,Bitmap>类和覆盖sizeOf()方法的方法,剩下的一切对我来说似乎还可以。
Android学习者2015年

好的,我很快就会尝试一下,但是您能解释一下您在那里做了什么,为您解决了魔力并解决了问题吗?源代码对我来说,您的覆盖的sizeOf方法应该在源代码中。
阿里


1

在我的情况下,以上任何问题或其他具有相同问题的stackoverflow问题的答案均​​无效。

好吧,每次单击该项目时,我都使用自定义动画,为此,我正在调用notifyItemChanged(int position,Object Payload)将有效负载传递给我的CustomAnimator类。

注意,RecyclerView Adapter中有2种onBindViewHolder(...)方法可用。具有3个参数的onBindViewHolder(...)方法将始终在具有2个参数的onBindViewHolder(...)方法之前调用。

通常,我们总是重写具有2个参数的onBindViewHolder(...)方法,问题的主要根源是我在做同样的事情,因为每次调用notifyItemChanged(...)时,我们的onBindViewHolder(...)方法都会被称为,我使用Picasso将图像加载到ImageView中,这就是无论内存还是Internet都重新加载图像的原因。在加载之前,它向我显示了占位符图像,这是当我单击itemview时闪烁1秒钟的原因。

稍后,我还将重写另一个具有3个参数的onBindViewHolder(...)方法。在这里,我检查有效负载列表是否为空,然后返回此方法的超类实现,否则,如果存在有效负载,我只是将holder的itemView的alpha值设置为1。

是的,可悲的是浪费了一整天后,我得到了解决问题的方法!

这是我的onBindViewHolder(...)方法的代码:

具有2个参数的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);
    }

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);在活动中调用了我的适配器对象。

希望以上方法均不能解决问题!


1

在我的情况下,有一个简单得多的问题,但它看起来/感觉很像上面的问题。我已经将Groupable的ExpandableListView转换为具有Groupie的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" />

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

对于我的应用程序,我更改了一些数据,但我不希望整个视图闪烁。

我仅通过将旧视图的alpha降低0.5并以0.5开始的newview来解决了这个问题。这样就创建了更柔和的淡入淡出过渡,而不会使视图完全消失。

不幸的是,由于私有实现,我无法将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

使用适当的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

Kotlin解决方案:

(recyclerViewIdFromXML.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.