ListView/RecyclerView相关知识点

一、缓存机制对比

1.1 ListView(两级缓存)

ListView
是否需要回调createView 是否需要回调bindView 生命周期 备注
mActiveViews onLayout函数周期中 用于屏幕内ItemView快速重用
mScrapViews 与mAdapter一致,当adapter被更换时,mScrapViews会被清空

1.2 RecyclerView(四级缓存)

RecyclerView
是否需要回调createView 是否需要回调bindView 生命周期 备注
mAttachedScrap onLayout函数周期内 用于屏幕内ItemView快速重用
mCacheViews 与Adapter一致,当mAdapter被更换时,mCacheViews即被缓存至mRecyclerPool 默认上限为2,即缓存屏幕外2个ItemView
mViewCacheExtension 不直接使用,需要用户在定制,默认不实现
mRecyclerPool 与自身生命周期一致,不再被引用时即被释放 默认上限为5,技术上可以实现所有RecyclerViewPool共用同一个

1.3 ListView和RecyclerView获取缓存流程

ListView获取缓存原理

listview获取缓存原理.jpg

RecyclerView获取缓存原理

recyclerview获取缓存原理.jpg

二、RecyclerView局部刷新

ListView离屏缓存实现机制:ListView从mScrapViews根据pos获取相应的缓存,但是没有直接用,而是重新getView,bindView。RecyclerView中通过pos获取的是viewholder,即pos->(view, viewHolder, flag),标志位flag的作用是判断View是否需要重新bindView。

三、ListView开发注意事项

3.1 ListView加载图片数据混乱

ListView是根据RecycleBin来实现ItemView的复用的,出现图片数据错乱加载的原因是每次从mScrapViews中获取ItemView,都会重新调用getView()方法,但是图片是异步加载的,但是ItemView是复用的。所以可以通过tag来区分当前ImageView和要显示的图片是否一致,如果一致则不去网络加载。

// 给 ImageView 设置一个 tag
holder.img.setTag(imgUrl);
// 预设一个图片
holder.img.setImageResource(R.drawable.ic_launcher);

// 通过 tag 来防止图片错位
if (imageView.getTag() != null && imageView.getTag().equals(imageUrl)) {
    imageView.setImageBitmap(result);
}
231543494598347.jpg

3.2 ListView的优化

  • 使用ViewHolder复用机制

由于ListView的实现机制,每次从缓存里面获取ItemView都需要getView(),重新inflate布局比较耗时,可以给ListView添加ViewHolder并设置tag,代码如下:

ViewHolder viewHolder = null;
View view = null;//getView方法要返回的View
if(convertView == null){//如果当前没有可以复用的View
   viewHolder = new ViewHolder();
   view = LayoutInflater.from(context).inflate(resourceId,null);//那么就从XML文件生成一个View
   viewHolder.resourceViewName = view.findViewById(resouceViewId);//从XML中找到对应的View
   view.setTag(viewHolder);//将ViewHolder设置在当前ItemView的tag里面
}else{//否则
   view = convertView;//就使用可以复用的View
   viewHolder = (ViewHolder)convertView.getTag();//从复用的View中取出viewHoder
}

class  ViewHolder {  
    TextView name;  
}  
  • 对图片的优化

    给图片设置内存缓存(LruCache,SoftReference),当使用图片时可用直接加载好的图片,这样体验会更流畅;另外当用户快速滑动ListView时候,可以默认为不加载。

listView.setOnScrollListener(new AbsListView.OnScrollListener() {
  @Override
  public void onScrollStateChanged(AbsListView view, int scrollState) {
       if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {//list停止滚动时加载图片    
           loadImage(startPos, endPos);// 异步加载图片   ,只加载可以看到的图片    
       }    
  }

  @Override
  public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
  //设置当前屏幕显示的起始pos和结束pos   
        startPos = firstVisibleItem;    
        endPos = firstVisibleItem + visibleItemCount;    
        if (endPos >= totalItemCount) {    
         endPos = totalItemCount - 1;    
        }    
  }
});
  • onClickListener的处理

当contentView中有很多View需要对点击事件的处理,可以集中在viewHolder中处理:

class  ViewHolder implements OnClickListener{  
    int position;  
    TextView name;  
  
    public void setPosition(int position){  
        this.position = position;  
    }  
  
    @Override  
    public void onClick(View v) {  
        switch (v.getId()){  
            //XXXX  
        }  
    }  
}  

ViewHolder viewHolder = null;
View view = null;//getView方法要返回的View
if(convertView == null){//如果当前没有可以复用的View
   viewHolder = new ViewHolder();
   view = LayoutInflater.from(context).inflate(resourceId,null);//那么就从XML文件生成一个View
   viewHolder.resourceViewName = view.findViewById(resouceViewId);//从XML中找到对应的View
   viewHolder.setPosition(position);//设置位置
   viewHolder.name.setOnClickListener(viewHolder);//设置ClickListener
   view.setTag(viewHolder);//将ViewHolder设置在当前ItemView的tag里面
}else{//否则
   view = convertView;//就使用可以复用的View
   viewHolder = (ViewHolder)convertView.getTag();//从复用的View中取出viewHoder
}

四、RecyclerView开发注意事项

4.1 RecyclerView的性能优化

  • 数据方面的处理

    类似于这种对数据进行html栅格化的操作 ( Html.fromHtml(data) ), 有必要放在子线程和网络加载中一起处理,可以让用户觉得数据加载的时间会稍微长点;分页加载的数据要进行缓存起来,加快加载速度。

  • 布局优化

    1. 减少过度绘制,可考虑自定义View来减少层级,或更合理的设置布局来减少层级,不推荐在RecyclerView中使用ConstranintLayout。
    2. 减少xml文件inflate时间,如果当item的复用几率很低的情况下,随着type增多,可以尝试用代码去生成布局,这样可以加快布局的加载速度。
    3. 减少View对象的创建,尽量的简化ItemView,设计ItemType时,多对ViewType能够共用的地方设计成自定义View。
  • 其他

    1. 25.1.0版本及以上使用Prefetch功能,RecyclerView视图数据预取。

    在RecyclerView显示一帧的过程包括UI线程创建视图和绑定视图的操作,然后交给RenderThread发指令通知GPU进行渲染的操作,但是在显示一帧完成和下帧开始创建视图的过程中,UI线程是无所事事的,可以在这个时候对后面的视图进行创建和绑定操作。

    1. 如果Item高度是固定的话,可以使用RecyclerView.setHasFixedSize(true);来避免requestLayout浪费资源。

    2. 设置RecyclerView.addOnScrollListener(listener),来对滑动过程中停止加载的操作。

    3. 如果不要求动画,可以调用 getItemAnimator().setSupportsChangeAnimations(false),把默认动画关闭。

    4. 对TextView使用String.toUpperCase来替代 android:textAllCaps="true"。

    5. 通过重写RecyclerView.onViewRecycled(holder)来回收资源,当这个方法被回调的时候,就表示表示这个Holder已经被扔进mRecyclerPool.mScrap里了,也就是再次取出的时候会经过onBindViewHolder方法重新绑定数据。

    6. 通过设置RecyclerView.setItemViewCacheSize(size);来加大RecyclerView的缓存,用空间来换取时间上的流畅性。

    7. 如果多个RecyclerView的Adapter是一样的,比如嵌套的RecyclerView中存在一样的Adapter,可以设置RecyclerView.setRecycledViewPool(pool);来共用一个RecycledViewPool。

    8. 对ItemView设置监听器,不要对每个Item都调用addXXListener,应该共用一个Listener,根据ID来进行不同的操作,优化了对象的频繁创建带来的资源消耗。

    9. 通过getExtraLayoutSpace来增加RecyclerView预留的额外空间。

    //显示范围之外提供更大的缓存空间。
    new LinearLayoutManager(this) {
        @Override
        protected int getExtraLayoutSpace(RecyclerView.State state) {
            return size;
        }
    };
    

4.2 stableId 的使用

setHasStableIds用来标识每一个itemView是否需要一个唯一标识,当stableId设置为true的时候,每一个itemView数据就有一个唯一标识。getItemId()返回代表这个ViewHolder的唯一标识,如果没有设置stableId唯一性,返回NO_ID=-1。通过setHasStableIds可以使itemView的焦点固定,从而解决RecyclerView的notify方法使得图片加载时闪烁问题。注意:setHasStableIds()必须在 setAdapter() 方法之前调用,否则会抛异常。因为RecyclerView.setAdapter后就设置了观察者,设置了观察者stateIds就不能变了。

4.3 7.0新工具类DiffUtil

DiffUtil是support-v7:24.2.0中的新工具类,它用来比较两个数据集,寻找出旧数据集—>新数据集的最小变化量,它和mAdapter.notifyDataSetChanged()最大不同在于`它会自动计算新老数据集的差异,并根据差异情况,自动调用以下四个方法:

adapter.notifyItemRangeInserted(position, count);
adapter.notifyItemRangeRemoved(position, count);
adapter.notifyItemMoved(fromPosition, toPosition);
adapter.notifyItemRangeChanged(position, count, payload);

且调用notifyDataSetChanged()不会触发RecyclerView的动画(删除、新增、位移、change动画),其次性能较低,它不管数据是否一样都整个刷新了一遍整个RecyclerView ,使用如下:

public abstract static class Callback {
    public abstract int getOldListSize();//老数据集size

    public abstract int getNewListSize();//新数据集size

    //新老数据集在同一个position的Item是否是一个对象,如果给itemView设置了stableIds,则仅比较它们单独的           //id(可能内容不同,如果这里返回true,会调用下面的方法)
    public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);

    //这个方法仅仅是上面方法返回true才会调用,判断item的内容是否有变化,类似于Object.equals(Object)
    public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);

    //当areItemsTheSame()返回true且areContentsTheSame()返回false,
    //同之处用下面的方法找出两个itemView的data不        
    @Nullable
    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
        return null;
    }
}

//使用的时候实现Callback接口
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new ProductListDiffCallback(mProducts, newProducts));
diffResult.dispatchUpdatesTo(mProductAdapter);

4.4 NestedScrollView嵌套RecyclerView

  • 滑动IRecyclerView列表会出现强烈的卡顿感

    mRecyclerView.setNestedScrollingEnabled(false);

    RecyclerView默认是setNestedScrollingEnabled(true),是支持嵌套滚动的,也就是说当它嵌套在NestedScrollView中时,默认会随着NestedScrollView滚动而滚动,放弃了自己的滚动。将该值置false可以让RecyclerView不支持嵌套滑动,这样RecyclerView可以自己响应滑动事件。

  • 每次打开界面都是定位在RecyclerView在屏幕顶端,列表上面的布局都被顶上去了

    RecyclerView抢占了焦点,自动滚动导致的.

    RecyclerView会在构造方法中调用setFocusableInTouchMode(true), 抢占焦点后一定会定位到第一行的位置,可以在NestedScrollView中添加属性:android:focusableInTouchMode="true",同时在RecyclerView中添加属性:android:descendantFocusability="blocksDescendants"或直接设置mRecyclerVIew.setFocusableInTouchMode(false)

4.5 图片混乱和闪烁的问题

  • 当Item还在加载图片的过程中,被移除屏幕可视范围,不需要继续加载这张图片了,可以在onViewRecycled中取消图片的加载,这样就不会造成图片加载完设置到其他Item的ImageView中了。
  • 每一个经过屏幕可视区域的item,加载的图片都要放到缓存中,即使item离开了可视区域,也要加载完毕并放入缓存中,方便下次浏览时能够快速加载,每次onBind时对ImageView设置tag标记,如果tag标记已经被更改了,旧线程加载好的图片不再设置到ImageView中。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342