首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >日常开发踩坑记-刷新抖动

日常开发踩坑记-刷新抖动

作者头像
韦东锏
发布于 2021-09-29 07:18:24
发布于 2021-09-29 07:18:24
84200
代码可运行
举报
文章被收录于专栏:Android码农Android码农
运行总次数:0
代码可运行

recyclerview刷新抖动踩坑记

问题:下拉刷新后,更新数据,页面顶部的UI会闪烁

看下目前的实现 整个页面是一个大的Recycle了View,顶部是一个item,内部也是采用RecyclerView实现的,代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//顶部Item的Holder                                                           
private class TabMainCategoryHolder(                                      
    val binding: ItemTabMainCategoryTypeBinding,                          
    categoryItemClick: ItemClickListener                                  
) :                                                                       
    RecyclerView.ViewHolder(binding.root) {                               
                                                                          
    init {                                                                
        //Holder初始化的时候,就先设置好了layoutManger跟adapter                       
        binding.rvItemCategory.layoutManager =                            
            GridLayoutManager(binding.root.context, CATEGORY_SPAN_COUNT)  
        val categoryAdapter = TabMainCategoryAdapter()                    
        categoryAdapter.itemClickListener = categoryItemClick             
        binding.adapter = categoryAdapter                                 
    }                                                                     
                                                                          
    fun onBind(navigation: List<GoodsItem>?) {                            
        //在每次onBindViewHolder的时候,更新数据,刷新item                           
        val dataList = navigation ?: return                               
        binding.adapter?.dataList = dataList                              
        binding.adapter?.notifyDataSetChanged()                           
    }                                                                     
}                                                                         

然后看下TabMainCategoryAdapter的代码,也是非常的简单的adapter

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class TabMainCategoryAdapter :
    BaseAdapter() {

    var dataList = listOf<GoodsItem>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val binding = ItemTabMainCategoryItemBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return TabMainCategoryItemHolder(binding)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is TabMainCategoryItemHolder) {
            holder.binding.item = dataList[position]
        }
    }

    override fun getItemCount(): Int {
        return dataList.size
    }
    
    class TabMainCategoryItemHolder(val binding: ItemTabMainCategoryItemBinding) :
        RecyclerView.ViewHolder(binding.root) {

    }
}

看到这里,基本能猜到闪烁的原因了吧

其实是在notifyDataChange后,holder在复用的时候,每个holder不是原来位置的holder了,所以重新绑定数据,发生了闪烁

我们验证下上面的猜想

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class TabMainCategoryItemHolder(val binding: ItemTabMainCategoryItemBinding) :
    RecyclerView.ViewHolder(binding.root) {                                   
        //缓存上次的itemID的值                                                       
        var itemId = 0                                                        
}                                                                             

然后打印在onBind的时候,打印itemId信息

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {                    
    if (holder is TabMainCategoryItemHolder) {                                                     
        val item = dataList[position]                                                              
        //打印holder上次的id跟这次新的item的id的值                                                              
        Pug.d("TabMainCategoryAdapter", "last Id ${holder.itemId} current id ${item.id}")          
        holder.itemId = item.id                                                                    
        holder.binding.item = item                                                                 
    }                                                                                              
}                                                                                                  

然后下拉刷新,看下结果

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//第一次刷新
D/TabMainCategoryAdapter: last Id 6 current id 1       
D/TabMainCategoryAdapter: last Id 7 current id 2       
D/TabMainCategoryAdapter: last Id 8 current id 3       
D/TabMainCategoryAdapter: last Id 9 current id 4       
D/TabMainCategoryAdapter: last Id 10 current id 5      
D/TabMainCategoryAdapter: last Id 0 current id 6       
D/TabMainCategoryAdapter: last Id 0 current id 7       
D/TabMainCategoryAdapter: last Id 0 current id 8       
D/TabMainCategoryAdapter: last Id 0 current id 9       
D/TabMainCategoryAdapter: last Id 0 current id 10      

//第二次刷新
D/TabMainCategoryAdapter: last Id 6 current id 1   
D/TabMainCategoryAdapter: last Id 7 current id 2   
D/TabMainCategoryAdapter: last Id 8 current id 3   
D/TabMainCategoryAdapter: last Id 9 current id 4   
D/TabMainCategoryAdapter: last Id 10 current id 5  
D/TabMainCategoryAdapter: last Id 0 current id 6   
D/TabMainCategoryAdapter: last Id 0 current id 7   
D/TabMainCategoryAdapter: last Id 0 current id 8   
D/TabMainCategoryAdapter: last Id 0 current id 9   
D/TabMainCategoryAdapter: last Id 0 current id 10  

可以发现,每次刷新前后,复用的holder都不是原来的holder,都需要重新设置图片跟文案,所以产生了闪烁

这里,问题来了,每次刷新,后面五个holder的初始ID都是0,说明每次刷新,都新建了五个viewHolder,viewHolder不是复用的?为什么还会不停的新建

在recyclerview的源码,可以发现答案

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static class RecycledViewPool {                  
    private static final int DEFAULT_MAX_SCRAP = 5;     

原来RecycledViewPool内部只最多缓存五个,看下具体缓存viewHolder的代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**                                                                                         
 * Add a scrap ViewHolder to the pool.                                                      
 * <p>                                                                                      
 * If the pool is already full for that ViewHolder's type, it will be immediately discarded.
 * @param scrap ViewHolder to be added to the pool.                                         
 */                                                                                         
public void putRecycledView(ViewHolder scrap) {                                             
    final int viewType = scrap.getItemViewType();                                           
    final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;       
    if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {                             //超过五个,不会加到缓存,会被清除掉  
        return;                                                                             
    }                                                                                       
    if (DEBUG && scrapHeap.contains(scrap)) {                                               
        throw new IllegalArgumentException("this scrap item already exists");               
    }                                                                                       
    scrap.resetInternal();                                                                  
    scrapHeap.add(scrap);                                                                   
}                                                                                           

相同的viewType的holder,最多缓存五个,超过的直接丢弃

因为这里一共有10个item,所以每次刷新调用notify后,原来的10个holder会被标记为失效,进入了缓存池,而缓存池最多容纳5个,在绑定新的数据,复用holder,还需要重新创建5个新的holder

分析到此,其实解决方案已经非常清楚了

解决方案1:把RecyclerViewPool的最大的缓存数量设置为10
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
rvItemCategory.recycledViewPool.setMaxRecycledViews(0, 10)

可以看下效果

除了上面的方案,其实还有另外一个修复方案,继续看下RecyclerView内部的源码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 if (viewHolder.isInvalid() && !viewHolder.isRemoved()       
         && !mRecyclerView.mAdapter.hasStableIds()) {        
     removeViewAt(index);                                    
     recycler.recycleViewHolderInternal(viewHolder);         
 } else {                                                    
     detachViewAt(index);                                    
     recycler.scrapView(view);                               
     mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
 }                                                           

可以发现,如果有设置stableId的话,就不会走recycledViewPool,而是进入scrap缓存

修复方案2:设置HasStableId为true
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class TabMainCategoryAdapter :
    BaseAdapter() {

    init {
        //初始化的时候,设置stableId为true
        setHasStableIds(true)
    }

然后adapter需要返回的下每个item的id值

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
override fun getItemId(position: Int): Long { 
    return dataList[position].id.toLong()     
}                                             

这样也可以修复闪烁问题,两种对比,建议选用方案2

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-05-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android码农 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
recyclerView源码解析
使用recyclerView主要需要了解RecyclerViewAdapter、layoutManager。重写recyclerViewAdapter的三个方法,onCreateViewHodler、onBindViewHolder、getItemCount。Adpater的职责是对用数据对每个item进行填充。layoutManager的作用主要就是用摆放item的位置。
用户10208357
2022/11/20
8950
Recyclerview竟能如此丝滑,这14个优化策略不容错过...
在Android开发中,RecyclerView是一种常用的列表控件,用于展示大量数据。然而,随着数据量的增加,RecyclerView的性能可能会受到影响,导致卡顿、内存泄漏等问题。本文将介绍一些优化技巧,帮助大家提升RecyclerView的性能,使其在各种情况下都能保持流畅。
Rouse
2024/03/25
2.2K0
Recyclerview竟能如此丝滑,这14个优化策略不容错过...
RecyclerView优化实战指南
在 Android 开发中,RecyclerView 是一个非常常用的组件,用于展示大量数据。然而,如果不进行优化,RecyclerView 可能会导致 UI 卡顿、内存泄漏等问题。本文将介绍一些优化技巧,帮助你更好地使用 RecyclerView。
Rouse
2023/08/31
7080
RecyclerView优化实战指南
构建Recyclerview DSL
上一篇文章说了如何把DSL用在项目的布局中,而这篇文章来讲讲怎么把DSL用在Recyclerview中。此框架已经在我的项目中大规模使用,并且极大地提高了Recyclerview列表构建效率和复用能力。
bennyhuo
2020/02/20
1K0
RecyclerView的复用机制
RecyclerView是Android业务开发非常常用的组件。我们知道它有复用,并且设计优雅。可能看过源码的同学还知道,它有几层复用。 但看网上的博客会发现,大多只是照着源码看一遍,并不会仔细地分析和推敲,RecyclerView为什么要设计这一层缓存,每一层缓存在什么情景下使用,以及每一层缓存的设置,对RecyclerView运行真正的影响。 所以,笔者试图通过本文,讲清楚以下几个问题:
Oceanlong
2020/06/28
1.4K0
RecyclerView缓存详解
RecyclerView的运行主要依赖于Adapter、LayoutManager和Recycler这三个类,其中Adapter负责与数据集交互,LayoutManager负责ItemView的布局,Recycler负责管理ViewHolder,其结构如下图。
Anymarvel
2021/12/08
1.1K0
RecyclerView缓存详解
Android基于DataBinding封装RecyclerView实现快速列表开发
在移动应用开发中,列表组件是一个非常常见的 UI 组件,绝大多数应用开发中都会使用到列表组件进行界面的开发,在 Android 开发中列表组件一般使用官方提供的 RecyclerView ,而 RecyclerView 的常规开发需要手动创建对应的 Adapter、ViewHolder 代码,且每个 RecyclerView 的使用都需要编写这种的样板代码,存在重复代码,降低了开发效率,于是为了提高列表的开发效率(偷懒)就有了各种对 RecyclerView 封装的框架来简化其开发流程,本篇就是其中一种框架的实现。
loongwind
2022/09/27
3.1K0
Android基于DataBinding封装RecyclerView实现快速列表开发
kotlin-android-extensions插件也被废弃了?扶我起来
kotlin-android-extensions插件可能算得上是我最喜欢的一个Kotlin在Android上的特性了。
用户1158055
2021/01/29
4.3K0
记一次全民K歌的crash定位过程
全民K歌4.6版本发布后,出现了一个与RecyclerView相关的Bug,作此记录。
QQ音乐技术团队
2018/07/02
2.5K0
Google挖坑后人埋-ViewBinding(上)
https://developer.android.com/topic/libraries/view-binding
用户1907613
2021/04/26
2.5K0
Recyclerview中使用databinding完成多布局
其实目前使用Recyclerview的关键部分在于adapter如何去编写,网上也有很多大神封装了各种万能adapter来供大家使用,但是对于我们这些新手,如果我们自己纯手撸一个adapter的话,可能会加深我们对于adapter的理解,下面就把我的撸法跟大家分享一下:
坑吭吭
2018/08/31
2.4K0
关于我使用的安卓View Binding方式
} 二、Using View Binding in Activities class MainActivity : AppCompatActivity() {
多凡
2021/12/06
7420
浅谈RecyclerView的性能优化
在我们谈RecyclerView的性能优化之前,先让我们回顾一下RecyclerView的缓存机制。
xuexiangjys
2022/11/29
2.1K0
viewBinding的使用(记一次重构项目的过程)
最近放寒假了,终于有空做项目了,想着把之前的一些项目重构一下,碰巧重构到 view Binding 这块,之前都是用 kotlin 的那个扩展,其实刚开始做项目的时候这个就已经废弃了,由于当时自己有点懒,没去学习这个新的代替方案,所以就成为一个历史遗留的问题,参考官方文档
wresource
2023/01/31
1.1K0
深入浅出 RecyclerView
原文:http://kymjs.com/code/2016/07/10/01 作者:kymjs张涛 今天推荐给各位的是张涛同学最近的一篇文章,说实话,RecyclerView 的文章挺多的,但像这样由浅入深,一步步讲到源码实现工作原理的不是那么多,推荐大家阅读。 起深入浅出这名字的时候我是慎重又慎重的,生怕被人骂标题党,写的什么破玩意还敢说深入浅出。所以还是请大家不要抱着太高的期望,因为没有期望就没有失望,就像陈润说的,超预期嘛。全当看小说的心情来看这系列文章了。 这篇文章分几个部分,简单跟大家讲一下 R
顶级程序员
2018/05/03
1.8K0
深入浅出 RecyclerView
使用优化 | RecyclerView中可优化的点
​ ViewHolder 和 item 是一一对应的关系,在创建一个item的时候就会创建一个 ViewHolder,这样当 Item 进行复用的时候就可以直接拿到 ViewHolder,从而防止重复进行 findViewById 。
345
2022/02/11
1.7K0
使用优化 | RecyclerView中可优化的点
ListView 和 RecyclerView 的对比分析
Android app中通过列表展示数据是非常常见的场景。 例如, IM类会话列表/消息列表就会使用列表进行数据展示。 列表数据显示如下图 :
艳龙
2021/12/16
1.4K0
ListView  和 RecyclerView  的对比分析
踩坑记-databinding
可以看到,布局是使用databinding来实现的,用item的值来判断可见性,自行验证了下,item的值没有问题,设置item是在onbindviewholder设置的,我们看下代码
韦东锏
2021/11/11
6790
踩坑记-databinding
Android面试之3个RecycleView经典面试题
解答: 在RecyclerView中,可以通过调用Adapter的notifyItemChanged(int position, Object payload)方法实现局部刷新,其中payload参数用于指定具体需要更新的控件或数据。
AntDream
2024/10/28
4320
Android面试之3个RecycleView经典面试题
Android面试之4个RecycleView面试题
解答: RecyclerView和ListView都是用于显示列表数据的控件,但RecyclerView在功能和性能上有显著的改进:
AntDream
2024/10/25
2510
Android面试之4个RecycleView面试题
相关推荐
recyclerView源码解析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档