在notifyDatasetChanged()之后RecyclerView闪烁

时间:2015-03-29 15:35:11

标签: android android-fragments android-recyclerview

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

这是Adapter的实现:

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

任何人都遇到过这样的问题?可能是什么原因?

20 个答案:

答案 0 :(得分:99)

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

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

没有稳定的ID,在notifyDataSetChanged()之后,ViewHolders通常被分配到不同的位置。这就是我案件中闪烁的原因。

您可以找到good explanation here.

答案 1 :(得分:35)

这很简单:

recyclerView.getItemAnimator().setChangeDuration(0);

答案 2 :(得分:8)

我在从一些网址加载图片时遇到同样的问题,然后imageView会闪烁。 通过使用

解决
notifyItemRangeInserted()    

而不是

notifyDataSetChanged()

避免重新加载那些未更改的旧数据。

答案 3 :(得分:7)

尝试此操作以禁用默认动画

ItemAnimator animator = recyclerView.getItemAnimator();

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

这是自Android支持23以来禁用动画的新方法

这种旧方法适用于旧版本的支持库

recyclerView.getItemAnimator().setSupportsChangeAnimations(false)

答案 4 :(得分:2)

假设mItems是支持Adapter的集合,为什么要删除所有内容并重新添加?你基本上告诉它一切都已经改变,所以RecyclerView重新绑定所有视图,而不是我假设图像库没有正确处理它仍然重置视图,即使它是相同的图像URL。也许他们在AdapterView的解决方案中有一些很好用,这样它在GridView中工作正常。

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

答案 5 :(得分:2)

在 Kotlin 中试试这个

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

答案 6 :(得分:2)

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

答案 7 :(得分: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 单例类[extends 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;
}

希望它会帮助你。

答案 8 :(得分: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”的情况下,从展开的组到折叠的组的动画好像会闪烁,但这实际上只是从“错误”的位置进行动画处理(即使尝试了该线程中的大多数建议)。 / p>

无论如何,只需将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" />

答案 9 :(得分:1)

就我而言,以上任何内容或其他具有相同问题的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(...)方法。在这里,我检查有效载荷列表是否为空,然后返回此方法的超类实现,否则,如果存在有效载荷,我只是将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);

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

答案 10 :(得分:0)

使用适当的recyclerview方法更新视图将解决此问题

首先,在列表中进行更改

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

然后使用

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

希望这会有所帮助!!

答案 11 :(得分:0)

尝试在回收站视图中使用stableId。下一篇文章对此进行了简要说明

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

答案 12 :(得分:0)

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

答案 13 :(得分:0)

为我recyclerView.setHasFixedSize(true);工作

答案 14 :(得分:0)

对于我的应用程序,我有一些数据在变化,但我不希望整个视图闪烁。

我通过将旧视图淡化0.5 alpha并将新视图alpha开始为0.5来解决它。这样可以创建更柔和的渐变过渡,而不会使视图完全消失。

不幸的是,由于私有实现,我无法继承DefaultItemAnimator以进行此更改,因此我必须克隆代码并进行以下更改

中的

ViewCompat.setAlpha(newHolder.itemView, 0);  //change 0 to 0.5f
在animateChangeImpl中:

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

答案 15 :(得分:0)

科特琳溶液:

(recyclerViewIdFromXML.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false

答案 16 :(得分:0)

在我的情况下,我使用了带有视图模型绑定的SwipeRefresh和RecycleView并面临闪烁。用->解决 使用 submitList()保持列表更新 因为DiffUtils已经完成了工作,否则列表将完全重新加载 请参阅CodeLab https://developer.android.com/codelabs/kotlin-android-training-diffutil-databinding#4

答案 17 :(得分: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)
}

答案 18 :(得分: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;
}

答案 19 :(得分:0)

在我的例子中(一个图像到更高分辨率图像之间的共享元素转换),我向项目装饰器添加了一些延迟,以使其不那么明显:

(yourRv.itemAnimator as? DefaultItemAnimator)?.changeDuration = 2000
相关问题