美文网首页android待研究各种view
RecyclerView网络图像刷新会闪烁

RecyclerView网络图像刷新会闪烁

作者: lanceJin | 来源:发表于2017-08-12 12:17 被阅读2586次

    1.前言


    最近将Glide3.8升级到4.0,除了使用上有细微的调整,过渡还是很流畅的。但是在替换完,并体验到新特性带来的便利后,测试反馈了个现象,标记为Bug让修复。以下记录分析问题和解决问题的过程,希望对大家有一定的帮助。若有更好的方法,欢迎在评论中指出,大家共同探讨。

    2.常见现象


    界面很简单就不上图了,大概描述一下。一个横向的RecyclerView中展示一堆Item,每个Item分别包含纵向排布的圆形头像和名字。根据用户的操作,会向其中添加、删除和更新某些或某个Item。由于是对数据操作,肯定得刷新RecyclerView的Adapter。这时,会导致Item先显示底色或占位图,再渐显地加载图像。

    Glide.png

    由于官方给出说明,有三种圆形ImageView会与Glide的方法产生问题,建议使用BitmapTransformation,或.dontAnimate()取消加载过渡动画,又或者4.0中的.circleCrop()裁剪图像。本人亲测,谷歌官方v4包中的RoundedBitmapDrawable也是支持Glide的,不知道的同学可以参考小菜希的Blog

    然而,在按文档修改完后,问题仍然出现了。这是什么原因呢?仅仅只是升级Glide,并没有改变业务逻辑啊,难道是新版Glide的原因。排除自己使用错了,不可能还原回去,指望着它的新特性解决问题呢。

    3.解决方案


    经过向谷歌询问N遍,发现很可能是RecyclerView的使用姿势不对。就几种常见情况给大家分析一下:

    3.1.全局刷新

    当你使用.notifyDataSetChanged()刷新列表时,会更新所有的数据。耗资源不说,关键是整个界面图像重新加载,给用户莫名其妙的感觉,影响体验和心情。

    都知道RecyclerView的Item是可以复用,但是如何复用,可能就不大清楚了。每个Item被复用是随机的,它移出屏幕外被回收后,等待再次使用。全局刷新,会让回收的Item加载内容,没回收的可能内容被别的Item先加载了,只能回收后加载。一瞬间Item的位置全乱了,都得重新加载内容,尤其图像加载较慢,你就感觉整个世界胡乱地闪了一下。

    知道原因,想解决就不难了。给每个Item一个固定的ID,刷新时与位置对应,别瞎跑就可以了。重写Adapter的getItemId(int position)方法,返回position作为ID,然后再设置Adapter的setHasStableIds(true)即可。

    3.2.局部刷新

    对RecyclerView了解比较多的人都知道,它的Adapter新增了不少局部刷新的方法,具体如下:

    • notifyItemChanged(int position) 更新列表position位置上的数据时调用,伴有渐变动画效果
    • notifyItemInserted(int position) 列表position位置添加一条数据时调用,伴有动画效果
    • notifyItemRemoved(int position) 列表position位置移除一条数据时调用,伴有动画效果
    • notifyItemMoved(int fromPosition, int toPosition) 列表fromPosition位置的数据移到toPosition位置时调用,伴有动画效果
    • notifyItemRangeChanged(int positionStart, int itemCount) 列表从positionStart位置到itemCount数量的列表项进行数据刷新,伴有渐变动画效果
    • notifyItemRangeInserted(int positionStart, int itemCount) 列表从positionStart位置到itemCount数量的列表项批量添加数据时调用,伴有动画效果
    • notifyItemRangeRemoved(int positionStart, int itemCount) 列表从positionStart位置到itemCount数量的列表项批量删除数据时调用,伴有动画效果

    使用局部刷新解决了Item错乱的问题,并且减少了额外的资源消耗,可是闪烁仍然存在。只不过导致的原因变了,是由于渐变动画效果增加了展示图像的时间。所以解决方法也很简单,就是取消或屏蔽过渡动画。提供两种常用的方法供大家参考:

    // 第一种,直接取消动画
    ItemAnimator animator = recyclerView.getItemAnimator();
    if (animator instanceof SimpleItemAnimator) {
      ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
    }
    // 老版本的支持库
    recyclerView.getItemAnimator().setSupportsChangeAnimations(false);
    
    // 第二种,设置动画时间为0
    recyclerView.getItemAnimator().setSupportsChangeAnimations(false);
    

    4.性能优化


    若你觉得解决问题就完了,那么下面的可以不用看了。

    细心的朋友会发现RecyclerView的Adapter还有一个方法没有讲,那就是notifyItemChanged(int position, Object payload)。payload对象有啥用?怎么用?别着急,听我慢慢道来。

    当你只想对Item中的名字进行更改,那么刷新Item会重复加载图像,哪怕已经缓存了,可毕竟还是消耗资源。如何才能仅仅更新Item中的某一项嘞,方法是有的。刷新其实是调用Adapter的onBindViewHolder(RecyclerView.ViewHolder holder, int position)方法,当你重写时会发现还有一个类似的方法onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads),现在知道payload对象是干嘛用的了吧。你可以在重写时对payload进行判断,从而更新Item中不同的项,枚举类型是个不错的选择。

    5.总结


    主要的都讲完了,通过这篇文章反映了解决问题的思路,希望能对大家有所启发。最后,忍不住偷偷透露一下,若你想同时进行增加、移动和修改,并且都得带上动画咋办?谷歌Support Library提供了DiffUtil工具类来比较每一项的变化,真是良心之作,还不知道的点击这里

    相关文章

      网友评论

      • 举头望明月泣:onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads)
        这个有例子吗??我实在想不出怎么用啊
      • 鲁班一号:重写Adapter的getItemId(int position)方法,返回position作为ID,然后再设置Adapter的setHasStableIds(true)即可。
        这种方法不管用 楼主试过没有 我试了没用
        lanceJin:不好意思,最近忙于项目才看到。我遇到的问题是点击某个item,使RecyclerView发生滚动,从而让item居中。刚开始,滚动时会导致每个item刷新(闪一下),感觉data没改变,不应该导致全局刷新,后来使用setHasStableIds(true)方法,使位置确定就解决了。我觉得你没起作用,可能是你没找到导致问题产生的原因,对症下药很重要,希望对你有帮助。:blush:
      • 墨源:这个BUG我遇到都是禁用RecyclerView的动画,或者你实在是想加载动画,OK,重写RecyclerView的默认动画,因为RecyclerView的默认动画有个BUG,以后估计会修复,还真不是你使用的姿势不对。
        lanceJin: @墨源 好的,我知道了,谢谢!
        墨源:@lanceJin 你上Google上搜,有很多。
        lanceJin:原来是默认动画的Bug,有文章详细介绍吗?:flushed:

      本文标题:RecyclerView网络图像刷新会闪烁

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