日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ListView和GridView的缓存机制及measure过程

發布時間:2024/4/15 编程问答 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ListView和GridView的缓存机制及measure过程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

前言

1、View的Transient狀態

2、RecycleBin

3、obtainView

4、getView的調用

5、GridView的onMeasure

6、ListView的onMeasure


前言

在Android開發中我們經常使用ListView和GridView,它們都有一套緩存機制,通過復用防止view的不停創建。

ListView和GridView都是AbsListView的子類,使用其內部類RecycleBin來進行view的緩存。
?


1、View的Transient狀態

要想搞懂RecycleBin的緩存機制,我們首先要了解Transient和Scrap都是什么。
Transient是View的一種狀態,可以通過View的hasTransientState函數來判斷,官方解釋如下:

A view with transient state cannot be trivially rebound from an external data source, such as an adapter binding item views in a list. This may be because the view is performing an animation, tracking user selection of content, or similar.

從解釋上看,Transient是指View的一種不穩定狀態,是瞬時狀態,比如說正在執行一個動畫,有可能下一秒就改變了。

而Scrap則是ListView和GridView的緩存狀態,當一個Item不可見被回收后存入緩存。

?

2、RecycleBin

在RecycleBin中與緩存相關的有三個List:

private ArrayList<View>[] mScrapViews; private SparseArray<View> mTransientStateViews; private LongSparseArray<View> mTransientStateViewsById;

其中mTransientStateViews和mTransientStateViewsById都是緩存Transient狀態的view的,而mScrapViews則是緩存Scrap狀態的view。
我們從添加緩存開始來看,來看RecycleBin的addScrapView函數,部分代碼如下:

void addScrapView(View scrap, int position) {...// 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.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<>();}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);}} }

在這里我們先判斷view是否處于Transient狀態,如果是Transient,則將其保存至mTransientStateViews或mTransientStateViewsById中。

至于到底保存到哪個list中,則通過mAdapterHasStableIds變量來判斷,mAdapterHasStableIds則是通過Adapter的hasStableIds函數獲得的,這個函數是需要子類去實現,它的含義是Adapter擁有穩定的ItemId,即Adapter中同一個Object的ItemId是固定不變的,這就需要我們一定要重寫Adapter的getItemId方法,否則這里就會出現問題。


關于ItemId這部分,在AbsListView的setItemViewLayoutParams可以查看到:

private void setItemViewLayoutParams(View child, int position) {final ViewGroup.LayoutParams vlp = child.getLayoutParams();...if (mAdapterHasStableIds) {lp.itemId = mAdapter.getItemId(position);}lp.viewType = mAdapter.getItemViewType(position);if (lp != vlp) {child.setLayoutParams(lp);} }


回到addScrapView函數,如果不是Transient狀態,則會將child保存到mScrapViews中。

3、obtainView

前面我們看到了添加緩存的過程,那么在哪里使用呢?

obtainView函數是AbsListView的一個函數,用于獲取每個item的view,其中就包括使用緩存機制。

這個函數的代碼如下:

View obtainView(int position, boolean[] isScrap) {...// Check whether we have a transient state view. Attempt to re-bind the// data and discard the view if we fail.final View transientView = mRecycler.getTransientStateView(position);if (transientView != null) {final LayoutParams params = (LayoutParams) transientView.getLayoutParams();// If the view type hasn't changed, attempt to re-bind the data.if (params.viewType == mAdapter.getItemViewType(position)) {final View updatedView = mAdapter.getView(position, transientView, this);// If we failed to re-bind the data, scrap the obtained view.if (updatedView != transientView) {setItemViewLayoutParams(updatedView, position);mRecycler.addScrapView(updatedView, position);}}isScrap[0] = true;// Finish the temporary detach started in addScrapView().transientView.dispatchFinishTemporaryDetach();return transientView;}final View scrapView = mRecycler.getScrapView(position);final View child = mAdapter.getView(position, scrapView, this);if (scrapView != null) {if (child != scrapView) {// Failed to re-bind the data, return scrap to the heap.mRecycler.addScrapView(scrapView, position);} else {isScrap[0] = true;// Finish the temporary detach started in addScrapView().child.dispatchFinishTemporaryDetach();}}...return child; }


這里面包含兩個部分

第一部分:
通過RecycleBin的getTransientStateView獲取transient狀態的view。
如果存在對應position的transient狀態的view,再判斷transientView的viewType與這個position的ViewType是否一致。
如果ViewType一致,則調用Adapter的getView方法獲取child,而transientView作為convertView參數。
如果得到的child的view與transientView不是同一個對象,比如getView中未使用convertView,則將child添加進ScrapView緩存中。
第一部分結束直接return了,不會繼續執行下一部分。

第二部分:
如果不存在transient狀態的view,即getTransientStateView獲取的是null,那么通過RecycleBin的getScrapView函數從緩存列表中獲取一個scrapView。
注意這里沒有判斷ViewType,是因為getScrapView函數內部進行判斷處理了。
然后調用Adapter的getView方法獲取child,而將scrapView作為convertView參數。
最后同樣判斷得到的child的view與scrapView是不是同一個對象,不是則添加進ScrapView緩存。

4、getView的調用


以上就是ListView和GridView的緩存機制。

那么我們來思考另外一個問題:
經常使用Adapter的同學可能會發現,當初始化頁面的時候,getView的調用并不是從0到count走一遍即可。那么為什么會這樣?這樣的意義在哪?

這就要從ListView和GridView的measuer說起。

5、GridView的onMeasure

先來看看GridView的onMeasure方法,關鍵代碼如下:

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);...mItemCount = mAdapter == null ? 0 : mAdapter.getCount();final int count = mItemCount;if (count > 0) {final View child = obtainView(0, mIsScrap);AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();...child.measure(childWidthSpec, childHeightSpec);childHeight = child.getMeasuredHeight();childState = combineMeasuredStates(childState, child.getMeasuredState());if (mRecycler.shouldRecycleViewType(p.viewType)) {mRecycler.addScrapView(child, -1);}}if (heightMode == MeasureSpec.UNSPECIFIED) {heightSize = mListPadding.top + mListPadding.bottom + childHeight +getVerticalFadingEdgeLength() * 2;}if (heightMode == MeasureSpec.AT_MOST) {int ourSize = mListPadding.top + mListPadding.bottom;final int numColumns = mNumColumns;for (int i = 0; i < count; i += numColumns) {ourSize += childHeight;if (i + numColumns < count) {ourSize += mVerticalSpacing;}if (ourSize >= heightSize) {ourSize = heightSize;break;}}heightSize = ourSize;}...setMeasuredDimension(widthSize, heightSize);mWidthMeasureSpec = widthMeasureSpec; }


當GridView有Adapter且其count>0時,通過obtainView這個函數獲取到了position為0的child。
在這里就解釋來getView的調用問題,因為通過前面內存我們知道obtainView函數中調用了getView,所以對于GridView來說position為0的getView會提前被調用一次。
那么這里為什么要得到這個child?
我們繼續向下看,拿到child之后收到調用了它的measure函數進行自身測量,然后拿到child的高度MeasuredHeight。
繼續向下看,當height的SpecMode為UNSPECIFIED或AT_MOST時,則需要用這個child的MeasuredHeight去計算GridView的高度。

當SpecMode為UNSPECIFIED時,GridView的高度只是一個child的高度,這就是為什么在ListView或ScrollView中嵌套GridView只顯示一行的原因。

當SpecMode為AT_MOST,需要考慮GridView的ColumnNum,GridView的高度實際上是第一個child的高度和rowNum的乘積,并且加上垂直方向的間隔mVerticalSpacing。
上面的嵌套情況,我們一般的做法是將GridView完全撐開,即自定義一個GridView并重寫onMeasuer方法,代碼如下:

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST);super.onMeasure(widthMeasureSpec, expandSpec); }


這種情況下正是SpecMode為AT_MOST的情況,注意這時的GridView的撐開的高度只與第一個child的高度有關!

6、ListView的onMeasure

上面我們研究了GridView的onMeasure,下面來看看ListView的onMeasure,代碼如下:

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// Sets up mListPaddingsuper.onMeasure(widthMeasureSpec, heightMeasureSpec);...mItemCount = mAdapter == null ? 0 : mAdapter.getCount();if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED|| heightMode == MeasureSpec.UNSPECIFIED)) {final View child = obtainView(0, mIsScrap);// Lay out child directly against the parent measure spec so that// we can obtain exected minimum width and height.measureScrapChild(child, 0, widthMeasureSpec, heightSize);childWidth = child.getMeasuredWidth();childHeight = child.getMeasuredHeight();childState = combineMeasuredStates(childState, child.getMeasuredState());if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(((LayoutParams) child.getLayoutParams()).viewType)) {mRecycler.addScrapView(child, 0);}}...if (heightMode == MeasureSpec.UNSPECIFIED) {heightSize = mListPadding.top + mListPadding.bottom + childHeight +getVerticalFadingEdgeLength() * 2;}if (heightMode == MeasureSpec.AT_MOST) {// TODO: after first layout we should maybe start at the first visible position, not 0heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);}setMeasuredDimension(widthSize, heightSize);mWidthMeasureSpec = widthMeasureSpec; }


同樣,當有Adapter且其count>0時,通過obtainView這個函數獲取到了第一個child。
然后我們沒有看到child的measure函數,但是執行了一個measureScrapChild函數,這個函數中對child進行了一次measure,這里就不貼出代碼了。
在高度計算方面,SpecMode為UNSPECIFIED時與GridView一樣,這也解釋了ScrollView或ListView嵌套ListView為啥只顯示一行。
與GridView一樣,解決嵌套問題也是自定義ListView并重寫onMeasure方法。
但是這里SpecMode為AT_MOST的情況與GridView有所不同,我們看到執行了一個measureHeightOfChildren函數,這個函數代碼如下:

final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,int maxHeight, int disallowPartialChildPosition) {final ListAdapter adapter = mAdapter;if (adapter == null) {return mListPadding.top + mListPadding.bottom;}...endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;...for (i = startPosition; i <= endPosition; ++i) {child = obtainView(i, isScrap);measureScrapChild(child, i, widthMeasureSpec, maxHeight);if (i > 0) {// Count the divider for all but one childreturnedHeight += dividerHeight;}// Recycle the view before we possibly return from the methodif (recyle && recycleBin.shouldRecycleViewType(((LayoutParams) child.getLayoutParams()).viewType)) {recycleBin.addScrapView(child, -1);}returnedHeight += child.getMeasuredHeight();...}// At this point, we went through the range of children, and they each// completely fit, so return the returnedHeightreturn returnedHeight; }


當Adapter不為空,這時startPosition是0,而endPosition是count-1。
再往下看,發現會遍歷拿到所有的child,并通過measureScrapChild函數執行它們的measure函數。
并且將這些child的高度累加起來,同時還會加上divider的高度。

這里就與GridView有所不同了。GridView只用了第一個child去做乘積,而ListView則用到了所有child。所以當SpecMode不是AT_MOST時,ListView之后提前調用一次getView,position 是0。但是如果SpecMode是AT_MOST時,ListView先調用一次position為0的getView,然后再遍歷調用一遍所有的getView,如果算上添加布局時的調用,第一個child的getView就會被調用三次!

超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生

總結

以上是生活随笔為你收集整理的ListView和GridView的缓存机制及measure过程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。