美文网首页androidAndroid杂识Android开发经验谈
解决RecyclerView notifyItem闪屏问题

解决RecyclerView notifyItem闪屏问题

作者: 尹star | 来源:发表于2016-06-21 23:19 被阅读15010次

之前由于业务需求,要在列表的Item中做一个点赞的效果,并且自己做了个动画效果,完了点赞的数目也要跟着改变,于是操作完RecyclerView做了一个notifyItemChanged()的操作,功能都顺利实现,美中不足的是当前Item闪了一下,QA甚至为此提了Bug,一开始以为是图片加载库的问题,之后随着图片加载库从ImageLoader换成Picaso,又换成Glide,这个Bug一直如影随形。后来才发现“闪一下”原来是RecyclerView的默认动画,我的代码里有这样一句 mRecyclerView.setItemAnimator(new DefaultItemAnimator());原来是这句代码搞的鬼,于是注掉再跑,但并没什么卵用。于是又改成mRecyclerView.setItemAnimator(null);仍然没什么卵用。看来加不加这句,RecyclerView都默认执行了这个动画,看来还有点小麻烦。

before.gif

先来了解下这个RecyclerView的动画吧。

RecyclerView.ItemAnimator

ItemAnimator能够帮助Item实现独立的动画。
ItemAnimator触发于以下三种事件:

某条数据被插入到数据集合中
从数据集合中移除某条数据
更改数据集合中的某条数据

在Android中默认实现了一个DefaultItemAnimator
,我们可以通过以下代码为Item增加动画效果:
recyclerView.setItemAnimator(new DefaultItemAnimator());

在之前的版本中,当数据集合发生改变时,我们通过调用notifyDataSetChanged()来刷新列表,因为这样做会触发列表的重绘,所以并不会出现任何动画效果,但现在我的需求是只改变了当前一个Item的状态,因此需要调用一些以notifyItem*()作为前缀的特殊方法,比如:

向指定位置插入Item
public final void notifyItemInserted(int position)
移除指定位置Item
public final void notifyItemRemoved(int position)
更新指定位置Item
public final void notifyItemChanged(int position)

但是现在的问题就是,调用notifyItem*()方法会触发RecyclerView的默认动画,而这个动画我并不想要,但是似乎并没有合适的办法来屏蔽这个动画,QA甚至认为这是个Bug,需要修复。与同行们交流了下,也有人遇到同样的问题,解决办法居然是调用notifyDataSetChanged()方法来刷新数据,这样就不会有闪一下的动画了。但是这样不就失去使用RecyclerView的优势和意义了吗?

最后我是通过重写RecyclerView的动画来解决这一“Bug”。

public class NoAlphaItemAnimator extends RecyclerView.ItemAnimator {

}

将DefaultItemAnimator类里的代码全部copy到自己写的动画类中,然后做一些修改。

首先找到private void animateChangeImpl(final ChangeInfo changeInfo) {}方法。

找到方法里这两句代码:
1:oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() { ... }

2:newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).alpha(1).setListener(new VpaListenerAdapter() { ... }

替换成:
1:oldViewAnim.setListener(new VpaListenerAdapter() { ... }

2newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).setListener(new VpaListenerAdapter() { ... }

也就是说分别去掉上述代码中的alpha(0)alpha(1),然后保存。这个时候基本就打工告成了。最后在自己的RecyclerView中进行如下调用mRecyclerView.setItemAnimator(new NoAlphaItemAnimator());,再跑起来,bug完美解决。

after.gif

关于这个问题,不知道大家有没有其他更好的办法解决,如果有可以告诉我,欢迎讨论交流。

上面的方案还是太重了,果然有同学有更好的方案:
((SimpleItemAnimator)recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);
加上这一句就可以了,设置为false,动画就不显示了。感谢@七方 同学。

还有另外一种方案:
recyclerView.getItemAnimator().setChangeDuration(0);
通过设置动画执行时间为0来解决问题,感谢@越风 同学。

相关文章

网友评论

  • suit_liu:同样的问题,动画时长设为0,动画设为空基本上都起作用,还有部分会闪烁(个别item),我加载的视频,还在找是什么原因
  • Jinbeen:我是列表显示更多的时候闪屏,试了很多方法,包括动画设置为空,消除动画时间以及透明度一直不变,结果都没好,昨天把图片显示框架换成fresco就好了……
  • 763cba3b6161:回复于 2018年5月14日09:50:37

    mRecyclerView.setItemAnimator(null); 即可解决问题...

    Support 包 24.XXX
  • 763cba3b6161:@2018年5月14日09:25:19

    mRecyclerView.setItemAnimator(null); 即可解决....

    Support 包版本 24.XXXX.
  • sollian:mark一下,我也遇到过,不过当时没有深究,直接修改的点赞图标
  • dinus_developer:建议全部
    animator.setChangeDuration(0);
    animator.setMoveDuration(0);
    animator.setRemoveDuration(0);
    animator.setAddDuration(0);
    设置为0
  • 1c5452070edf:用这个吧,notifyItemChanged(int position, Object payload)
  • bitman:这种应该使用局部刷新来解决吧
  • 相互交流:还有一种方案就是只更新某个控件,数据保存集合,不需要调用刷新列表方法,就不会闪屏...
  • 一碗沙世:可以:+1:
  • 1ddbcb1a307f:点赞刷新的时候 我也遇到了
    /**
    * 替换指定索引的数据条目
    *
    * @param location
    * @param newModel
    */
    public void setItem(int location, M newModel) {
    mData.set(location, newModel);

    //局部刷新
    notifyItemChanged(location, "22"); 这样也能解决
    }
    1ddbcb1a307f:查看源码,notifyItemChange(position)默认调用的notifyItemChange(position, null)
    compile 'com.android.support:recyclerview-v7:23.2.1'
    这个版本及以上的版本中recycleview 中新增
    /**
    * Notify any registered observers that the item at <code>position</code> has changed with an
    * optional payload object.
    *
    * <p>This is an item change event, not a structural change event. It indicates that any
    * reflection of the data at <code>position</code> is out of date and should be updated.
    * The item at <code>position</code> retains the same identity.
    * </p>
    *
    * <p>
    * Client can optionally pass a payload for partial change. These payloads will be merged
    * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the
    * item is already represented by a ViewHolder and it will be rebound to the same
    * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing
    * payloads on that item and prevent future payload until
    * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume
    * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not
    * attached, the payload will be simply dropped.
    *
    * @param position Position of the item that has changed
    * @param payload Optional parameter, use null to identify a "full" update
    *
    * @see #notifyItemRangeChanged(int, int)
    */
    public final void notifyItemChanged(int position, Object payload) {
    mObservable.notifyItemRangeChanged(position, 1, payload);
    }
    方法 ,楼主可以试试。
  • 斑马搬码:我也遇到了这个问题,搜到了你这,嫌太麻烦,于是我找到了这个方法,你看行不行:http://blog.csdn.net/u014537423/article/details/52777978
    尹star: @七方 感谢,有空试试。
  • 越风:这方案可行是可以的,就是太复杂。我是这样改的:
    mRecyclerViewgetItemAnimator().setChangeDuration(0); 把动画持续时间设置为0
    d7992c4bdb46:加这个((SimpleItemAnimator)recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);找不到SimpleItemAnimator类。加recyclerView.getItemAnimator().setChangeDuration(0);后无效,求破?谢谢
    d7992c4bdb46:加了无效
    尹star: @越风 有空试试
  • 砂砾han:adapter.notifyItemChanged(position, payload);然后重写adapter里的
    @Override
    public void onBindViewHolder(DynamicViewHolder holder, int position, List<Object> payloads) {
    if (payloads == null || payloads.isEmpty()) {
    onBindViewHolder(holder, position);
    } else {
    //change item
    }
    }
    不要重新加载图片就OK了
    刻薄小北:@砂砾han 我竟无言以对
    砂砾han:@明媚的天_简 更新数据的时候调用 adapter.notifyItemChanged(position, "digs");

    adapter中重写 onBindViewHolder(VH holder, int position, List<Object> payloads),应该是你姿势不对
    刻薄小北:payload接收不到,怎么破?
  • b0f758808ba4:Notifyitem 有个重载的方法 可以自定义刷新的
  • Pitty:简单点的
    ItemAnimator animator = new DefaultItemAnimator();
    animator.setChangeDuration(0);
    recyclerView.setItemAnimator(animator);
  • 隔壁王大锤:看到很多人说直接改UI,这样不太好,从设计模式角度考虑,最好是直接改数据,然后交给适配器去刷新
  • 5466dabd27c2:表示 DefaultItemAnimator 继承的是SimpleItemAnimator 不是RecyclerView.ItemAnimator 而且,我遇见了 是上拉加载 图片闪动 有点无解啊
  • 65dda56b3e3b:不需要notifyItemChanged,直接改ui就行了!
    尹star:@yjxandroid 我还要改model
  • 捡淑:马克
    shawn168:兄弟 我对你的头像很感兴趣呀~~介意不说话 兄嘚~!:grin:
  • 皮球二二:改变UI不需要刷新的,数据源变就行了
    北极星APP安卓杨涛:菜不是你错
  • MycroftWong:这个看个人喜好吧,闪烁一下我觉得不是什么大问题,如果不想的话,直接对item上的view操作就好了,没必要notify...
  • SnapKit:mark
  • hackware:这个其实都可以不用notifyItemChanged
    dinus_developer:@hackware 你有这种思维, 我不敢想象你的代码写的多么复杂
    hackware: @尹star 可以不用notify的,点击事件触发时,你本身可以拿到view,对view做更改、应用动画不就得了。同时记得更改一下model
    尹star: @hackware 我要改变UI啊,不notify怎么行
  • hackware:有时候闪一下是因为ImageView被重新加载,加载时先显示了placeholder,接着立马显示原图导致的
    LeoYe168:@尹star 层主说得对。这个问题跟你使用图片框架无关,我的解决方法是第一次刷新时候setTag,然后再刷判断图片URL时候变化,变化了才调用loadImage
    尹star: @hackware 不是图片库的问题,换了好几个库依旧
  • nothingwxq:我也遇到过类似的,但我没法去掉单个item的默认动画。
    尹star: @nothingwxq 试试文中的办法
    nothingwxq:@尹star 没找到好的解决办法,使用的笨拙的全局刷新。
    尹star:@nothingwxq 那怎么解决
  • jdsjlzx:暂时没有遇到。。。
    尹star:@jdsjlzx 你有notifyItemChanged()吗?
    jdsjlzx:@尹star
    回复速度很快啊,我是这么用的:

    Glide.with(context)
    .load(product.thumbUrl)
    .centerCrop()
    .placeholder(R.drawable.ic_product_default)
    .crossFade()
    .into(viewHolder.productImage);
    尹star: @jdsjlzx 我了个嚓,难道真是我的使用姿势不对

本文标题:解决RecyclerView notifyItem闪屏问题

本文链接:https://www.haomeiwen.com/subject/qwtgdttx.html