ListView相比RecyclerView,有一些優點:
addHeaderView()
, addFooterView()
添加頭視圖和尾視圖。setOnItemClickListener()
和setOnItemLongClickListener()
設置點擊事件和長按事件。這些功能在RecyclerView中都沒有直接的接口,要自己實現(雖然實現起來很簡單),因此如果只是實現簡單的顯示功能,ListView無疑更簡單。
RecyclerView相比ListView,有一些明顯的優點:
if(convertView == null)
的實現,而且回收機制更加完善。RecyclerView是一個插件式的實現,對各個功能進行解耦,從而擴展性比較好。
我們都知道ListView通過adapter.notifyDataSetChanged()
實現ListView的更新,這種更新方法的缺點是全局更新,即對每個Item View都進行重繪。但事實上很多時候,我們只是更新了其中一個Item的數據,其他Item其實可以不需要重繪。
recyclerview使用詳解?這里給出ListView實現局部更新的方法:
public void updateItemView(ListView listview, int position, Data data){int firstPos = listview.getFirstVisiblePosition();int lastPos = listview.getLastVisiblePosition();if(position >= firstPos && position <= lastPos){ //可見才更新,不可見則在getView()時更新//listview.getChildAt(i)獲得的是當前可見的第i個item的viewView view = listview.getChildAt(position - firstPos);VH vh = (VH)view.getTag();vh.text.setText(data.text);}
}
可以看出,我們通過ListView的getChildAt()
來獲得需要更新的View,然后通過getTag()
獲得ViewHolder,從而實現更新。
RecyclerView提供了notifyItemInserted()
,notifyItemRemoved()
,notifyItemChanged()
等API更新單個或某個范圍的Item視圖。
ListView與RecyclerView緩存機制原理大致相似,如下圖所示:
image
recyclerview跳轉到指定位置,過程中,離屏的ItemView即被回收至緩存,入屏的ItemView則會優先從緩存中獲取,只是ListView與RecyclerView的實現細節有差異.(這只是緩存使用的其中一個場景,還有如刷新等)
1. 層級不同:
RecyclerView比ListView多兩級緩存,支持多個離ItemView緩存,支持開發者自定義緩存處理邏輯,支持所有RecyclerView共用同一個RecyclerViewPool(緩存池)。
具體來說:
ListView(兩級緩存):
image
listview用法?RecyclerView(四級緩存):
image
ListView和RecyclerView緩存機制基本一致:
1). mActiveViews和mAttachedScrap功能相似,意義在于快速重用屏幕上可見的列表項ItemView,而不需要重新createView和bindView;
2). mScrapView和mCachedViews + mReyclerViewPool功能相似,意義在于緩存離開屏幕的ItemView,目的是讓即將進入屏幕的ItemView重用.
listview和recyclerview的區別?3). RecyclerView的優勢在于
客觀來說,RecyclerView在特定場景下對ListView的緩存機制做了補強和完善。
2. 緩存不同:
1). RecyclerView緩存RecyclerView.ViewHolder,抽象可理解為:
View + ViewHolder(避免每次createView時調用findViewById) + flag(標識狀態);
2). ListView緩存View。
緩存不同,二者在緩存的使用上也略有差別,具體來說:
ListView獲取緩存的流程:
recyclerview是什么,image
RecyclerView獲取緩存的流程:
image
1). RecyclerView中mCacheViews(屏幕外)獲取緩存時,是通過匹配pos獲取目標位置的緩存,這樣做的好處是,當數據源數據不變的情況下,無須重新bindView:
?
recyclerview嵌套recyclerview、
?
而同樣是離屏緩存,ListView從mScrapViews根據pos獲取相應的緩存,但是并沒有直接使用,而是重新getView(即必定會重新bindView),相關代碼如下:
//AbsListView源碼:line2345
//通過匹配pos從mScrapView中獲取緩存
final View scrapView = mRecycler.getScrapView(position);
//無論是否成功都直接調用getView,導致必定會調用createView
final View child = mAdapter.getView(position, scrapView, this);
if (scrapView != null) {if (child != scrapView) {mRecycler.addScrapView(scrapView, position);} else {...}
}
2). ListView中通過pos獲取的是view,即pos–>view;
RecyclerView中通過pos獲取的是viewholder,即pos –> (view,viewHolder,flag);
從流程圖中可以看出,標志flag的作用是判斷view是否需要重新bindView,這也是RecyclerView實現局部刷新的一個核心.
由上文可知,RecyclerView的緩存機制確實更加完善,但還不算質的變化,RecyclerView更大的亮點在于提供了局部刷新的接口,通過局部刷新,就能避免調用許多無用的bindView.
?
recyclerview緩存機制。
(RecyclerView和ListView添加,移除Item效果對比)
?
結合RecyclerView的緩存機制,看看局部刷新是如何實現的:
以RecyclerView中notifyItemRemoved(1)為例,最終會調用requestLayout(),使整個RecyclerView重新繪制,過程為:
onMeasure()–>onLayout()–>onDraw()
其中,onLayout()為重點,分為三步:
其中,layoutChildren()流程圖:
recyclerview原理?
image
image
當調用notifyItemRemoved時,會對屏幕內ItemView做預處理,修改ItemView相應的pos以及flag(流程圖中紅色部分):
recyclerView,image
當調用fill()中RecyclerView.getViewForPosition(pos)時,RecyclerView通過對pos和flag的預處理,使得bindview只調用一次.
需要指出,ListView和RecyclerView最大的區別在于數據源改變時的緩存的處理邏輯,ListView是”一鍋端”,將所有的mActiveViews都移入了二級緩存mScrapViews,而RecyclerView則是更加靈活地對每個View修改標志位,區分是否重新bindView。
ListView回收機制
ListView為了保證Item View的復用,實現了一套回收機制,該回收機制的實現類是RecycleBin,他實現了兩級緩存:
View[] mActiveViews
: 緩存屏幕上的View,在該緩存里的View不需要調用getView()
。ArrayList<View>[] mScrapViews;
: 每個Item Type對應一個列表作為回收站,緩存由于滾動而消失的View,此處的View如果被復用,會以參數的形式傳給getView()
。接下來我們通過源碼分析ListView是如何與RecycleBin交互的。其實ListView和RecyclerView的layout過程大同小異,ListView的布局函數是layoutChildren()
,實現如下:
void layoutChildren(){//1. 如果數據被改變了,則將所有Item View回收至scrapView //(而RecyclerView會根據情況放入Scrap Heap或RecyclePool);否則回收至mActiveViewsif (dataChanged) {for (int i = 0; i < childCount; i++) {recycleBin.addScrapView(getChildAt(i), firstPosition+i);}} else {recycleBin.fillActiveViews(childCount, firstPosition);}//2. 填充switch(){case LAYOUT_XXX:fillXxx();break;case LAYOUT_XXX:fillXxx();break;}//3. 回收多余的activeViewmRecycler.scrapActiveViews();
}
橫向recyclerview、其中fillXxx()
實現了對Item View進行填充,該方法內部調用了makeAndAddView()
,實現如下:
View makeAndAddView(){if (!mDataChanged) {child = mRecycler.getActiveView(position);if (child != null) {return child;}}child = obtainView(position, mIsScrap);return child;
}
其中,getActiveView()
是從mActiveViews中獲取合適的View,如果獲取到了,則直接返回,而不調用obtainView()
,這也印證了如果從mActiveViews獲取到了可復用的View,則不需要調用getView()
。
obtainView()
是從mScrapViews中獲取合適的View,然后以參數形式傳給了getView()
,實現如下:
View obtainView(int position){final View scrapView = mRecycler.getScrapView(position); //從RecycleBin中獲取復用的Viewfinal View child = mAdapter.getView(position, scrapView, this);
}
接下去我們介紹getScrapView(position)
的實現,該方法通過position得到Item Type,然后根據Item Type從mScrapViews獲取可復用的View,如果獲取不到,則返回null,具體實現如下:
class RecycleBin{private View[] mActiveViews; //存儲屏幕上的Viewprivate ArrayList<View>[] mScrapViews; //每個item type對應一個ArrayListprivate int mViewTypeCount; //item type的個數private ArrayList<View> mCurrentScrap; //mScrapViews[0]View getScrapView(int position) {final int whichScrap = mAdapter.getItemViewType(position);if (whichScrap < 0) {return null;}if (mViewTypeCount == 1) {return retrieveFromScrap(mCurrentScrap, position);} else if (whichScrap < mScrapViews.length) {return retrieveFromScrap(mScrapViews[whichScrap], position);}return null;}private View retrieveFromScrap(ArrayList<View> scrapViews, int position){int size = scrapViews.size();if(size > 0){return scrapView.remove(scrapViews.size() - 1); //從回收列表中取出最后一個元素復用} else{return null;}}
}
RecyclerView回收機制
RecyclerView和ListView的回收機制非常相似,但是ListView是以View作為單位進行回收,RecyclerView是以ViewHolder作為單位進行回收。
Recycler是RecyclerView回收機制的實現類,他實現了四級緩存:
getView()
。recyclerview多布局、在上文Layout Manager中已經介紹了RecyclerView的layout過程,但是一筆帶過了getViewForPosition()
,因此此處介紹該方法的實現。
View getViewForPosition(int position, boolean dryRun){if(holder == null){//從mAttachedScrap,mCachedViews獲取ViewHolderholder = getScrapViewForPosition(position,INVALID,dryRun); //此處獲得的View不需要bind}final int type = mAdapter.getItemViewType(offsetPosition);if (mAdapter.hasStableIds()) { //默認為falseholder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);}if(holder == null && mViewCacheExtension != null){final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type); //從if(view != null){holder = getChildViewHolder(view);}}if(holder == null){holder = getRecycledViewPool().getRecycledView(type);}if(holder == null){ //沒有緩存,則創建holder = mAdapter.createViewHolder(RecyclerView.this, type); //調用onCreateViewHolder()}if(!holder.isBound() || holder.needsUpdate() || holder.isInvalid()){mAdapter.bindViewHolder(holder, offsetPosition);}return holder.itemView;
}
從上述實現可以看出,依次從mAttachedScrap, mCachedViews, mViewCacheExtension, mRecyclerPool尋找可復用的ViewHolder,如果是從mAttachedScrap或mCachedViews中獲取的ViewHolder,則不會調用onBindViewHolder()
,mAttachedScrap和mCachedViews也就是我們所說的Scrap Heap;而如果從mViewCacheExtension或mRecyclerPool中獲取的ViewHolder,則會調用onBindViewHolder()
。
RecyclerView局部刷新的實現原理也是基于RecyclerView的回收機制,即能直接復用的ViewHolder就不調用onBindViewHolder()
。
轉載:https://www.jianshu.com/p/4f9591291365
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态