【问题标题】:How to prevent RecyclerView item from blinking after notifyItemChanged(pos)?如何防止 RecyclerView 项目在 notifyItemChanged(pos) 后闪烁?
【发布时间】:2017-02-21 23:02:52
【问题描述】:

我目前有一个回收站视图,其数据每 5 秒更新一次。为了更新列表中的数据,我正在使用

notifyItemChanged(position);
notifyItemRangeChanged(position, mList.size());

每次我调用 notifyItemChanged() 时,我的回收站视图上的项目都会正确更新,但是,它会闪烁,因为这会导致再次调用 onBindViewHolder。所以就好像每次都是新的负载。如果可能,我该如何防止这种情况发生?

【问题讨论】:

标签: android android-recyclerview


【解决方案1】:

RecyclerView 内置动画,通常会添加漂亮的抛光效果。在您的情况下,您需要禁用它们:

((SimpleItemAnimator) mRecyclerView.getItemAnimator()).setSupportsChangeAnimations(false);

(默认的回收器视图动画器应该已经是SimpleItemAnimator 的实例)

对于 Kotlin,

(mRecyclerView?.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false

【讨论】:

  • 是的!这正是我所需要的。
  • 但我认为这也会禁用 notifyItemInserted 等方法的默认动画
  • @user6650650 对我有用。你也是正确的。谢谢!
  • 字对我来说很棒!
【解决方案2】:

您可以禁用项目动画。

mRecyclerView.setItemAnimator(null);

【讨论】:

  • 这似乎使 flickr 比以前更快,我正在使用数据绑定。 StableIds 是明智的优化,但您仍然在正在更新的行上有一个 flickr(它们具有更改状态的单选按钮,但视图的其余部分未更改,因此不应该 filckr)
  • 这是最好的答案。一次修复了一些错误。
【解决方案3】:

问题可能不是来自动画,而是来自not stable id of the list item

要使用稳定的 ID,您需要:

- setHasStableIds(true)
在 RecyclerView.Adapter 中,我们需要设置 setHasStableIds(true); true 表示此适配器将发布一个唯一值作为数据集中项目的键。在通知数据发生变化后,适配器可以通过key来指示它们是否相同。

- 覆盖 getItemId(int position)
然后我们必须覆盖 getItemId(int position),以返回标识的 long 位置的项目。我们需要确保不存在具有相同返回 id 的不同商品数据。

is here的解决方案来源。

【讨论】:

  • 这应该是公认的答案。 setAnimator 为 null 是一个修补程序,不适用于那些想要在其 RecyclerView @portfoliobuilder 中使用动画的人
  • 当然应该,但是...欢迎来到 SO。
【解决方案4】:

在您的适配器中使用 stableId

调用 adapter.setHasStableIds(true) 并覆盖适配器类中的 getItemId(int position) 方法。

另外,从getItemId(int position) 为每个项目返回一些唯一的 id。不要只是简单地返回位置。

【讨论】:

  • 对我没有任何改变。使用按钮更新一个项目时,该项目仍然闪烁
【解决方案5】:

如果你想同时保留RecyclerView的动画和避免item闪烁,可以按照以下步骤进行:

1。直接更改视图组件状态 2.直接修改Adapter中的数据 3.不需要手动刷新UI,调用notifyItemChanged()

直接更改视图不会立即更改,更好的方法是使用payload

在您的适配器中:

override fun onBindViewHolder(
        holder: RecyclerView.ViewHolder,
        position: Int,
        payloads: MutableList<Any>
    ) {
        // Using payload to refresh partly
        if (payloads.isNullOrEmpty()) {
            // refresh all 
            onBindViewHolder(holder, position)
            return
        }
        // just change one component's status which would not "flash".
        (holder as YourHolder).apply{
            // make you view change
            // etc. checkBox.isChecked = true
        }
    }

并使用它:


yourAdapter.notifyItemChanged(position, "sample_pay_load")

这里是notifyitemchanged's Doc

【讨论】:

  • 使用有效负载指示更新是否部分的良好解决方法
【解决方案6】:

我阅读了以下解决方案并实施了它,它在我测试的每台设备上都能正常工作。

  • 克隆默认动画师类。
  • 在 animateChange() 方法中,注释下面 3 行,

    final float prevAlpha = oldHolder.itemView.getAlpha();
    .
    .
    oldHolder.itemView.setAlpha(prevAlpha);
    .
    .
    newHolder.itemView.setAlpha(0);
    
  • 将 recyclerview 项目动画器设置为克隆类的动画器。

//注意:我确实了解解决方案如何在不更改新持有人的 alpha 值的情况下工作,但这不是我的解决方案,我在 stackoverflow 本身上阅读了此内容,但由于某种原因无法再找到它。分享此内容以帮助其他开发人员。

【讨论】:

  • 我认为这是最好的答案之一。谢谢!
  • 我做错了什么或者这不起作用。我已经扩展了 DefaultItemAnimator() 类并覆盖了 animateChange() 但这没有任何改变
【解决方案7】:

发生这种情况是因为 Adapter 正在创建新的 ViewHolder 并尝试在新旧 ViewHolder 之间设置动画。这就是为什么所有“禁用动画”答案在技术上都有效的原因。

相反,您可以告诉RecyclerView 重新使用带有canReuseUpdatedViewHolder() 的Viewholders。请参阅类似问题的答案: https://stackoverflow.com/a/60427676/1650674

【讨论】:

    【解决方案8】:

    就我而言,上述任何一个问题以及其他具有相同问题的 stackoverflow 问题的答案都不起作用。

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

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

    通常,我们总是覆盖具有 2 个参数的 onBindViewHolder(...) 方法,而问题的主要根源是我也在做同样的事情, 每次调用 notifyItemChanged(...) 时,都会调用我们的 onBindViewHolder(...) 方法,在该方法中,我使用 Picasso 在 ImageView 中加载图像,这就是它再次加载的原因,不管它是从内存中加载的或来自互联网。在加载之前,它会向我显示占位符图像,这就是每当我单击 itemview 时闪烁 1 秒的原因。

    稍后,我还重写了另一个具有 3 个参数的 onBindViewHolder(...) 方法。这里我检查payload列表是否为空,然后返回该方法的超类实现,否则如果有payload,我只是将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);

    如果以上答案都不起作用,希望这会有所帮助!

    【讨论】:

    • 这对我有用,我还注意到它是在动画期间减少的项目的 alpha。这解决了它,谢谢!
    【解决方案9】:

    另外,尝试在 Main 范围内单独启动的协程作业中运行每个 notifyItemChanged。它非常适合我显着消除眨眼。

    for (i in 0 until adapter.itemCount) {
        CoroutineScope(Main).launch {
            adapter.notifyItemChanged(i)
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-04
      • 2011-01-30
      相关资源
      最近更新 更多