使用stateListAnimator在RecyclerView的项中,在调用notifyDataSetChanged时会导致闪烁效果。

13

我发现,如果在RecylerView的项上应用android:stateListAnimator,则调用adapter.notifyDataSetChanged将会导致某些RecylerView项出现不必要的闪烁效果(奇怪的是,并非所有项都会出现该问题)。

这是我的RecylerView项:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    android:stateListAnimator="@anim/lift_up"
    android:background="@drawable/white" >

    ...
</LinearLayout>

@anim/lift_up的定义如下:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_enabled="true"
        android:state_pressed="true">
        <objectAnimator
            android:duration="@android:integer/config_shortAnimTime"
            android:propertyName="translationZ"
            android:valueTo="8dip"
            android:valueType="floatType" />
    </item>
    <item>
        <objectAnimator
            android:duration="@android:integer/config_shortAnimTime"
            android:propertyName="translationZ"
            android:valueTo="4dip"
            android:valueType="floatType" />
    </item>
</selector>

并且@drawable/white被定义为

<drawable name="white">#ffffffff</drawable>

当我调用adapter.notifyDataSetChanged时,RecyclerVIew的最后5个可见项会出现奇怪的闪烁效果。(屏幕上总共有10个可见项)

https://youtu.be/yB4UP2wEFk0

这个问题只会在API 21及以上的版本中出现,因为只有API 21支持android:stateListAnimator

这是一个bug吗?还是我漏掉了什么?

完整的最小可行代码可以从https://github.com/yccheok/RecyclerViewTutorial/tree/4763879598864233a8e6544fe240c3fb34a15b73下载。


1
也许你可以在RecyclerView上使用动画。 - tiny sunlight
@tinysunlight 在Recyclerview上应用动画并没有什么帮助。 - Cheok Yan Cheng
2个回答

7

奇怪的是,不是所有的项都会被处理

这是故意设计的(我相信)。

内部地,所有我所处理过的 可回收 的 ViewGroups 都维护着一个View池。从头创建View很费时间。使用View池可以分散一些开销,但代价是资源的使用。这个池的大小就代表了这种平衡。你可以在这里看到一个基本实现:来自 DeckView 的 ViewPool

RecyclerView 也是使用RecycledViewPool 做同样的事情。注意默认的最大值:

public static class RecycledViewPool {
    ....
    private static final int DEFAULT_MAX_SCRAP = 5;
    ....
}

我相信你的情况中前5个视图不会闪烁,因为它们来自池子 - 它们不是在调用notifyDataSetChanged()时创建的。这可能是StateListAnimator无法启动的原因。但对于其余的5行/项,新的Views将被创建。
从源代码中可以看出:
View getViewForPosition(int position, boolean dryRun) {
    ....
    // 0) If there is a changed scrap, try to find from there
    ....
    // 1) Find from scrap by position
    ....
    // 2) Find from scrap via stable ids, if exists
    ....
    // fallback to recycler
        ....
        // getRecycledViewPool() returns an instance of RecycledViewPool
        holder = getRecycledViewPool().getRecycledView(type);
        ....
    // if holder is still 'null' after checking the pool, create a new one
        ....
        if (holder == null) {
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
        }
        ....
}

正如你所知道的那样,回收利用是一项严肃的业务。我无法解释选项0、1和2为什么失败,也无法确定它们是否失败。如果要检查我的假设,您可以更改池的max大小,并注意任何不同之处(在闪烁视图数量方面):

mRecyclerView
    .getRecycledViewPool()
        .setMaxRecycledViews(RecyclerView.INVALID_TYPE, 10);

乍一看,似乎是由于视图回收不正确引起的。然而,在我添加了 setMaxRecycledViews 之后,结果仍然相同。这些最后的视图仍然有闪烁效果。 - Cheok Yan Cheng
@CheokYanCheng 我明白了。我能想到的唯一其他原因与视图缓存有关。RecyclerView 也会缓存视图。在上面发布的代码中,RecyclerViewPool 在第3步提供视图。从您的示例项目中,由于您的适配器没有稳定的 ID,因此不会发生第2步。这留下了步骤0和1。不幸的是,我认为步骤0和1是无法验证的。您可以浏览源代码并发现我可能忽略的内容。 - Vikram
通过在getItemId(int position)中使用setHasStableIds(true)this.items.get(position).getId(),我成功解决了闪烁问题。https://github.com/yccheok/RecyclerViewTutorial/tree/114684d741270b11e9d556d8b883e729c52944cc 但是,你有什么想法为什么会这样吗? - Cheok Yan Cheng
@CheokYanCheng 我试运行了你的示例项目。使用调试器,我确认了我之前所说的 - 我相信在你的情况下前5个视图不会闪烁,因为它们来自池子。我犯的错误是告诉你尝试 mRecyclerView.getRecycledViewPool().setMaxRecycledViews(RecyclerView.INVALID_TYPE, 10);。常量 RecyclerView.INVALID_TYPE 的值为 -1,而 getItemViewType() 的默认实现返回 0。这就是为什么你没有看到任何区别的原因。请尝试使用参数 (0, 10) 进行上述调用。 - Vikram
1
嗨,Vikram,使用参数(0, 10)对我有效。你有什么想法,如何不硬编码值10?是否有一种动态确定最佳值的方法?(0,?) - Cheok Yan Cheng
2
@CheokYanCheng ? 应该等于可见的视图数。根据这个信息,我认为 ? 应该等于 mRecyclerView.getChildCount()。我能想到的唯一边界情况是当项目数量很少且它们 完全填满屏幕时。在这种情况下,? == mRecyclerView.getChildCount() && ? == mAdapter.getItemCount()。为什么这是一个边界情况 - 如果项目数量增加(添加新项目),则必须更新 ? - Vikram

1
如果您查看RecyclerView方法notifyDataSetChanged的文档http://androidxref.com/6.0.0_r1/xref/frameworks/support/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java,它提到以下内容:“当使用此方法时,对于报告具有{@link #hasStableIds()stable IDs}的适配器, RecyclerView 将尝试合成可见的结构更改事件。这可以帮助进行动画和视觉对象持久化,但是仍需要重新绑定和重新布局单个项目视图。”类似的想法也在https://www.youtube.com/watch?v=8MIfSxgsHIs中概述,如果您必须为列表视图执行动画,则应将stableIds设置为true,上述dev bytes系列中的更多示例假定具有稳定的id以实现列表项的动画持久性。

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