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 數組。
- getActiveView(), 根據位置取出 ActiveViews 數組 中的 itemView ,并將最對應的數組元素置為 null。
對于移除屏幕的 itemView 也有兩個操作:
- addScrapView() ,將移除 的 itemView 添加到 mScrapViews/mCurrentScrap 中。
- getScrapView(),用于從廢棄緩存中取出一個 ItemView,如果只有一個類型就直接從 mCurrentScrap 當中獲取尾部的一個 view 進行返回,同樣取出后就直接移除元素。
三.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 的缓存机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ubuntu20.04自带python版
- 下一篇: 管理动物园动物c++_《过山车大亨》开发