用法上的區別
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
參考博客: