美文网首页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
      • mokewuwen:用这个吧,notifyItemChanged(int position, Object payload)
      • bitman:这种应该使用局部刷新来解决吧
      • 相互交流:还有一种方案就是只更新某个控件,数据保存集合,不需要调用刷新列表方法,就不会闪屏...
      • 一碗沙世:可以:+1:
      • 初升时朝阳:点赞刷新的时候 我也遇到了
        /**
        * 替换指定索引的数据条目
        *
        * @param location
        * @param newModel
        */
        public void setItem(int location, M newModel) {
        mData.set(location, newModel);

        //局部刷新
        notifyItemChanged(location, "22"); 这样也能解决
        }
        初升时朝阳:查看源码,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
        千纸鹤_4c15:加这个((SimpleItemAnimator)recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);找不到SimpleItemAnimator类。加recyclerView.getItemAnimator().setChangeDuration(0);后无效,求破?谢谢
        千纸鹤_4c15:加了无效
        尹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 而且,我遇见了 是上拉加载 图片闪动 有点无解啊
      • yjxandroid:不需要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