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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

delphi listview 添加数据 慢_ListView 的缓存机制

發布時間:2025/3/11 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 delphi listview 添加数据 慢_ListView 的缓存机制 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一.前言

ListView 作為一個 Android 5.x 之前的一個用于顯示數據列表的控件,或許在今天都已經被 RecyclerView 完全替代,但是其中的緩存機制仍然值得我們去了解,對后面學習 RecyclerView 的緩存機制有很大的幫助。

下面將根據 ListView 的三個過程徹底理解其緩存機制 - OnLayout 過程,這個過程實踐上有兩次,而且兩次是有區別的。 - 滑動一個 Item ,即最上面的一個 item 移除屏幕,屏幕下面出現后一個 item 。 - 滑動一個以上的item .

二.RecycleBin機制

ListView 的緩存實際上都是由 RecyclerBin 類完成的,這是 ListView 的父類 AbsListView 的一個內部類,它也是 GridView 的一個父類,說明 ListView 的緩存和 GridView 的緩存實際上有很多相似的地方。

class RecycleBin {// 第一個可見的 item 的下標private int mFirstActivePosition;// 表示屏幕上可見的 itemView private View[] mActiveViews = new View[0];//表示廢棄的 itemView ,即屏幕上被移除的 itemView //就會添加到這里, 注意這里是個 數組,//數組的每個元素都是 List ,因為 ListView 可能存在多個//類型的 item ,因此用不同的 List 進行存儲。private ArrayList<View>[] mScrapViews;// 表示不同類型的 itemView 的數量private int mViewTypeCount;// 表示mScrapViews 數組中一個元素,默認是 第一個private ArrayList<View> mCurrentScrap;...//下面就是初始化的過程。ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];for (int i = 0; i < viewTypeCount; i++) {scrapViews[i] = new ArrayList<View>();}mViewTypeCount = viewTypeCount;mCurrentScrap = scrapViews[0];mScrapViews = scrapViews;

與上面對應的有四個方法,分為兩類,

對于可見的 itemView 有兩個操作:

  • fillActiveViews ,將屏幕上可見的 itemView 添加到 ActiveViews 數組。
void fillActiveViews(int childCount, int firstActivePosition) {...final View[] activeViews = mActiveViews;for (int i = 0; i < childCount; i++) {View child = getChildAt(i);AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {activeViews[i] = child;lp.scrappedFromPosition = firstActivePosition + i;}}}
  • getActiveView(), 根據位置取出 ActiveViews 數組 中的 itemView ,并將最對應的數組元素置為 null。
View getActiveView(int position) {int index = position - mFirstActivePosition;final View[] activeViews = mActiveViews;if (index >=0 && index < activeViews.length) {final View match = activeViews[index];activeViews[index] = null;return match;}return null;}

對于移除屏幕的 itemView 也有兩個操作:

  • addScrapView() ,將移除 的 itemView 添加到 mScrapViews/mCurrentScrap 中。
void addScrapView(View scrap, int position) {...if (mViewTypeCount == 1) {mCurrentScrap.add(scrap);} else {mScrapViews[viewType].add(scrap);}...}
  • getScrapView(),用于從廢棄緩存中取出一個 ItemView,如果只有一個類型就直接從 mCurrentScrap 當中獲取尾部的一個 view 進行返回,同樣取出后就直接移除元素。
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;}

三.OnLayout 過程

一個 View 的繪制的時候至少會進行 2 次 onMeasure、onLayout,原因可參考這篇文章 View為什么會至少進行2次onMeasure、onLayout,那么對于 ListView 這兩次過程由于緩存機制的存在,就顯得不一樣。

(1)第一次 OnLayout

對于 RecyclerBin 中的幾個變量,因為還未添加任何 View 所以都為 0.

變量 | 數量 ---|--- mActiveViews (表示屏幕可見的itemView )| 0 個 mCurrentScrap/mScrapViews[0] ((表示廢棄移除的itemView )) | 0個 getChildCount()/childCount | 0 個

@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);mInLayout = true;...//因為是第一次 OnLayout 所以 getChildCount //還是 0 final int childCount = getChildCount();if (changed) {for (int i = 0; i < childCount; i++) {getChildAt(i).forceLayout();}mRecycler.markChildrenDirty();}//直接進入 layoutChildrenlayoutChildren();....} @Overrideprotected void layoutChildren() {...// 因為 childcount 為 0 ,所以這里并沒有什么作用//但是 在第二次的時候 這里就需要注意//現在可以先跳過。// Pull all children into the RecycleBin.// These views will be reused if possiblefinal int firstPosition = mFirstPosition;final RecycleBin recycleBin = mRecycler;if (dataChanged) {for (int i = 0; i < childCount; i++) {recycleBin.addScrapView(getChildAt(i), firstPosition+i);}} else {recycleBin.fillActiveViews(childCount, firstPosition);}//和上面的一樣// Clear out old viewsdetachAllViewsFromParent();//} switch 里面default:if (childCount == 0) {if (!mStackFromBottom) {final int position = lookForSelectablePosition(0, true);setSelectedPositionInt(position);// 到這里方法sel = fillFromTop(childrenTop);} else {final int position = lookForSelectablePosition(mItemCount - 1, false);setSelectedPositionInt(position);sel = fillUp(mItemCount - 1, childrenBottom);}}

因為是第一次 OnLayout ,因此有效的操作實際上就到 fillFromTop 這個方法

private View fillFromTop(int nextTop) {mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);if (mFirstPosition < 0) {mFirstPosition = 0;}return fillDown(mFirstPosition, nextTop);}

fillFromTop->fillDown 這兩個方法就是進行第一次往 ListView 添加 View 。 其中的 fillDown 有個具體的循環。

private View fillDown(int pos, int nextTop) {View selectedView = null;...int end = (mBottom - mTop);...//進入一個循環while (nextTop < end && pos < mItemCount) {View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);}}

上面的循環就是 根據屏幕的大下,對 ListView 添加滿屏幕的 ItemView 。重點關注一下 makeAndView

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,boolean selected) {if (!mDataChanged) {// Try to use an existing view for this position.//嘗試從 getActiveView 獲取,但是這個時候為 0//所以 activeView 為 nullfinal View activeView = mRecycler.getActiveView(position);if (activeView != null) {// Found it. We're reusing an existing child, so it just needs// to be positioned like a scrap view.setupChild(activeView, position, y, flow, childrenLeft, selected, true);return activeView;}}//通過 obtainView 獲取// Make a new view for this position, or convert an unused view if// possible.final View child = obtainView(position, mIsScrap);// This needs to be positioned and measured.setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);return child;} View obtainView(int position, boolean[] outMetadata) {...//首先會獲取一個 ScrapView 緩存廢棄的 itemView ,因為這個時候為 0 //所以 會將 null 傳到 mAdapter.getView 這個方法中。final View scrapView = mRecycler.getScrapView(position);final View child = mAdapter.getView(position, scrapView, this);...return child;}

我們可以知道 mAdapter.getView 方法就是 BaseAdapter 中的 getView 方法。

@Overridepublic View getView(int position, View convertView, ViewGroup parent) {if (convertView==null){convertView = LayoutInflater.from(Main2Activity.this).inflate(R.layout.item,null);}TextView textView = convertView.findViewById(R.id.tv_text);textView.setText((position + ":對應為" + convertView).replace("android.widget.",""));return convertView;}});

此時 convertView 就是 scrapView ,因為這時為 null ,所以就通過 LayoutInflater 進行加載。這樣 obainView 放回一個 加載的 View , 最后回到 setupChild ,在 setupChild 就將 ItemView 添加到 ListViewGoup 并 mChildrenCount ++ .

private void addInArray(View child, int index) {View[] children = mChildren;final int count = mChildrenCount;...children[index] = child;mChildrenCount++;...}

(2)第二次 OnLayout

經過一次 OnLayout 后之前的三個變量變化如下: 變量 | 數量 ---|--- mActiveViews (表示屏幕可見的itemView )| 0 個 mCurrentScrap/mScrapViews[0] ((表示廢棄移除的itemView )) | 0個 getChildCount()/childCount | 占滿屏幕的數量 首先還是還是 從 layoutChildren 開始

@Overrideprotected void layoutChildren() {...// 因為 childcount 這個時候就有值了 n ,//假設為 n// 首先判斷有沒有數據改變 dataChanged // 沒有就進入 else // Pull all children into the RecycleBin.// These views will be reused if possiblefinal int firstPosition = mFirstPosition;final RecycleBin recycleBin = mRecycler;if (dataChanged) {for (int i = 0; i < childCount; i++) {recycleBin.addScrapView(getChildAt(i), firstPosition+i);}} else {// 這里就將 屏幕上的 itemView 添加到 //ActiveViews 中 ,ActiveViews 就是表示屏幕上的 itemView //集合recycleBin.fillActiveViews(childCount, firstPosition);}//然后就將 所有的 View 從 ListView 中先移除//這是為了后面操作導致重復添加。// Clear out old viewsdetachAllViewsFromParent();//}

上面的邏輯就是將 ListView 中的itemView 添加到 ActiveViews 數組中,然后就先移除,因為保存到了 ActiveViews 中,所以不用擔心會重新 LayoutInflate 的問題。 變量 | 數量 ---|--- mActiveViews (表示屏幕可見的itemView )| n 個 mCurrentScrap/mScrapViews[0] ((表示廢棄移除的itemView )) | 0個 getChildCount()/childCount | n 個 因為是第二次 onLayout ,所以不會進入 fillTop ->fillDown ,而是進入 fillSpecific,但是最后還是回到 makeAndaddView

private View fillSpecific(int position, int top) {boolean tempIsSelected = position == mSelectedPosition;View temp = makeAndAddView(position, top, true, mListPadding.left, ........} private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,boolean selected) {if (!mDataChanged) {// Try to use an existing view for this position.final View activeView = mRecycler.getActiveView(position);if (activeView != null) {// Found it. We're reusing an existing child, so it just needs// to be positioned like a scrap view.setupChild(activeView, position, y, flow, childrenLeft, selected, true);return activeView;}}...}

因為 ActiveView 不為 null 了,所以這里就將之前保存的 每個 itemView 重新添加到 ListView ViewGroup . 而且 ActiveView 每次get 都會進行刪除。 這樣三個變量的結果就為 變量 | 數量 ---|--- mActiveViews (表示屏幕可見的itemView )| 0 個 mCurrentScrap/mScrapViews[0] ((表示廢棄移除的itemView )) | 0個 getChildCount()/childCount | n 個

四.滑動一個 item

因為是 滑動所以肯定在 onTouchEvent 的 MOVE 里面

@Overridepublic boolean onTouchEvent(MotionEvent ev) {....switch (actionMasked) {....case MotionEvent.ACTION_MOVE: {onTouchMove(ev, vtev);break;} private void onTouchMove(MotionEvent ev, MotionEvent vtev) {// 這里又回到 layoutChildrenif (mDataChanged) {// Re-sync everything if data has been changed// since the scroll operation can query the adapter.layoutChildren();}

在 layoutChildren 最后又會回到 makeAndAddView

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,boolean selected) {if (!mDataChanged) {// Try to use an existing view for this position.final View activeView = mRecycler.getActiveView(position);if (activeView != null) {// Found it. We're reusing an existing child, so it just needs// to be positioned like a scrap view.setupChild(activeView, position, y, flow, childrenLeft, selected, true);return activeView;}}// 執行下面的// Make a new view for this position, or convert an unused view if// possible.final View child = obtainView(position, mIsScrap);// This needs to be positioned and measured.setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);return child;}

這里因為 之前的 getActiveView 已經將所有的 item取出,所以還是會通過 obtainView 去加載一個 item.而且在 onTouchMove 最后還會調用

for (int i = childCount - 1; i >= 0; i--) {final View child = getChildAt(i);if (child.getTop() <= bottom) {break;} else {....// 將移除屏幕的 itemView 添加到 ScrapView mRecycler.addScrapView(child, position);}}}}

這個時候那個幾個變量的變化為 mActiveViews (表示屏幕可見的itemView )| 0 個 mCurrentScrap/mScrapViews[0] ((表示廢棄移除的itemView )) | 1個 getChildCount()/childCount | n+1 個

五.繼續滑動

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,boolean selected) {final View child = obtainView(position, mIsScrap);// This needs to be positioned and measured.setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);return child;} final View scrapView = mRecycler.getScrapView(position);//這個時候和第一次就不同了 因為 這個時候 的 scrapView//就不為 null, 因此 scrapView 和 convertView 就不為null.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 if (child.isTemporarilyDetached()) {outMetadata[0] = true;// Finish the temporary detach started in addScrapView().child.dispatchFinishTemporaryDetach();}}

上面的過程實際上就是將之前移除屏幕的 itemView 重新獲取并設置到 mAdapter.getView 中,這也是 我們在寫 getView 方法的時候需要對 convertView 進行判斷,因為這樣就可以利用 ListView 的緩存機制,不用重新進行 LayoutInflate 。

最后:

  • 一個 ListView 共創建的 itemView 數就是屏幕顯示的 數量+1 ,這個原因在滑動一個 item 的時候就說明。

為了證明這個說法,最后做一下驗證。

總結

以上是生活随笔為你收集整理的delphi listview 添加数据 慢_ListView 的缓存机制的全部內容,希望文章能夠幫你解決所遇到的問題。

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