回收视图在通知 DatasetChanged()后闪烁

我有一个回收视图,它从 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());
}

问题是,当我们在回收视图中进行刷新时,它会在开始时闪烁很短的一段时间,这看起来很奇怪。

我只是使用 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);

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

84523 次浏览

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

调用 notifyDataSetChanged将导致重新绑定所有视图,而不是调用粒度通知事件(通知添加/删除/移动/更新) ,这样回收视图将只重新绑定必要的视图,没有任何闪烁。

嘿,阿里,可能要晚点回放了。我也面临着这个问题,解决了以下的解决方案,它可能会帮助你请检查。

创建 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);
}
}

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

希望能对你有所帮助。

我也有类似的问题,这对我很有效 您可以调用此方法来设置图像缓存的大小

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

根据这个 问题页面... ... 它是默认的回收视图项目改变动画... 你可以关闭它。试试这个

recyclerView.getItemAnimator().setSupportsChangeAnimations(false);

更改最新版本

引自 Android 开发者博客:

注意,这个新 API 不向后兼容 实现了 ItemAnimator,则可以转而扩展 SimpleItemAnimator,它通过包装新的 您还会注意到一些方法已经被完全删除了 从 ItemAnimator。例如,如果您正在调用 GetItemAnimator () . setSupportsChangeAnimations (false) , 这段代码将不再编译。您可以用以下代码替换它:

ItemAnimator animator = recyclerView.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}

尝试在回收视图适配器中使用稳定 ID

重写 getItemId(int position)

如果没有稳定的 ID,在 notifyDataSetChanged()之后,ViewHolder 通常被分配到不同的位置。这就是我眨眼睛的原因。

你可以找到 很好的解释。

尝试禁用默认动画

ItemAnimator animator = recyclerView.getItemAnimator();


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

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

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

recyclerView.getItemAnimator().setSupportsChangeAnimations(false)

这种方法简单有效:

recyclerView.getItemAnimator().setChangeDuration(0);

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

我解决这个问题的方法是将旧视图的 alpha 值降低0.5,并将新视图的 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

对我来说 recyclerView.setHasFixedSize(true);成功了

回收视图使用 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 线。

要使用新的 Animator,只需在回收视图上调用 setItemAnimator () :

mRecyclerView.setItemAnimator(new MyItemAnimator());

使用适当的回收视图方法来更新视图将解决这个问题

首先,对列表进行更改

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

然后通知使用

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

希望这个能帮上忙! !

我有同样的问题从一些网址加载图像,然后 imageView 闪烁。 通过使用

notifyItemRangeInserted()

而不是

notifyDataSetChanged()

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

在 Kotlin,你可以使用“类扩展”来回收视图:

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

在我的例子中,上述任何一个问题,以及其他存在相同问题的堆栈溢出问题的答案都不起作用。

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

注意,回收视图适配器中有2个 onBindViewHolder (...)方法。 具有3个参数的 onBindViewHolder (...)方法将始终在具有2个参数的 onBindViewHolder (...)方法之前调用。

通常,我们总是重写具有2个参数的 onBindViewHolder (...)方法,问题的主要根源是我正在做同样的事情, 当每次调用 notifyItemChanged (...)时,我们的 onBindViewHolder (...)方法将被调用,在这个方法中,我使用毕加索在 ImageView 中加载我的图像,这就是为什么它再次加载的原因,不管它是从内存还是从互联网。在加载之前,它一直在向我显示占位符图像,这就是为什么每当我点击项目视图时都会闪烁1秒钟的原因。

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

我悲伤地浪费了整整一天,终于找到了解决问题的办法!

下面是 onBindViewHolder (...)方法的代码:

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

如果上面的答案都不起作用,希望这个能有所帮助!

在我的例子中,有一个更简单的问题,但它看起来/感觉起来非常像上面的问题。我用 Groupie (使用 Groupie 的 ExpandableGroup 特性)将 ExpandableListView 转换为 RecylerView。我最初的布局是这样的:

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/hint_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white" />

将 layup _ height 设置为“ wrad _ content”,从展开组到折叠组的动画感觉像是在闪烁,但实际上它只是从“错误”的位置进行动画(即使在尝试了本帖中的大部分推荐之后)。

无论如何,像这样简单地将 lay_ height 更改为 match _ father 就可以解决问题。

<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 解决方案:

(recyclerViewIdFromXML.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false

在我的例子中,我使用了带有视图模型绑定的 SwipeRefresh 和 RevicleView,并且面临着眨眼的问题 使用 SubmitList ()更新列表 因为 DiffUtils 已经完成了这项工作,否则列表将完全重新加载 请参考 CodeLab < a href = “ https://developer.android.com/codelabs/kotlin-android-training-affutil-database inding # 4”rel = “ nofollow noReferrer”> https://developer.android.com/codelabs/kotlin-android-training-diffutil-databinding#4

在使用实时数据的时候,我用助推器解决了这个问题 这就是我如何将 diffUtil 和数据绑定与适配器结合起来的方法

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

在 Kotlin 试试这个

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

在我的例子中(一个图像到高分辨率图像之间的共享元素转换) ,我在项目装饰器中添加了 有点耽搁,以使它不那么显眼:

(yourRv.itemAnimator as? DefaultItemAnimator)?.changeDuration = 2000

我自己的问题是一个非常具体的问题,我将在这里分享一些见解,虽然我还没有完全理解它。

基本上,我有一个可重用的回收视图片段,所以我可以有一个嵌套菜单菜单。在回收视图中选择一个项目,它将打开另一个包含更多选项的片段。在 xml 布局中,使用 JetPack 导航组件和使用 LiveData 的数据绑定都很方便。

无论如何,这里是我如何解决我的问题的项目闪烁时,回收视图改变(虽然这是值得记住的,这只是 外表,因为它是一个’新的’回收视图每次)。为了更新 LiveData 项目列表(在我的例子中,一些视图模型表示菜单项的对象) ,我使用了 LiveData.value = new items。改为 postValue(new items)固定的问题,虽然我还不确定为什么。

我已经阅读了 value (Java 中的 setValue)和 PostValue 之间的区别,我知道它们与使用主线程或后台线程有关,后者只在主线程准备就绪时应用一次。但是除此之外,我不确定为什么在我的回收视图中会出现这种固定的闪烁。也许有人知道些什么?在任何情况下,希望这将帮助有人面临类似的问题给我。