一、了解 RecyclerView
五虎上将
RecyclerView.LayoutManager
类型 |
作用 |
RecyclerView.LayoutManager |
负责Item视图的布局的显示管理 |
RecyclerView.ItemDecoration |
给每一项Item添加子View,例如可以进行画分割线之类 |
RecyclerView.ItemAnimator |
负责处理数据添加或删除时的动画效果 |
RecyclerView.Adapter |
为每一项Item创建视图 |
RecyclerView.ViewHolder |
承载Item视图的子布局 |
RecyclerView.Recycler |
负责处理View的缓存 |
调用关系
调用关系
二、缓存 ViewHolder
缓存机制
RecyclerView缓存机制
缓存流程
- scrap --> cache --> ViewCacheExtension --> RecycledViewPool --> 创建新的ViewHolder
- 滑出屏幕表项对应的ViewHolder会被回收到mCachedViews+mRecyclerPool结构中,mCachedViews是ArrayList,默认存储最多2个ViewHolder,当它放不下的时候,按照先进先出原则将最先进入的ViewHolder存入回收池的方式来腾出空间。mRecyclerPool是SparseArray,它会按viewType分类存储ViewHolder,默认每种类型最多存5个。
缓存流程1
缓存流程2
总结
Scrap
说明 |
结果 |
优先级 |
一级 |
重新创建ViewHolder
|
false |
重新绑定数据 |
false |
容量 |
无大小限制,但最多包含屏幕可见表项 |
用途 |
用于布局过程中屏幕可见表项的回收和复用 |
类型 |
ArrayList<ViewHolder> |
- mAttachedScrap : 缓存显示在屏幕上的
item
的ViewHolder
- 按照
id
和position
来查找ViewHolder
- mChangedScrap : 缓存发生变化的
item
的ViewHolder
(notifXXX)
- 按照
id
和position
来查找ViewHolder
Cached
说明 |
结果 |
优先级 |
二级 |
重新创建ViewHolder
|
false |
重新绑定数据 |
false |
容量 |
默认大小限制为2 |
用途 |
用于移出屏幕表项的回收和复用,且只能用于指定位置的表项,有点像“回收池预备队列” |
类型 |
ArrayList<ViewHolder> |
- mCachedViews : 滑动过程中的回收和复用都是先处理的这个
List
- 只能复用于指定位置
- 最大储存
mViewCacheMax = mRequestedCacheMax + extraCache
(extraCache
是由prefetch
的时候计算出来的)
- 用于解决
RecyclerView
滑动抖动时的情况,还有用于保存Prefetch
ViewCacheExtension
说明 |
结果 |
优先级 |
二级 |
重新创建ViewHolder
|
/ |
重新绑定数据 |
/ |
容量 |
/ |
用途 |
开发者可自定义的一层缓存 |
类型 |
/ |
- mViewCacheExtension : 开发者可自定义的一层缓存,是虚拟类ViewCacheExtension的一个实例,开发者可实现方法
getViewForPositionAndType(Recycler recycler, int position, int type)
来实现自己的缓存
Pool : 回收池
说明 |
结果 |
优先级 |
三级 |
重新创建ViewHolder
|
false |
重新绑定数据 |
true |
容量 |
默认每种类型最多存5个 |
用途 |
用于移出屏幕表项的回收和复用,且只能用于指定viewType的表项 |
类型 |
对ViewHolder 按类型分类存储在SparseArray<ScrapData> 中,同类ViewHolder 存储在ScrapData 中的ArrayList 中 |
- mRecyclerPool : 在有限的
mCachedViews
放不下的时候,按先进先出
原则将最先进入的ViewHolder
存入mRecyclerPool
三、优化
Ⅰ、基础优化
1.onBindViewHolder
- 1.不要在
onBind
设置监听。因为mRecyclerPool
取ViewHolder
时要重新调用onBind
。onClickListener
应设置在ViewHolder
。
/*=========== Adapter ===========*/
...
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
...
// 把item对应的实体类传给itemView,方便点击事件发生时得到对应的资源。
viewHolder.itemView.setTag(i);
}
...
class ViewHolder extends RecyclerView.ViewHolder {
...
private LinearLayout mItem;
ViewHolder(@NonNull final View itemView) {
...
mItem = itemView.findViewById(R.id.ll_item);
// onClickListener 不要在 onBind 的时候设置,因为 mRecyclerPool 取 ViewHolder 时要重新调用 onBind
mItem.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (null != mItemClick) {
// 设置点击监听
mItemClick.onItemClick(v, (int) itemView.getTag());
}
}
});
}
...
}
// Item监听
private OnRvItemClick mItemClick;
// 设置监听
public void setItemClick(OnRvItemClick mItemClick) {
this.mItemClick = mItemClick;
}
public interface OnRvItemClick {
// 点击项事件
void onItemClick(View v, int position);
}
/*=========== Other ===========*/
// 设置点击项监听事件
Adapter.setItemClick(new OnRvItemClick() {
@Override
public void onItemClick(View v, int data) {
mAdapter.getItem(data);
}
});
- 2.不要在
onBindViewHolder
做逻辑判断和计算
bindViewHolder
方法是在UI线程进行的,如果在该方法进行耗时操作,将会影响滑动的流畅性。
2.设置高度固定
- 如果
RecyclerView
固定宽高,只是用于展示固定大小的组件,可设置recyclerView.setHasFixedSize(true)
这样可避免每次绘制Item
时,不再重新计算高度。
RecyclerView.setHasFixedSize(true);
3.重写 onScroll
- 对于大量图片、复杂布局的
RecyclerView
考虑重写onScroll
事件,滑动暂停后再加载。
/*
// 空闲状态
RecyclerView.SCROLL_STATE_IDLE
// 滚动状态
RecyclerView.SCROLL_STATE_FLING
// 触摸后状态
RecyclerView.SCROLL_STATE_TOUCH_SCROLL
*/
// 添加滑动监听
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
// 空闲状态时加载图片(数据)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
Glide.with(mContext).resumeRequests();
}else {
Glide.with(mContext).pauseRequests();
}
}
});
4.最大程度不使用 notifyDataSetChanged
- 对于新增或删除数据通过
DiffUtil
,来进行局部数据刷新,而不是一味的全局刷新数据。
DiffUtil是support包下新增的一个工具类,用来判断新数据和旧数据的差别,从而进行局部刷新。
DiffUtil的使用,在原来调用mAdapter.notifyDataSetChanged()的地方:
// 设置数据
public void setData(final List<String> newData) {
//notifyDataSetChanged();
// 数据优化
// 对于新增或删除数据通过DiffUtil,来进行局部数据刷新,而不是一味的全局刷新数据。
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Override
public int getOldListSize() {
return getItemCount();
}
@Override
public int getNewListSize() {
return newData == null ? 0 : newData.size();
}
@Override
public boolean areItemsTheSame(int i, int i1) {
return true;
}
@Override
public boolean areContentsTheSame(int i, int i1) {
return false;
}
});
diffResult.dispatchUpdatesTo(this);
this.mData = newData;
}
5.减少每个 ItemView 的层级嵌套,减少过度绘制
6.刷新闪烁
- 设置稳定Id
eg:当调用notifyDataSetChanged
的时候,recyclerView
不知道到底发生了什么,所以它只能认为所有的东西都发生了变化,即,将所有的viewHolder
都放入到pool
中。
但是,如果我们设置了stable ids
,那么就会不一样了:viewHolder
被放入了scrap
中,而不是pool
中。注意,这里,它的性能提升了很多!
- 不用重新绑定,重新创建新的
viewHolder
,不用重新addView
。addView
会导致重新测量…
- 原本我们需要调用
notifyItemMoved(4, 6)
,但是现在直接调用notifyDataSetChanged()
就好了,但是测试结果不会有动画效果。
Adapter.setHasStableIds(true);
7.使用 RecyclerView Prefetch 功能
// 在嵌套内部的LayoutManager中调用LinearLayoutManger的设置方法
// num的取值:如果列表刚刚展示4个半item,则设置为5
innerLLM.setInitialItemsPrefetchCount(num);
Ⅱ、缓存优化
1.mCachedViews
- 设置
mCachedViews
大小
eg:我们有一个壁纸库的列表,用户经常会上下(左右)滑动,那么我们增加cache
的容量,就会获得更好得性能。
然而对于feed流之类的列表,用户很少返回,所以增加cache
容量意义不大。
RecyclerView.setItemViewCacheSize();
2.RecycledViewPool
- 设置每个类型储存的容量
eg1:在我们滑动的过程中,一个类型的viewHolder
在pool
中应该一直只会存在一个(除非你使用了GridLayoutManager
),所以,如果你的pool
中存在多个viewHolder
的话,他们在滚动过程中基本上是无用的。
eg2:当我们调用notifyDataSetChanged()
或者notifyItemRangeChanged(i, c)
(c
这个范围非常大的时候),那么很多viewHolder
都会最终被放入到pool
中,因为pool
只能放置5个,那么多余的就会被丢弃,等待回收。最重要的是会重新create
与bind
对性能影响比较大。如果你的列表能够容纳很多行,而且使用notifyDataSetChanged
方法比较频繁,那么你应该考虑设置一下容量大小。
RecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 20);
- 多个列表公用一个
RecycledViewPool
eg1:RecyclerView
嵌套RecyclerView
考虑设置RecyclerPool
缓存
RecyclerView.setRecycledViewPool();
// RecyclerView + RecyclerView,每个item内部RecyclerView设同一个pool
private RecyclerView.RecycledViewPool childPool;
public XXAdapter(){
childPool = new RecyclerView.RecycledViewPool();
}
private class RcyViewHolder extends RecyclerView.ViewHolder {
private SRecyclerView sRcy;
public RcyViewHolder(View itemView) {
super(itemView);
sRcy = itemView.findViewById(R.id.rcy_child);
LinearLayoutManager manager = new LinearLayoutManager(mContext);
// 1.设置回收
manager.setRecycleChildrenOnDetach(true);
manager.setOrientation(LinearLayoutManager.HORIZONTAL);
sRcy.setLayoutManager(manager);
// 2.设置缓存Pool
sRcy.setRecycledViewPool(childPool);
}
}
Ⅲ、其他优化
数据优化
- 分页加载远端数据,对拉取的远端数据进行缓存,提高二次加载速度。
四、常见解决
1.局部刷新
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position);
} else {
// 局部刷新
holder.setTitle("局部更新");
}
}
...
// 局部刷新测试
mAdapter.notifyItemChanged(2, "payload");
相关资料
资料
资料
资料
2019/07/19 12:28:12
网友评论