Listview與Recycleview的區別-(用法及快取機制)
用法上的區別
1、listview的用法
- 繼承的時BaseAdapter,需要重寫四個方法
- 不強制使用viewholder
- 可以直接使用item的點選事件
- 不用單獨設定分隔線
- 不可以定向重新整理某一條資料
示例程式碼如下:專案程式碼詳見地址:
public class MyListAdapter<T> extends BaseAdapter { private static final String TAG = "MyListAdapter"; private Context mContext; // private int itemViewId; private List<T> datas; public MyListAdapter(Context mContext, /*int itemViewId,*/ List<T> datas) { this.mContext = mContext; // this.itemViewId = itemViewId; this.datas = datas; } @Override public int getCount() { return datas == null ? 0 : datas.size(); } @Override public T getItem(int position) { return datas.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { Log.d(TAG, "getView: "+position); MyHolder holder; if (convertView==null){ convertView = View.inflate(mContext,R.layout.ada_item,null); holder = new MyHolder(convertView); convertView.setTag(holder); }else { holder = (MyHolder) convertView.getTag(); } holder.itemTv.setText((CharSequence) datas.get(position)); return convertView; } static class MyHolder { @BindView(R.id.item) TextView itemTv; public MyHolder(View view) { ButterKnife.bind(this,view); } } }
2、recycleview的用法
- 繼承的是Recycleview.Adapter
- 必須使用viewholder,封裝了view的複用
- 使用佈局管理器管理佈局的樣式(橫向、豎向、網格、瀑布流佈局)
- 點選事件可以使用給控制元件設定點選事件,也可以自定義點選事件。
- 可以自定義繪製分隔線
- 可以自定義item刪除增加的動畫效果
- 可以定向重新整理某一條資料
notifyItemChanged
等眾多方法
例項程式碼如下:
private static final String TAG = "MyRecycleAdapter"; private Context mContext; private List<T> datas; //可換成泛型 public MyRecycleAdapter(Context mContext, List<T> datas) { this.mContext = mContext; this.datas = datas; } static class MyViewHolder extends RecyclerView.ViewHolder{ @BindView(R.id.item) TextView itemTv; public MyViewHolder(@NonNull View itemView) { super(itemView); ButterKnife.bind(this,itemView); } } @NonNull @Override public MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i){ View inflate = LayoutInflater.from(mContext).inflate(R.layout.ada_item, viewGroup, false); return new MyViewHolder(inflate); } @Override public void onBindViewHolder(@NonNull MyViewHolder myViewHolder, final int i) { Log.d(TAG, "onBindViewHolder: "+i); // myViewHolder = new MyViewHolder() myViewHolder.itemTv.setText((CharSequence) datas.get(i)); //設定點選事件---或者使用自定義介面實現 myViewHolder.itemTv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(mContext, "點選了: "+i,Toast.LENGTH_LONG).show(); } }); } @Override public int getItemCount() { return datas==null ? 0:datas.size(); } }
recycleview 使用注意點:
- 必須給recycleview設定佈局管理方式
- 滑動方向的item佈局對應屬性(垂直-height,橫向-width)需要設定為wrap_content,否則會出現一個item佔滿整個螢幕的bug
快取上的區別
recycleview的快取機制
總結:
原始碼分析:
recycleview的快取機制是通過內部類實現的,檢視recycleview中的recycler,宣告如下:
public final class Recycler { //1級快取集合 --mAttachedScrap final ArrayList<RecyclerView.ViewHolder> mAttachedScrap = new ArrayList(); ArrayList<RecyclerView.ViewHolder> mChangedScrap = null; //2級快取集合 mCachedViews final ArrayList<RecyclerView.ViewHolder> mCachedViews = new ArrayList(); private final List<RecyclerView.ViewHolder> mUnmodifiableAttachedScrap; private int mRequestedCacheMax; int mViewCacheMax; //4級快取view池 RecyclerView.RecycledViewPool mRecyclerPool; //3級快取 private RecyclerView.ViewCacheExtension mViewCacheExtension; static final int DEFAULT_CACHE_SIZE = 2; }
從getViewForPosition追蹤,最終走的是tryGetViewHolderForPositionByDeadline方法:
if (RecyclerView.this.mState.isPreLayout()) {
//mState.isPreLayout()預設為false,只有有動畫時才為true
holder = this.getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
一級快取mAttached中獲取
二級快取mCachedViews中獲取
開始查詢item:
if (holder == null) {
//獲取holder----接下來詳細介紹
holder = this.getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
//檢驗holder是不是當前position
if (!this.validateViewHolderForOffsetPosition(holder)) {
if (!dryRun) { //dryRun傳過來的值是false
holder.addFlags(4);
if (holder.isScrap()) {
//不是當前position---從mAttached中刪除 RecyclerView.this.removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
//把當前view儲存到mCachedViews或者 addViewHolderToRecycledViewPool中
this.recycleViewHolderInternal(holder);
}
//並將holder置空,進入下一級查詢
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
獲取holder:getScrapOrHiddenOrCachedHolderForPosition
mCachedViews的預設儲存大小是: DEFAULT_CACHE_SIZE = 2;
int scrapCount = this.mAttachedScrap.size();
int cacheSize;
RecyclerView.ViewHolder vh;
//從mAttached中獲取holder
for(cacheSize = 0; cacheSize < scrapCount; ++cacheSize) {
vh = (RecyclerView.ViewHolder)this.mAttachedScrap.get(cacheSize);
if (!vh.wasReturnedFromScrap() && vh.getLayoutPosition() == position && !vh.isInvalid() && (RecyclerView.this.mState.mInPreLayout || !vh.isRemoved())) {
///holder是有效的,並且position相同
vh.addFlags(32);
return vh;
}
}
===================
//從mCachedViews中獲取holder
cacheSize = this.mCachedViews.size();
for(int i = 0; i < cacheSize; ++i) {
RecyclerView.ViewHolder holder = (RecyclerView.ViewHolder)this.mCachedViews.get(i);
if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
///holder是有效的,並且position相同
if (!dryRun) {
//獲取完之後從mCachedViews中移除
this.mCachedViews.remove(i);
}
return holder;
}
}
return null;
從原始碼可以看到,mCacheViews預設快取大小是 2,從mAttached和mCacheViews中取的holder都必須要求position是相同的位置,而且不關心type.
也就是說只有原來的位置可以重新複用這裡的viewholder,新的位置無法從mCachedViews中去viewholder。因此複用時也不需要重新bindView
在取完holder後才判斷位置是否正確,型別是否正確:
boolean validateViewHolderForOffsetPosition(RecyclerView.ViewHolder holder) {
if (holder.isRemoved()) {
return RecyclerView.this.mState.isPreLayout();
} else if (holder.mPosition >= 0 && holder.mPosition < RecyclerView.this.mAdapter.getItemCount()) {
if (!RecyclerView.this.mState.isPreLayout()) {
//type是否核對上
int type = RecyclerView.this.mAdapter.getItemViewType(holder.mPosition);
if (type != holder.getItemViewType()) {
return false;
}
}
if (RecyclerView.this.mAdapter.hasStableIds()) {
return holder.getItemId() == RecyclerView.this.mAdapter.getItemId(holder.mPosition);
} else {
return true;
}
} else {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder adapter position" + holder + RecyclerView.this.exceptionLabel());
}
}
//position的位置沒有找到,根據Adapter的id再找一便
int offsetPosition;
int type;
if (holder == null) {
//
offsetPosition = RecyclerView.this.mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= RecyclerView.this.mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item position " + position + "(offset:" + offsetPosition + ")." + "state:" + RecyclerView.this.mState.getItemCount() + RecyclerView.this.exceptionLabel());
}
type = RecyclerView.this.mAdapter.getItemViewType(offsetPosition);
if (RecyclerView.this.mAdapter.hasStableIds()) {
//根據ID找
holder = this.getScrapOrCachedViewForId(RecyclerView.this.mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
三級快取:mViewCacheExtension
mViewCacheExtension是Recycleview中提供的一個抽象類,供開發者自定義快取機制。
if (holder == null && this.mViewCacheExtension != null) {
//如果有定義擴充套件類,從擴充套件類中查詢
View view = this.mViewCacheExtension.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = RecyclerView.this.getChildViewHolder(view);
if (holder == null) {
throw new IllegalArgumentException("getViewForPositionAndType returned a view which does not have a ViewHolder" + RecyclerView.this.exceptionLabel());
}
if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned a view that is ignored. You must call stopIgnoring before returning this view." + RecyclerView.this.exceptionLabel());
}
}
}
四級快取:RecycledViewPool
根據type獲取holder,內部使用的儲存是使用SparseArray,儲存的是包含ViewHolder集合屬性的ScrapData物件。
//預設的快取大小為:DEFAULT_MAX_SCRAP = 5;
@Nullable
//根據type取的viewholder
public RecyclerView.ViewHolder getRecycledView(int viewType) {
RecyclerView.RecycledViewPool.ScrapData scrapData = (RecyclerView.RecycledViewPool.ScrapData)this.mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
ArrayList<RecyclerView.ViewHolder> scrapHeap = scrapData.mScrapHeap;
//通過remove方法拿到的viewholder,把與傳入的type對應的ViewHolder集合中取最後一個ViewHolder來複用
return (RecyclerView.ViewHolder)scrapHeap.remove(scrapHeap.size() - 1);
} else {
return null;
}
}
=================
//定義儲存的資料結構
SparseArray<RecyclerView.RecycledViewPool.ScrapData> mScrap = new SparseArray();
static class ScrapData {
final ArrayList<RecyclerView.ViewHolder> mScrapHeap = new ArrayList();
int mMaxScrap = 5;
long mCreateRunningAverageNs = 0L;
long mBindRunningAverageNs = 0L;
ScrapData() {
}
}
快取在ViewPool裡的ViewHolder只匹配type。是通過remove方法取的最後一個。
取完ViewHolder後將holder重置,之後ViewHolder就可以作為一個全新的ViewHolder來使用,也就是這個ViewHolder需要重新繫結呼叫:onBindViewHolder()
if (holder == null) {
holder = this.getRecycledViewPool().getRecycledView(type);
if (holder != null) {
//重置ViewHolder
holder.resetInternal();
if (RecyclerView.FORCE_INVALIDATE_DISPLAY_LIST) {
this.invalidateDisplayListInt(holder);
}
}
}
最終建立:createViewHolder
holder = RecyclerView.this.mAdapter.createViewHolder(RecyclerView.this, type);
Recycleview的回收機制
由LayoutManager遍歷移出螢幕的卡位,然後對每個卡位進行回收操作,回收時,都是把ViewHolder裝到mCachedViews裡,如果mCachedViews滿了,則將第一個移動ViewPool裡。
原始碼分析
void recycleViewHolderInternal(RecyclerView.ViewHolder holder) {
if (!holder.isScrap() && holder.itemView.getParent() == null) {
.........
if (forceRecycle || holder.isRecyclable()) {
if (this.mViewCacheMax > 0 && !holder.hasAnyOfTheFlags(526)) {
int cachedViewSize = this.mCachedViews.size();
if (cachedViewSize >= this.mViewCacheMax && cachedViewSize > 0) { //(mViewCacheMax 預設是2)如果滿了把第一個移ViewPool中去
this.recycleCachedViewAt(0);
--cachedViewSize;
}
int targetCacheIndex = cachedViewSize;
if (RecyclerView.ALLOW_THREAD_GAP_WORK && cachedViewSize > 0 && !RecyclerView.this.mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
int cacheIndex;
for(cacheIndex = cachedViewSize - 1; cacheIndex >= 0; --cacheIndex) {
int cachedPos = ((RecyclerView.ViewHolder)this.mCachedViews.get(cacheIndex)).mPosition;
if (!RecyclerView.this.mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
break;
}
}
targetCacheIndex = cacheIndex + 1;
}
//新增到mCachedViews中
this.mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
//新增到 ViewPool中
if (!cached) {
this.addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
........
}
} else {
......
}
}
**注意:**Recycleview是先複用再回收的。
綜上:listview的快取機制比Recycleview的較為簡單一些,當處理輕量級list時recycleBin的快取機制會是的顯示的效率更高,因此,listview還未過時。
後續會更新demo的快取機制分析過程,demo程式碼地址:https://github.com/MarinaTsang/TestDemo
參考部落格: