一晃听、了解 RecyclerView
五虎上將
RecyclerView.LayoutManager
類型 | 作用 |
---|---|
RecyclerView.LayoutManager | 負責(zé)Item視圖的布局的顯示管理 |
RecyclerView.ItemDecoration | 給每一項Item添加子View,例如可以進行畫分割線之類 |
RecyclerView.ItemAnimator | 負責(zé)處理數(shù)據(jù)添加或刪除時的動畫效果 |
RecyclerView.Adapter | 為每一項Item創(chuàng)建視圖 |
RecyclerView.ViewHolder | 承載Item視圖的子布局 |
RecyclerView.Recycler | 負責(zé)處理View的緩存 |
調(diào)用關(guān)系
調(diào)用關(guān)系
二、緩存 ViewHolder
緩存機制
緩存流程
- scrap --> cache --> ViewCacheExtension --> RecycledViewPool --> 創(chuàng)建新的ViewHolder
- 滑出屏幕表項對應(yīng)的ViewHolder會被回收到mCachedViews+mRecyclerPool結(jié)構(gòu)中介却,mCachedViews是ArrayList谴供,默認存儲最多2個ViewHolder,當它放不下的時候齿坷,按照先進先出原則將最先進入的ViewHolder存入回收池的方式來騰出空間桂肌。mRecyclerPool是SparseArray,它會按viewType分類存儲ViewHolder永淌,默認每種類型最多存5個崎场。
緩存流程1
緩存流程2
總結(jié)
Scrap
說明 | 結(jié)果 |
---|---|
優(yōu)先級 | 一級 |
重新創(chuàng)建ViewHolder
|
false |
重新綁定數(shù)據(jù) | false |
容量 | 無大小限制,但最多包含屏幕可見表項 |
用途 | 用于布局過程中屏幕可見表項的回收和復(fù)用 |
類型 | ArrayList<ViewHolder> |
- mAttachedScrap : 緩存顯示在屏幕上的
item
的ViewHolder
- 按照
id
和position
來查找ViewHolder
- 按照
- mChangedScrap : 緩存發(fā)生變化的
item
的ViewHolder
(notifXXX)- 按照
id
和position
來查找ViewHolder
- 按照
Cached
說明 | 結(jié)果 |
---|---|
優(yōu)先級 | 二級 |
重新創(chuàng)建ViewHolder
|
false |
重新綁定數(shù)據(jù) | false |
容量 | 默認大小限制為2 |
用途 | 用于移出屏幕表項的回收和復(fù)用遂蛀,且只能用于指定位置的表項谭跨,有點像“回收池預(yù)備隊列” |
類型 | ArrayList<ViewHolder> |
- mCachedViews : 滑動過程中的回收和復(fù)用都是先處理的這個
List
- 只能復(fù)用于指定位置
- 最大儲存
mViewCacheMax = mRequestedCacheMax + extraCache
(extraCache
是由prefetch
的時候計算出來的) - 用于解決
RecyclerView
滑動抖動時的情況,還有用于保存Prefetch
ViewCacheExtension
說明 | 結(jié)果 |
---|---|
優(yōu)先級 | 二級 |
重新創(chuàng)建ViewHolder
|
/ |
重新綁定數(shù)據(jù) | / |
容量 | / |
用途 | 開發(fā)者可自定義的一層緩存 |
類型 | / |
- mViewCacheExtension : 開發(fā)者可自定義的一層緩存李滴,是虛擬類ViewCacheExtension的一個實例螃宙,開發(fā)者可實現(xiàn)方法
getViewForPositionAndType(Recycler recycler, int position, int type)
來實現(xiàn)自己的緩存- 適用場景(自備梯子)
- 位置固定
- 內(nèi)容不變
- 數(shù)量有限
Pool : 回收池
說明 | 結(jié)果 |
---|---|
優(yōu)先級 | 三級 |
重新創(chuàng)建ViewHolder
|
false |
重新綁定數(shù)據(jù) | true |
容量 | 默認每種類型最多存5個 |
用途 | 用于移出屏幕表項的回收和復(fù)用,且只能用于指定viewType的表項 |
類型 | 對ViewHolder 按類型分類存儲在SparseArray<ScrapData> 中所坯,同類ViewHolder 存儲在ScrapData 中的ArrayList 中 |
- mRecyclerPool : 在有限的
mCachedViews
放不下的時候谆扎,按先進先出
原則將最先進入的ViewHolder
存入mRecyclerPool
三、優(yōu)化
Ⅰ芹助、基礎(chǔ)優(yōu)化
1.onBindViewHolder
- 1.不要在
onBind
設(shè)置監(jiān)聽堂湖。因為mRecyclerPool
取ViewHolder
時要重新調(diào)用onBind
。onClickListener
應(yīng)設(shè)置在ViewHolder
状土。
/*=========== Adapter ===========*/
...
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
...
// 把item對應(yīng)的實體類傳給itemView苗缩,方便點擊事件發(fā)生時得到對應(yīng)的資源。
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 的時候設(shè)置声诸,因為 mRecyclerPool 取 ViewHolder 時要重新調(diào)用 onBind
mItem.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (null != mItemClick) {
// 設(shè)置點擊監(jiān)聽
mItemClick.onItemClick(v, (int) itemView.getTag());
}
}
});
}
...
}
// Item監(jiān)聽
private OnRvItemClick mItemClick;
// 設(shè)置監(jiān)聽
public void setItemClick(OnRvItemClick mItemClick) {
this.mItemClick = mItemClick;
}
public interface OnRvItemClick {
// 點擊項事件
void onItemClick(View v, int position);
}
/*=========== Other ===========*/
// 設(shè)置點擊項監(jiān)聽事件
Adapter.setItemClick(new OnRvItemClick() {
@Override
public void onItemClick(View v, int data) {
mAdapter.getItem(data);
}
});
- 2.不要在
onBindViewHolder
做邏輯判斷和計算
bindViewHolder
方法是在UI線程進行的酱讶,如果在該方法進行耗時操作,將會影響滑動的流暢性彼乌。
2.設(shè)置高度固定
- 如果
RecyclerView
固定寬高泻肯,只是用于展示固定大小的組件,可設(shè)置recyclerView.setHasFixedSize(true)
這樣可避免每次繪制Item
時慰照,不再重新計算高度灶挟。
RecyclerView.setHasFixedSize(true);
3.重寫 onScroll
- 對于大量圖片、復(fù)雜布局的
RecyclerView
考慮重寫onScroll
事件毒租,滑動暫停后再加載稚铣。
/*
// 空閑狀態(tài)
RecyclerView.SCROLL_STATE_IDLE
// 滾動狀態(tài)
RecyclerView.SCROLL_STATE_FLING
// 觸摸后狀態(tài)
RecyclerView.SCROLL_STATE_TOUCH_SCROLL
*/
// 添加滑動監(jiān)聽
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
// 空閑狀態(tài)時加載圖片(數(shù)據(jù))
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
Glide.with(mContext).resumeRequests();
}else {
Glide.with(mContext).pauseRequests();
}
}
});
4.最大程度不使用 notifyDataSetChanged
- 對于新增或刪除數(shù)據(jù)通過
DiffUtil
,來進行局部數(shù)據(jù)刷新,而不是一味的全局刷新數(shù)據(jù)惕医。
DiffUtil是support包下新增的一個工具類耕漱,用來判斷新數(shù)據(jù)和舊數(shù)據(jù)的差別,從而進行局部刷新抬伺。
DiffUtil的使用螟够,在原來調(diào)用mAdapter.notifyDataSetChanged()的地方:
// 設(shè)置數(shù)據(jù)
public void setData(final List<String> newData) {
//notifyDataSetChanged();
// 數(shù)據(jù)優(yōu)化
// 對于新增或刪除數(shù)據(jù)通過DiffUtil,來進行局部數(shù)據(jù)刷新峡钓,而不是一味的全局刷新數(shù)據(jù)妓笙。
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.刷新閃爍
- 設(shè)置穩(wěn)定Id
eg:當調(diào)用notifyDataSetChanged
的時候能岩,recyclerView
不知道到底發(fā)生了什么寞宫,所以它只能認為所有的東西都發(fā)生了變化,即拉鹃,將所有的viewHolder
都放入到pool
中辈赋。
但是,如果我們設(shè)置了stable ids
毛俏,那么就會不一樣了:viewHolder
被放入了scrap
中炭庙,而不是pool
中。注意煌寇,這里焕蹄,它的性能提升了很多!- 不用重新綁定阀溶,重新創(chuàng)建新的
viewHolder
腻脏,不用重新addView
。addView
會導(dǎo)致重新測量… - 原本我們需要調(diào)用
notifyItemMoved(4, 6)
银锻,但是現(xiàn)在直接調(diào)用notifyDataSetChanged()
就好了永品,但是測試結(jié)果不會有動畫效果。
- 不用重新綁定阀溶,重新創(chuàng)建新的
Adapter.setHasStableIds(true);
7.使用 RecyclerView Prefetch 功能
- Prefetch 功能
- 橫向嵌套击纬,利用
RecyclerView
數(shù)據(jù)預(yù)取功能鼎姐。
// 在嵌套內(nèi)部的LayoutManager中調(diào)用LinearLayoutManger的設(shè)置方法
// num的取值:如果列表剛剛展示4個半item,則設(shè)置為5
innerLLM.setInitialItemsPrefetchCount(num);
Ⅱ更振、緩存優(yōu)化
1.mCachedViews
- 設(shè)置
mCachedViews
大小
eg:我們有一個壁紙庫的列表炕桨,用戶經(jīng)常會上下(左右)滑動,那么我們增加cache
的容量肯腕,就會獲得更好得性能献宫。
然而對于feed流之類的列表,用戶很少返回实撒,所以增加cache
容量意義不大姊途。
RecyclerView.setItemViewCacheSize();
2.RecycledViewPool
- 設(shè)置每個類型儲存的容量
eg1:在我們滑動的過程中涉瘾,一個類型的viewHolder
在pool
中應(yīng)該一直只會存在一個(除非你使用了GridLayoutManager
),所以捷兰,如果你的pool
中存在多個viewHolder
的話立叛,他們在滾動過程中基本上是無用的。
eg2:當我們調(diào)用notifyDataSetChanged()
或者notifyItemRangeChanged(i, c)
(c
這個范圍非常大的時候)寂殉,那么很多viewHolder
都會最終被放入到pool
中囚巴,因為pool
只能放置5個原在,那么多余的就會被丟棄友扰,等待回收。最重要的是會重新create
與bind
對性能影響比較大庶柿。如果你的列表能夠容納很多行村怪,而且使用notifyDataSetChanged
方法比較頻繁,那么你應(yīng)該考慮設(shè)置一下容量大小浮庐。
RecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 20);
- 多個列表公用一個
RecycledViewPool
eg1:RecyclerView
嵌套RecyclerView
考慮設(shè)置RecyclerPool
緩存
RecyclerView.setRecycledViewPool();
// RecyclerView + RecyclerView甚负,每個item內(nèi)部RecyclerView設(shè)同一個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.設(shè)置回收
manager.setRecycleChildrenOnDetach(true);
manager.setOrientation(LinearLayoutManager.HORIZONTAL);
sRcy.setLayoutManager(manager);
// 2.設(shè)置緩存Pool
sRcy.setRecycledViewPool(childPool);
}
}
Ⅲ、其他優(yōu)化
數(shù)據(jù)優(yōu)化
- 分頁加載遠端數(shù)據(jù)审残,對拉取的遠端數(shù)據(jù)進行緩存梭域,提高二次加載速度。
四搅轿、常見解決
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