listview源碼學習

前言

本文從源碼角度出發(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

ListView單選和多選模式完全解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末衷佃,一起剝皮案震驚了整個濱河市趟卸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌氏义,老刑警劉巖锄列,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異惯悠,居然都是意外死亡邻邮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門克婶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筒严,“玉大人,你說我怎么就攤上這事情萤⊙纪埽” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵筋岛,是天一觀的道長娶视。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么肪获? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任寝凌,我火速辦了婚禮,結(jié)果婚禮上孝赫,老公的妹妹穿的比我還像新娘硫兰。我一直安慰自己,他們只是感情好寒锚,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布劫映。 她就那樣靜靜地躺著,像睡著了一般刹前。 火紅的嫁衣襯著肌膚如雪泳赋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天喇喉,我揣著相機與錄音祖今,去河邊找鬼。 笑死拣技,一個胖子當著我的面吹牛千诬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播膏斤,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼徐绑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了莫辨?” 一聲冷哼從身側(cè)響起傲茄,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沮榜,沒想到半個月后盘榨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡蟆融,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年草巡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片型酥。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡山憨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出冕末,到底是詐尸還是另有隱情萍歉,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布档桃,位于F島的核電站犯助,受9級特大地震影響啥辨,放射性物質(zhì)發(fā)生泄漏其做。R本人自食惡果不足惜蓝晒,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸从撼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽低零。三九已至,卻和暖如春拯杠,著一層夾襖步出監(jiān)牢的瞬間掏婶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工潭陪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留雄妥,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓依溯,卻偏偏與公主長得像老厌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子黎炉,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,077評論 25 707
  • RecyclerView是Android 5.0系統(tǒng)官方推出的一個代替listView的組件枝秤,那么究竟好在哪里呢?...
    niknowzcd閱讀 6,901評論 5 24
  • 一拜隧、適用場景 ListViewListview是一個很重要的組件宿百,它以列表的形式根據(jù)數(shù)據(jù)的長自適應(yīng)展示具體內(nèi)容,用...
    Geeks_Liu閱讀 10,668評論 1 28
  • 最近時不時看看首頁推薦的文章,發(fā)現(xiàn)沒什么意思雀费,真材實料不多干奢,嘩眾取寵一堆,謾罵扭曲一窩盏袄,唉忿峻,一聲長嘆,不待了辕羽,走人逛尚!
    一聲長嘆唉閱讀 289評論 0 0
  • 早上鍛煉選擇了條新路線,去濱海棧道走了一圈刁愿,真是不虛此行绰寞,咔咔的不停拍照片,心情也變得更好了,這幾天什么都不用管滤钱,...
    令宜閱讀 278評論 0 0