前言
本文從源碼角度出發(fā)學習listview,主要分析首次RecycleBin的組成拷恨,layout的過程舔亭,滑動過程些膨,item的點擊實現(xiàn)蟀俊,如何支持Header,notifyDataSetChanged原理订雾。
問題
用了好幾年的listview肢预,有幾個問題卻一直不清楚
1、如何讓一個itemview不被回收洼哎,比如我的listview里有個viewpager比較復(fù)雜烫映,不想讓他被回收又重新創(chuàng)建。
2噩峦、head和foot的原理是怎么樣的锭沟,會被回收嗎?
3识补、scrap里的view會被進一步回收掉嗎族淮?
基礎(chǔ)知識
讀了3遍郭神的http://blog.csdn.net/sinyu890807/article/details/44996879 真是受益匪淺,原來listview是這么實現(xiàn)的凭涂。
listview的實現(xiàn)方法跟scrollview完全不同瞧筛,scrollview是內(nèi)部實例化了所有的view,在滾動的時候只是改變可見的部分导盅,scrollview的高度可能是幾千幾萬较幌。如果item數(shù)很多的話,必然會oom白翻。
而listview是首先畫出listview的殼乍炉,然后去adapter里取數(shù)據(jù),取到數(shù)據(jù)inflate為view滤馍,填到listview里面去岛琼,填滿了就好了。即使adapter里有1萬個數(shù)據(jù)巢株,第一次layout的時候取的也是很少的數(shù)據(jù)(看當前屏幕需要槐瑞,假設(shè)10個)。然后在上滑的過程中阁苞,首先用offsetTopAndBottom對所有child進行移動困檩,此時頂部view就會滑出部分,那么底部會出現(xiàn)gap那槽,再去adapter里面撈數(shù)據(jù)悼沿,填到底部;然后頂部的view逐漸的被完全移出屏幕骚灸,先detach糟趾,然后把這個view丟到scrap里面去,繼續(xù)滑動底部又出現(xiàn)了gap,就去scrap里面拿現(xiàn)成的view义郑。如此往復(fù)循環(huán)蝶柿,這就是listview的原理。
和scrollview對比非驮,listview的滑動過程中伴隨著view的detach只锭,attach,但是這些都不是耗時的東西院尔,時間上沒什么損失蜻展,但是空間上減少了大量的內(nèi)存開銷。先分析下layout過程和滑動過程邀摆。
listview內(nèi)的緩存主要就是scrap纵顾,離屏就會進入scrap。scrap在layout的時候會進行裁剪栋盹,去調(diào)尾部的一些view施逾,但是實際上這種情況發(fā)生的不多,后邊會詳細說例获。
layout過程
我測試了下layout的次數(shù)汉额,郭神文章說的是2次,我這里會有3次榨汤。
第一次layout
onLayout -> layoutChildren -> fillFromTop-> fillDown-> while() makeAndAddView
makeAndAddView ->
1蠕搜、obtainView -> getView -> inflate
2、setupChild -> addViewInLayout
->child.measure
->child.layout
第二次layout
onLayout -> layoutChildren ->
1收壕、fillActiveViews
2妓灌、detachAllViewsFromParent
3、fillSpecific-> fillDown->while() makeAndAddView
makeAndAddView -> getActiveView
->setupChild -> attachViewToParent
第三次layout
onLayout -> layoutChildren ->
1蜜宪、fillActiveViews
2虫埂、detachAllViewsFromParent
3、fillSpecific-> fillDown->while() makeAndAddView
makeAndAddView -> getActiveView
->setupChild -> attachViewToParent
->child.measure
->child.layout
可以看到后2次基本差不多圃验,區(qū)別在于在setupChild內(nèi)是否要執(zhí)行child的measure和layout掉伏。為什么第3次layout會調(diào)用measure和layout,而第二次不會呢澳窑?看下邊的代碼斧散,差別就在于第三次child.isLayoutRequested()變?yōu)榱藅rue。
//setupChild
final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
我在重新捋一下流程
1照捡、ViewRootImpl#dispatchResized被調(diào)用颅湘,發(fā)出MSG_RESIZED_REPORT消息
2、第一次ListView:layoutChildren
3栗精、第二次ListView:layoutChildren
4、收到發(fā)出MSG_RESIZED_REPORT消息,ViewRootImpl#forceLayout
5悲立、第三次ListView:layoutChildren
所以第三次ListView:layoutChildren的時候會觸發(fā)child.measure和child.layout鹿寨。奇怪的是,每次都是第2次layout之后收到MSG_RESIZED_REPORT消息
滑動
對移除屏幕的view addScrapView薪夕、detachViewsFromParent
對屏幕內(nèi)的view offsetChildrenTopAndBottom
對屏幕內(nèi)空白的地方 fillGap -> fillDown->while() makeAndAddView
makeAndAddView -> obtainView脚草、setupChild
obtainView-》getScrapView-》adapter.getView(convertview....)
void fillGap(boolean down) {
final int count = getChildCount();
if (down) {
final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :
getListPaddingTop();
//手指向上滑動,所以需要填充底部
fillDown(mFirstPosition + count, startOffset);
correctTooHigh(getChildCount());
} else {
final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :
getHeight() - getListPaddingBottom();
fillUp(mFirstPosition - 1, startOffset);
correctTooLow(getChildCount());
}
}
滑動過程中不會調(diào)用onMeasure或者onLayout
RecycleBin基本成員與方法
view的回收復(fù)用主要就依靠RecycleBin原献,所以重點分析下RecycleBin
mScrapViews
RecycleBin內(nèi)有個垃圾箱馏慨,mScrapViews用來存放移除屏幕的view。
private ArrayList<View>[] mScrapViews;
private ArrayList<View> mCurrentScrap = mScrapViews[0];;
為什么是個數(shù)組呢姑隅?數(shù)組的每一項都是個ArrayList<View>写隶,代表著某個type的垃圾view集合.如果只有一種type,那么垃圾都存在mScrapViews[0]內(nèi)讲仰,mCurrentScrap = scrapViews[0];如果只有一個類型慕趴,我們直接操作mCurrentScrap即可
addScrapView
addScrapView就是把一個view加入到垃圾箱內(nèi),一般在view離開屏幕的時候調(diào)用鄙陡。如果數(shù)據(jù)未變冕房,adapter有stable IDs,有暫態(tài)趁矾,那就不會被收到垃圾箱里耙册,會存著備用。毫捣。如果是header觅玻、footer那么就放入mSkippedScrap內(nèi),不放入mScrapViews培漏。如果是暫態(tài)而且有有stable IDs溪厘,就丟到mTransientStateViewsById里面去。如果不需要stable IDs牌柄,數(shù)據(jù)未變可以丟到mTransientStateViews
/**
* Puts a view into the list of scrap views.
* <p>
* If the list data hasn't changed or the adapter has stable IDs, views
* with transient state will be preserved for later retrieval.
*
* @param scrap The view to add
* @param position The view's position within its parent
*/
void addScrapView(View scrap, int position) {
final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
if (lp == null) {
// Can't recycle, but we don't know anything about the view.
// Ignore it completely.
return;
}
lp.scrappedFromPosition = position;
// Remove but don't scrap header or footer views, or views that
// should otherwise not be recycled.
final int viewType = lp.viewType;
if (!shouldRecycleViewType(viewType)) {
// Can't recycle. If it's not a header or footer, which have
// special handling and should be ignored, then skip the scrap
// heap and we'll fully detach the view later.
if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
getSkippedScrap().add(scrap);
}
return;
}
scrap.dispatchStartTemporaryDetach();
// The the accessibility state of the view may change while temporary
// detached and we do not allow detached views to fire accessibility
// events. So we are announcing that the subtree changed giving a chance
// to clients holding on to a view in this subtree to refresh it.
notifyViewAccessibilityStateChangedIfNeeded(
AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
// Don't scrap views that have transient state.
final boolean scrapHasTransientState = scrap.hasTransientState();
if (scrapHasTransientState) {
if (mAdapter != null && mAdapterHasStableIds) {
// If the adapter has stable IDs, we can reuse the view for
// the same data.
//是暫態(tài)view畸悬,并且需要stable ID就丟到mTransientStateViewsById里面去
if (mTransientStateViewsById == null) {
mTransientStateViewsById = new LongSparseArray<>();
}
mTransientStateViewsById.put(lp.itemId, scrap);
} else if (!mDataChanged) {
// If the data hasn't changed, we can reuse the views at
// their old positions.
if (mTransientStateViews == null) {
mTransientStateViews = new SparseArray<>();
}
//數(shù)據(jù)未變可以丟到mTransientStateViews
mTransientStateViews.put(position, scrap);
} else {
// Otherwise, we'll have to remove the view and start over.
getSkippedScrap().add(scrap);
}
} else {
if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap);
} else {
mScrapViews[viewType].add(scrap);
}
if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap);
}
}
}
retrieveFromScrap
從scrap里取view,核心代碼如下,如果是固定id的珊佣,那就根據(jù)adapter的id來找蹋宦,否則就根據(jù)scrappedFromPosition 來找,比如第7個item被回收到scrap里了咒锻,記下這個view的scrappedFromPosition為7冷冗, 那下次滑回第7個item,就盡量給scrappedFromPosition為7的view給他惑艇,簡單的說就是從哪里回收來的蒿辙,還回哪里去拇泛。如果根據(jù)scrappedFromPosition找不到,那就直接取scrap的最后一個
if (mAdapterHasStableIds) {
final long id = mAdapter.getItemId(position);
if (id == params.itemId) {
return scrapViews.remove(i);
}
} else if (params.scrappedFromPosition == position) {
final View scrap = scrapViews.remove(i);
clearAccessibilityFromScrap(scrap);
return scrap;
}
final View scrap = scrapViews.remove(size - 1);
return scrap;
activeViews
這個有什么意義思灌,沒看懂俺叭。根據(jù)上面的分析,在第二次layout的過程中泰偿,首先會把當前屏幕的itemview給detach掉熄守,扔到activeViews內(nèi),然后又把他們抓出來耗跛,給attach上裕照,此時activeViews必定為空,如果不為空调塌,把殘余的view丟到mScrapViews內(nèi)(scrapActiveViews) 我實在不明白這么搞有什么意義晋南。
shouldRecycleViewType
根據(jù)type類型來確定這個view是否能回收,type類型一般可以在adapter里指定烟阐,但是系統(tǒng)默認提供了2個類型搬俊,一個是ITEM_VIEW_TYPE_IGNORE=-1,一個是ITEM_VIEW_TYPE_HEADER_OR_FOOTER=-2蜒茄。第二個很明顯就是listview的頭和尾唉擂。第一個是什么呢?如果我們希望某個view不被回收的話檀葛,可以設(shè)置ITEM_VIEW_TYPE_IGNORE玩祟,這樣就可以了。(recyclerView有類似的嗎?)
public boolean shouldRecycleViewType(int viewType) {
return viewType >= 0;
}
mRecyclerListener
當發(fā)生View回收時屿聋,mRecyclerListener若有注冊空扎,則會通知給注冊者.RecyclerListener接口只有一個函數(shù)onMovedToScrapHeap,指明某個view被回收到了scrap heap.可以在這個接口回調(diào)里進行昂貴資源的回收(比如bitmap)润讥∽猓可以直接用listview來注冊監(jiān)聽者.
listview.setRecyclerListener(new AbsListView.RecyclerListener() {
@Override
public void onMovedToScrapHeap(View view) {
}
});
點擊item
點擊一個item,是和第二次layout類似的楚殿,會調(diào)用layoutChildren撮慨,然后把界面上的view抓起來丟到activeViews內(nèi),然后又重新填充脆粥,setupChild內(nèi)不會調(diào)用measure和layout
onTouchUp -> layoutChildren ->
1砌溺、fillActiveViews
2、detachAllViewsFromParent
3变隔、fillSpecific-> fillDown->while() makeAndAddView
makeAndAddView -> getActiveView
->setupChild -> attachViewToParent
跟第二次layout的區(qū)別就是沒有調(diào)用child的onMeasure和onLayout规伐,關(guān)鍵代碼如下,這里needToMeasure為false
final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
Header
我們可以輕易的給使用addHeaderView一個listview加上header匣缘。
我知道addHeaderView必須在setAdapter之前猖闪,可以add多個head鲜棠。
那么問題來了,為什么addHeaderView必須在setAdapter之前?
看setAdapter的部分代碼可以明白萧朝,如果之前設(shè)置了header岔留,那mAdapter將會被包裝起來HeaderViewListAdapter
//setAdapter
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
}
再看看HeaderViewListAdapter,看下邊的代碼可以看到實際上HeaderViewListAdapter實現(xiàn)了Adapter的各種接口夏哭,比如getCount检柬,getItem,getItemViewType竖配,getView何址,這就是把原來的adapter進行包裝,然后實現(xiàn)對應(yīng)接口进胯,把Header作為一種特殊類型AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER用爪,在ListView看來,他就是一個普通的adapter胁镐。
//HeaderViewListAdapter
public class HeaderViewListAdapter implements WrapperListAdapter, Filterable {
public int getCount() {
if (mAdapter != null) {
return getFootersCount() + getHeadersCount() + mAdapter.getCount();
} else {
return getFootersCount() + getHeadersCount();
}
}
public Object getItem(int position) {
// Header (negative positions will throw an IndexOutOfBoundsException)
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return mHeaderViewInfos.get(position).data;
}
// Adapter
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getItem(adjPosition);
}
}
// Footer (off-limits positions will throw an IndexOutOfBoundsException)
return mFooterViewInfos.get(adjPosition - adapterCount).data;
}
public View getView(int position, View convertView, ViewGroup parent) {
// Header (negative positions will throw an IndexOutOfBoundsException)
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return mHeaderViewInfos.get(position).view;
}
// Adapter
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getView(adjPosition, convertView, parent);
}
}
// Footer (off-limits positions will throw an IndexOutOfBoundsException)
return mFooterViewInfos.get(adjPosition - adapterCount).view;
}
public int getItemViewType(int position) {
int numHeaders = getHeadersCount();
if (mAdapter != null && position >= numHeaders) {
int adjPosition = position - numHeaders;
int adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getItemViewType(adjPosition);
}
}
return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
}
}
我們在分析下header是否會被多次創(chuàng)建偎血,是否會被丟到scrap里去
首先看,滑動的時候會不會回收header,這里明顯可以看到position的限制盯漂,header和footer是不會被回收的颇玷。既然不會回收,那下次再滑到header的時候還是找adapter要就缆,看上邊的adapte的getView代碼帖渠,mHeaderViewInfos.get(position).view,只是從mHeaderViewInfos.get內(nèi)取竭宰,所以header是不會被回收的空郊,永遠存在mHeaderViewInfos里面。這里可以得到啟發(fā)切揭,Recyclerview是不支持header狞甚,footer的,那我們是不是可以針對Recyclerview來一次類似的包裝廓旬,讓他支持header哼审,footer
//trackMotionScroll
if (position >= headerViewsCount && position < footerViewsStart) {
// The view will be rebound to new data, clear any
// system-managed transient state.
child.clearAccessibilityFocus();
mRecycler.addScrapView(child, position);
}
notifyDataSetChanged
之前一直沒有說過,數(shù)據(jù)發(fā)生變化的情況會怎么樣嗤谚,我們都知道棺蛛,數(shù)據(jù)發(fā)生變化調(diào)用adpater的notifyDataSetChanged就會刷新界面。這里面的原理是什么巩步? 這里面有個觀察者模式旁赊,BaseAdapter內(nèi)有個mDataSetObservable,AbsListView在onAttachedToWindow的時候會注冊觀察者椅野,代碼如下,這樣就注冊了一個觀察者mDataSetObserver
//AbsListView#onAttachedToWindow
if (mAdapter != null && mDataSetObserver == null) {
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
// Data may have changed while we were detached. Refresh.
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
}
notifyDataSetChanged會調(diào)用mDataSetObserver.onChanged,里面更新了mItemCount,然后調(diào)用了rememberSyncState和requestLayout终畅。
@Override
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount();
// Detect the case where a cursor that was previously invalidated has
// been repopulated with new data.
if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
&& mOldItemCount == 0 && mItemCount > 0) {
AdapterView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
rememberSyncState();
}
checkFocus();
requestLayout();
}
這里rememberSyncState比較陌生籍胯,實際上他做的事情也很少,主要就2行代碼,設(shè)置mSyncMode和mSyncPosition离福。重新布局的時候默認有個原則杖狼,之前誰在第一個,那么這次誰還是在第一個.
mSyncPosition = mFirstPosition;
mSyncMode = SYNC_FIRST_POSITION;
然后我們看又一次layout的過程
首先妖爷,handleDataChanged會定下mSyncPosition蝶涩,然后把
mLayoutMode = LAYOUT_SYNC;,這個mLayoutMode后邊會用到
第二步絮识,因為dataChanged所以這里直接把所有的界面上的view丟到scrap里绿聘,不像以前放在activeViews里
第三步,detachAllViewsFromParent
第四步次舌,fillSpecific是因為mLayoutMode是LAYOUT_SYNC所以直接調(diào)用fillSpecific熄攘。
里面的getView是adapter的getView一般在這里設(shè)置實際view的內(nèi)容(比如文本圖片)。所以view一般都會設(shè)置為PFLAG_FORCE_LAYOUT彼念,所以會重新measure挪圾、layout。(這里可以再思考下逐沙,其實大部分情況下哲思,重用view,并不用重新measure酱吝,而layout的時候只要把item往listview的框里丟就可以了也殖,item內(nèi)部也不需要layout,這樣應(yīng)該能夠提供效率务热,但是看了代碼后發(fā)現(xiàn)setupChild內(nèi)是根據(jù)needToMeasure來決定是否measure忆嗜、layout的,不能分別對待崎岂,哎捆毫。)
onLayout -> layoutChildren ->
1、handleDataChanged:定個mSyncPosition冲甘、mLayoutMode = LAYOUT_SYNC;
2绩卤、for() addScrapView
3、detachAllViewsFromParent
4江醇、fillSpecific-> fillDown->while() makeAndAddView
makeAndAddView -> obtainView->getView
->setupChild -> attachViewToParent
->child.measure
->child.layout
這次layout跟之前的區(qū)別主要是第二步和第四步濒憋。
listview動畫錯亂
listview的item如果在執(zhí)行動畫的同時,listview在滑動陶夜,我們知道listview滑動過程中凛驮,是會重用view的,所以可能本來針對position 為1的動畫条辟,跑到position為11的地方去了黔夭,所以我們得禁止這個view進入scrap,如何禁止宏胯?
setHasTransientState(true),讓view進入暫態(tài)
setHasTransientState是API16引入的函數(shù),在View里本姥,下邊是對他的介紹肩袍,主要是用于動畫開始和結(jié)束,在開始的時候setHasTransientState(true)婚惫,結(jié)束的時候setHasTransientState(false)氛赐,在這之間就是暫態(tài)的。
常見用法如下辰妙,在動畫開始的時候進入暫態(tài)鹰祸,動畫結(jié)束退出暫態(tài)甫窟。我們再對比listview的代碼可以發(fā)現(xiàn)密浑,進入暫態(tài)的view不會進入scrap,而是進入mTransientStateViewsById這個LongSparseArray內(nèi)粗井,這樣就不會被重用而導致動畫錯亂了尔破。
//Listview 的 OnItemClickListener 的內(nèi)容
//本范例點了 item 后會淡出并刪除該 item
public void onItemClick(AdapterView
parent, final View view, int position, long id) {
final String item = (String) parent.getItemAtPosition(position);
ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.ALPHA, 0);
anim.setDuration(500);
view.setHasTransientState(true); //設(shè)為 true 宣告 item 要被追蹤
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
myListview.remove(item);
adapter.notifyDataSetChanged(); //重新整理 listview
view.setAlpha(1);
view.setHasTransientState(false); //完成后設(shè)定回 false
}
});
anim.start();
}
不可否認這是一種解決方法,但并不是好的解決方法浇衬,因為item都滑出去了懒构,還在搞動畫,沒啥意義耘擂,真正好的方法是什么,如下所示胆剧,在onMovedToScrapHeap里面停止動畫,這才是最合適的醉冤。
listView.setRecyclerListener(new RecyclerListener() {
@Override
public void onMovedToScrapHeap(View view) {
// Stop animation on this view
}
});
hasStableIds
adapter有個接口叫hasStableIds秩霍,這個有什么意義,我查了資料和代碼蚁阳。發(fā)現(xiàn)hasStableIds一般情況下都是false铃绒,只有2個情況是true。
什么樣的adapter是stable的螺捐,個人以為是里面的數(shù)據(jù)的id不變化颠悬,數(shù)據(jù)可以變化,但是id不能變化定血。
首先赔癌,如果要用到listview的選中功能時,只有hasStableIds返回true澜沟,才能通過getCheckedItemIds方法才能正常獲取用戶選中的選項的id(當然adapter內(nèi)必須復(fù)寫getItemId)灾票。
還有個地方就是CursorAdapter,因為cursor是sql查詢的結(jié)果倔喂,所以說是stable的無可厚非铝条。CursorAdapter里面的hasStableIds就是返回true的靖苇。
總的來說hasStableIds沒啥用,我也沒看到改為true能優(yōu)化什么班缰。
問題
addViewInLayout和attachViewToParent有什么區(qū)別呢
addViewInLayout和attachViewToParent兩者接收的參數(shù)是一樣的贤壁,主要功能也相似,也就是往ViewGroup的view數(shù)組里添加View, 但是調(diào)用addViewInLayout會使被添加的View在界面上添加時會有動畫效果呈現(xiàn)埠忘。兩者的使用場景差別也很明顯了:一般來說某一個View第一次添加進ViewGroup時比較適合調(diào)用addViewInLayout脾拆,而以后同一個View再次被添加時則適合使用attachViewToParent。因為一般情況想我們會希望進入的動畫效果執(zhí)行一次就夠了莹妒,而不需要多次執(zhí)行名船。
具體可參考http://www.itdadao.com/articles/c15a444236p0.html
scap有數(shù)量限制嗎
在滑動過程中scrap是沒限制的,但是在layout的過程中調(diào)用scrapActiveViews->pruneScrapViews,在這里會把mScrapViews內(nèi)的每組緩存旨怠,都限制在mActiveViews.length大小渠驼。
scrap里的view會被去掉嗎
為什么要考慮這個問題呢?因為有的view創(chuàng)建成本很高鉴腻,我們不希望重復(fù)創(chuàng)建迷扇,什么情況下會重復(fù)創(chuàng)建呢?那就是view離屏爽哎,進scrap蜓席,scrap裁剪,被裁剪的view就沒有了课锌,下次必須重新inflate出來厨内。
我看了下要想remove scrap里的view,只有pruneScrapViews和clear方法渺贤,clear在設(shè)置setAdapter(ListAdapter)和onDetachedFromWindow()時會被調(diào)用雏胃。而pruneScrapViews是在layout過程中被調(diào)的。所以主要看pruneScrapViews癣亚。這里可以看到其實處理scrap長度的方法是比較粗暴的丑掺,查一下mActiveViews.length,任何一個scrap堆都不準超過這個長度述雾,否則直接截尾街州。于是我又看了下mActiveViews,每次顯示在界面上的view都會丟到mActiveViews里玻孟,又發(fā)現(xiàn)mActiveViews只會變長不會變短唆缴。這就有意思了,比如當前頁面有5個item黍翎,那mActiveViews.length就是5面徽,待會當前頁面有10個item了,那mActiveViews.length就是10,再過一塊又只有3個item了趟紊,那mActiveViews.length還是10(只增不減)氮双。要注意一點只有在layout的時候才會更新mActiveViews.length,如果只是滑來滑去是不會觸發(fā)layout的霎匈。所以如果戴差,mActiveViews.length值比較小,而scrap的item又很多的話铛嘱,會進入到L10暖释,進行裁剪(一般會發(fā)生在header高度比較大的情況下),這種裁剪方式其實是比較奇怪的墨吓,憑什么根據(jù)mActiveViews.length來裁剪球匕。
所以scrap里的view是有可能被丟棄的,但是如果某個scap堆里只有一個view帖烘,那放心亮曹,他絕不會被丟棄。
另外如果我們希望緩存的view數(shù)量多一些的話蚓让,我們可以在view比較多的時候掉一遍requestLayout乾忱,這樣讓他更新mActiveViews.length
final int maxViews = mActiveViews.length;
final int viewTypeCount = mViewTypeCount;
final ArrayList<View>[] scrapViews = mScrapViews;
for (int i = 0; i < viewTypeCount; ++i) {
final ArrayList<View> scrapPile = scrapViews[i];
int size = scrapPile.size();
final int extras = size - maxViews;
size--;
for (int j = 0; j < extras; j++) {
removeDetachedView(scrapPile.remove(size--), false);
}
}
其他
1、layoutChildren必定調(diào)用invalidate
2历极、initAbsListView內(nèi)設(shè)置ListView本身可以點擊即可以消耗父View分發(fā)的事件: setClickable(true);
3、我們常常用的convertView實際上來自scrapView
ref
http://blog.csdn.net/sinyu890807/article/details/44996879
https://github.com/CharonChui/AndroidNote/blob/master/Android%E5%8A%A0%E5%BC%BA/ListView%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md
http://www.itdadao.com/articles/c15a444236p0.html
http://www.cnblogs.com/qiengo/p/3628235.html
http://www.eoeandroid.com/thread-303373-1-1.html?_dsign=6a0c274f
http://edscb.blogspot.com/2013/09/animation-listview-animations.html