經過源碼學習ListView(大章)

從源碼開始學習ListView

前言

這個是我人生中寫的地一篇博客,若是有寫的很差,思惟混亂的地方請請請請輕噴。並謝謝指出不對的地方,我好修改。畢竟博客寫出來更多的是爲了記錄學習。
依舊現行感謝一下幾個博客對個人指導以及借鑑。若是有看的不明白的能夠移步去了解,排名不分先後。
Yogurt_QaQListView 源碼研究 帶你從源碼中讚歎谷歌大吊們的代碼藝術
康志Android ListView工做原理徹底解析,帶你從源碼的角度完全理解
孫羣源碼解析ListView中的RecycleBin機制
foxleezhAndroid系統源碼分析--如何下載和閱讀Android源碼android

1、ListView的組成

一、ListView的繼承關係
ListView-->AbsListView-->AdapterView-->ViewGroup-->View
由上可知,ListView繼承於AbsListView繼承AdapterView,再日後就是View的繼承,後面暫時不說。等我慢慢看完,學習完在作總結。AdapterView能夠知道,google的工程師把ListView分紅了兩部分,ListView和Adapter。
二、淺談Adapter
google爲了讓listview適應更多的需求,承載更多的數據類型。因此使用了Adpater去承載數據,起到一個橋樑的做用,固然adapter還有一個重要的方法getview();這個後面會詳細提到spring

2、ListView的源碼研究

一、RcycleBin機制
這個機制是Listview處理成大數據量時不會OOM的重要部分,先看源碼,以下。數組

/**
     * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
     * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
     * start of a layout. By construction, they are displaying current information. At the end of
     * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
     * could potentially be used by the adapter to avoid allocating views unnecessarily.
     *
     * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
     * @see android.widget.AbsListView.RecyclerListener
     */
    class RecycleBin {
        private RecyclerListener mRecyclerListener;

        /**
         * The position of the first view stored in mActiveViews.
         */
        private int mFirstActivePosition;

        /**
         * Views that were on screen at the start of layout. This array is populated at the start of
         * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
         * Views in mActiveViews represent a contiguous range of Views, with position of the first
         * view store in mFirstActivePosition.
         */
        private View[] mActiveViews = new View[0];

        /**
         * Unsorted views that can be used by the adapter as a convert view.
         */
        private ArrayList<View>[] mScrapViews;

        private int mViewTypeCount;

        private ArrayList<View> mCurrentScrap;

        private ArrayList<View> mSkippedScrap;

        private SparseArray<View> mTransientStateViews;
        private LongSparseArray<View> mTransientStateViewsById;

        public void setViewTypeCount(int viewTypeCount) {
            if (viewTypeCount < 1) {
                throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
            }
            //noinspection unchecked
            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;
        }

        public void markChildrenDirty() {
            if (mViewTypeCount == 1) {
                final ArrayList<View> scrap = mCurrentScrap;
                final int scrapCount = scrap.size();
                for (int i = 0; i < scrapCount; i++) {
                    scrap.get(i).forceLayout();
                }
            } else {
                final int typeCount = mViewTypeCount;
                for (int i = 0; i < typeCount; i++) {
                    final ArrayList<View> scrap = mScrapViews[i];
                    final int scrapCount = scrap.size();
                    for (int j = 0; j < scrapCount; j++) {
                        scrap.get(j).forceLayout();
                    }
                }
            }
            if (mTransientStateViews != null) {
                final int count = mTransientStateViews.size();
                for (int i = 0; i < count; i++) {
                    mTransientStateViews.valueAt(i).forceLayout();
                }
            }
            if (mTransientStateViewsById != null) {
                final int count = mTransientStateViewsById.size();
                for (int i = 0; i < count; i++) {
                    mTransientStateViewsById.valueAt(i).forceLayout();
                }
            }
        }

        public boolean shouldRecycleViewType(int viewType) {
            return viewType >= 0;
        }

        /**
         * Clears the scrap heap.
         */
        void clear() {
            if (mViewTypeCount == 1) {
                final ArrayList<View> scrap = mCurrentScrap;
                clearScrap(scrap);
            } else {
                final int typeCount = mViewTypeCount;
                for (int i = 0; i < typeCount; i++) {
                    final ArrayList<View> scrap = mScrapViews[i];
                    clearScrap(scrap);
                }
            }

            clearTransientStateViews();
        }

        /**
         * Fill ActiveViews with all of the children of the AbsListView.
         *
         * @param childCount The minimum number of views mActiveViews should hold
         * @param firstActivePosition The position of the first view that will be stored in
         *        mActiveViews
         */
        void fillActiveViews(int childCount, int firstActivePosition) {
            if (mActiveViews.length < childCount) {
                mActiveViews = new View[childCount];
            }
            mFirstActivePosition = firstActivePosition;

            //noinspection MismatchedReadAndWriteOfArray
            final View[] activeViews = mActiveViews;
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
                // Don't put header or footer views into the scrap heap if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views. // However, we will NOT place them into scrap views. activeViews[i] = child; // Remember the position so that setupChild() doesn't reset state.
                    lp.scrappedFromPosition = firstActivePosition + i;
                }
            }
        }

        /**
         * Get the view corresponding to the specified position. The view will be removed from
         * mActiveViews if it is found.
         *
         * @param position The position to look up in mActiveViews
         * @return The view if it is found, null otherwise
         */
        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;
        }

        View getTransientStateView(int position) {
            if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
                long id = mAdapter.getItemId(position);
                View result = mTransientStateViewsById.get(id);
                mTransientStateViewsById.remove(id);
                return result;
            }
            if (mTransientStateViews != null) {
                final int index = mTransientStateViews.indexOfKey(position);
                if (index >= 0) {
                    View result = mTransientStateViews.valueAt(index);
                    mTransientStateViews.removeAt(index);
                    return result;
                }
            }
            return null;
        }

        /**
         * Dumps and fully detaches any currently saved views with transient
         * state.
         */
        void clearTransientStateViews() {
            final SparseArray<View> viewsByPos = mTransientStateViews;
            if (viewsByPos != null) {
                final int N = viewsByPos.size();
                for (int i = 0; i < N; i++) {
                    removeDetachedView(viewsByPos.valueAt(i), false);
                }
                viewsByPos.clear();
            }

            final LongSparseArray<View> viewsById = mTransientStateViewsById;
            if (viewsById != null) {
                final int N = viewsById.size();
                for (int i = 0; i < N; i++) {
                    removeDetachedView(viewsById.valueAt(i), false);
                }
                viewsById.clear();
            }
        }

        /**
         * @return A view from the ScrapViews collection. These are unordered.
         */
        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;
        }

        /**
         * 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. 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. clearScrapForRebind(scrap); getSkippedScrap().add(scrap); } } else { clearScrapForRebind(scrap); if (mViewTypeCount == 1) { mCurrentScrap.add(scrap); } else { mScrapViews[viewType].add(scrap); } if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } } } private ArrayList<View> getSkippedScrap() { if (mSkippedScrap == null) { mSkippedScrap = new ArrayList<>(); } return mSkippedScrap; } /** * Finish the removal of any views that skipped the scrap heap. */ void removeSkippedScrap() { if (mSkippedScrap == null) { return; } final int count = mSkippedScrap.size(); for (int i = 0; i < count; i++) { removeDetachedView(mSkippedScrap.get(i), false); } mSkippedScrap.clear(); } /** * Move all views remaining in mActiveViews to mScrapViews. */ void scrapActiveViews() { final View[] activeViews = mActiveViews; final boolean hasListener = mRecyclerListener != null; final boolean multipleScraps = mViewTypeCount > 1; ArrayList<View> scrapViews = mCurrentScrap; final int count = activeViews.length; for (int i = count - 1; i >= 0; i--) { final View victim = activeViews[i]; if (victim != null) { final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) victim.getLayoutParams(); final int whichScrap = lp.viewType; activeViews[i] = null; if (victim.hasTransientState()) { // Store views with transient state for later use. victim.dispatchStartTemporaryDetach(); if (mAdapter != null && mAdapterHasStableIds) { if (mTransientStateViewsById == null) { mTransientStateViewsById = new LongSparseArray<View>(); } long id = mAdapter.getItemId(mFirstActivePosition + i); mTransientStateViewsById.put(id, victim); } else if (!mDataChanged) { if (mTransientStateViews == null) { mTransientStateViews = new SparseArray<View>(); } mTransientStateViews.put(mFirstActivePosition + i, victim); } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { // The data has changed, we can't keep this view.
                            removeDetachedView(victim, false);
                        }
                    } else if (!shouldRecycleViewType(whichScrap)) {
                        // Discard non-recyclable views except headers/footers.
                        if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                            removeDetachedView(victim, false);
                        }
                    } else {
                        // Store everything else on the appropriate scrap heap.
                        if (multipleScraps) {
                            scrapViews = mScrapViews[whichScrap];
                        }

                        lp.scrappedFromPosition = mFirstActivePosition + i;
                        removeDetachedView(victim, false);
                        scrapViews.add(victim);

                        if (hasListener) {
                            mRecyclerListener.onMovedToScrapHeap(victim);
                        }
                    }
                }
            }
            pruneScrapViews();
        }

        /**
         * At the end of a layout pass, all temp detached views should either be re-attached or
         * completely detached. This method ensures that any remaining view in the scrap list is
         * fully detached.
         */
        void fullyDetachScrapViews() {
            final int viewTypeCount = mViewTypeCount;
            final ArrayList<View>[] scrapViews = mScrapViews;
            for (int i = 0; i < viewTypeCount; ++i) {
                final ArrayList<View> scrapPile = scrapViews[i];
                for (int j = scrapPile.size() - 1; j >= 0; j--) {
                    final View view = scrapPile.get(j);
                    if (view.isTemporarilyDetached()) {
                        removeDetachedView(view, false);
                    }
                }
            }
        }

        /**
         * Makes sure that the size of mScrapViews does not exceed the size of
         * mActiveViews, which can happen if an adapter does not recycle its
         * views. Removes cached transient state views that no longer have
         * transient state.
         */
        private void pruneScrapViews() {
            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();
                while (size > maxViews) {
                    scrapPile.remove(--size);
                }
            }

            final SparseArray<View> transViewsByPos = mTransientStateViews;
            if (transViewsByPos != null) {
                for (int i = 0; i < transViewsByPos.size(); i++) {
                    final View v = transViewsByPos.valueAt(i);
                    if (!v.hasTransientState()) {
                        removeDetachedView(v, false);
                        transViewsByPos.removeAt(i);
                        i--;
                    }
                }
            }

            final LongSparseArray<View> transViewsById = mTransientStateViewsById;
            if (transViewsById != null) {
                for (int i = 0; i < transViewsById.size(); i++) {
                    final View v = transViewsById.valueAt(i);
                    if (!v.hasTransientState()) {
                        removeDetachedView(v, false);
                        transViewsById.removeAt(i);
                        i--;
                    }
                }
            }
        }

        /**
         * Puts all views in the scrap heap into the supplied list.
         */
        void reclaimScrapViews(List<View> views) {
            if (mViewTypeCount == 1) {
                views.addAll(mCurrentScrap);
            } else {
                final int viewTypeCount = mViewTypeCount;
                final ArrayList<View>[] scrapViews = mScrapViews;
                for (int i = 0; i < viewTypeCount; ++i) {
                    final ArrayList<View> scrapPile = scrapViews[i];
                    views.addAll(scrapPile);
                }
            }
        }

        /**
         * Updates the cache color hint of all known views.
         *
         * @param color The new cache color hint.
         */
        void setCacheColorHint(int color) {
            if (mViewTypeCount == 1) {
                final ArrayList<View> scrap = mCurrentScrap;
                final int scrapCount = scrap.size();
                for (int i = 0; i < scrapCount; i++) {
                    scrap.get(i).setDrawingCacheBackgroundColor(color);
                }
            } else {
                final int typeCount = mViewTypeCount;
                for (int i = 0; i < typeCount; i++) {
                    final ArrayList<View> scrap = mScrapViews[i];
                    final int scrapCount = scrap.size();
                    for (int j = 0; j < scrapCount; j++) {
                        scrap.get(j).setDrawingCacheBackgroundColor(color);
                    }
                }
            }
            // Just in case this is called during a layout pass
            final View[] activeViews = mActiveViews;
            final int count = activeViews.length;
            for (int i = 0; i < count; ++i) {
                final View victim = activeViews[i];
                if (victim != null) {
                    victim.setDrawingCacheBackgroundColor(color);
                }
            }
        }

        private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
            final int size = scrapViews.size();
            if (size > 0) {
                // See if we still have a view for this position or ID.
                // Traverse backwards to find the most recently used scrap view
                for (int i = size - 1; i >= 0; i--) {
                    final View view = scrapViews.get(i);
                    final AbsListView.LayoutParams params =
                            (AbsListView.LayoutParams) view.getLayoutParams();

                    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);
                        clearScrapForRebind(scrap);
                        return scrap;
                    }
                }
                final View scrap = scrapViews.remove(size - 1);
                clearScrapForRebind(scrap);
                return scrap;
            } else {
                return null;
            }
        }

        private void clearScrap(final ArrayList<View> scrap) {
            final int scrapCount = scrap.size();
            for (int j = 0; j < scrapCount; j++) {
                removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
            }
        }

        private void clearScrapForRebind(View view) {
            view.clearAccessibilityFocus();
            view.setAccessibilityDelegate(null);
        }

        private void removeDetachedView(View child, boolean animate) {
            child.setAccessibilityDelegate(null);
            AbsListView.this.removeDetachedView(child, animate);
        }
    }複製代碼

源碼很少只有300多行,我們先看開頭,google對這個的註釋。緩存

The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
     * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
     * start of a layout. By construction, they are displaying current information. At the end of
     * layout, all views in ActiveViews are demoted to The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
     * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
     * start of a layout. By construction, they are displaying current information. At the end of
     * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
     * could potentially be used by the adapter to avoid allocating views unnecessarily.. ScrapViews are old views that
     * could potentially be used by the adapter to avoid allocating views unnecessarily.複製代碼

可知,這個內部類中最關鍵的是ActiveViews和ScrapViews。更據這兩個關鍵詞咱們去找對應類,以下:bash

  • fillActiveViews()app

    * Fill ActiveViews with all of the children of the AbsListView.
       *
       * @param childCount The minimum number of views mActiveViews should hold
       * @param firstActivePosition The position of the first view that will be stored in
       *        mActiveViews
       */
      void fillActiveViews(int childCount, int firstActivePosition) {
          if (mActiveViews.length < childCount) {
              mActiveViews = new View[childCount];
          }
          mFirstActivePosition = firstActivePosition;
    
          //noinspection MismatchedReadAndWriteOfArray
          final View[] activeViews = mActiveViews;
          for (int i = 0; i < childCount; i++) {
              View child = getChildAt(i);
              AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
              // Don't put header or footer views into the scrap heap if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views. // However, we will NOT place them into scrap views. activeViews[i] = child; // Remember the position so that setupChild() doesn't reset state.
                  lp.scrappedFromPosition = firstActivePosition + i;
              }
          }
      }複製代碼

    老樣子,先從註釋看起,這裏仍是要感嘆一下本身平時,寫代碼要多加註釋!!言歸正傳,註釋上說,把AbsListView中的指定元素放到ActiveViews數組中。而後看傳入的兩個參數,childCount,firstActivePosition。@param中都有寫,一個是傳入須要儲存view的數量,另外一個是ListView中第一個可見view的position。調用這個函數主要是將view添加進ActiveViews數組。less

  • getActiveView()async

    * Get the view corresponding to the specified position. The view will be removed from
       * mActiveViews if it is found.
       *
       * @param position The position to look up in mActiveViews
       * @return The view if it is found, null otherwise
       */
      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;
      }複製代碼

    調用這個函數,其實註釋裏面說的很是清楚了,獲取一個position下的View而後它就從mActiveViews數組中移除了。意思是什麼呢,一個view只能用一個,用完之後就被廢棄了。那麼新的view怎麼加入,廢棄的view怎麼再利用呢?接着往下看。ide

  • addScrapView()
  • 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. 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. clearScrapForRebind(scrap); getSkippedScrap().add(scrap); } } else { clearScrapForRebind(scrap); if (mViewTypeCount == 1) { mCurrentScrap.add(scrap); } else { mScrapViews[viewType].add(scrap); } if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } } }複製代碼

    這個函數,更據他的代碼量就知道他是recyclebin中很關鍵的函數,意思很簡單,調用這個函數之後將view放進scrapviews數組。兩個傳入的變量,一個是廢棄的view,一個是view的位置。

  • getScrapView()

    * @return A view from the ScrapViews collection. These are unordered.
       */
      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;
      }複製代碼

    這個函數就很好理解了,就是將mScrapViews中最後一個取出來。
    上面這四個函數超級重要,必定要牢記

    3、ListView源碼流程

  • 第一次layout
    首先,ListView也是ViewGoup也是View,因此說ListView也是基本的三步繪製流程。onmeasure()測量View的大小,onlayout()確認View的佈局,ondraw()繪製View到界面上。onmeasure()在ListView中沒有什麼特殊的。可是在onlayout中體現出不同了,咱們從onlayout的源碼一層層看。
    看源碼能夠發現ListView中並無onlayout方法,那麼咱們去父類AbsListView中去看,果真咱們發現了onlayout。
/**
     * Subclasses should NOT override this method but
     *  {@link #layoutChildren()} instead.
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        mInLayout = true;

        final int childCount = getChildCount();
        if (changed) {
            for (int i = 0; i < childCount; i++) {
                getChildAt(i).forceLayout();
            }
            mRecycler.markChildrenDirty();
        }

        layoutChildren();

        mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;

        // TODO: Move somewhere sane. This doesn't belong in onLayout(). if (mFastScroll != null) { mFastScroll.onItemCountChanged(getChildCount(), mItemCount); } mInLayout = false; }複製代碼

方法代碼量不多,主要是一個判斷,若是View發生改變,那麼就從新繪製全部的view。而後就是layoutChildren()方法,這個從名字上能夠看出,是繪製子view的方法,咱們跟進去看。

* Subclasses must override this method to layout their children.
 */
protected void layoutChildren() {
}複製代碼

發現並無寫什麼,固然這句話是廢話,由於這是父類,不會涉及到具體的view繪製。因此咱們回到ListView中去找這個方法。

@Override
protected void layoutChildren() {
    final boolean blockLayoutRequests = mBlockLayoutRequests;
    if (blockLayoutRequests) {
        return;
    }

    mBlockLayoutRequests = true;

    try {
        super.layoutChildren();

        invalidate();

        if (mAdapter == null) {
            resetList();
            invokeOnItemScrollListener();
            return;
        }

        final int childrenTop = mListPadding.top;
        final int childrenBottom = mBottom - mTop - mListPadding.bottom;
        final int childCount = getChildCount();

        int index = 0;
        int delta = 0;

        View sel;
        View oldSel = null;
        View oldFirst = null;
        View newSel = null;

        // Remember stuff we will need down below
        switch (mLayoutMode) {
        case LAYOUT_SET_SELECTION:
            index = mNextSelectedPosition - mFirstPosition;
            if (index >= 0 && index < childCount) {
                newSel = getChildAt(index);
            }
            break;
        case LAYOUT_FORCE_TOP:
        case LAYOUT_FORCE_BOTTOM:
        case LAYOUT_SPECIFIC:
        case LAYOUT_SYNC:
            break;
        case LAYOUT_MOVE_SELECTION:
        default:
            // Remember the previously selected view
            index = mSelectedPosition - mFirstPosition;
            if (index >= 0 && index < childCount) {
                oldSel = getChildAt(index);
            }

            // Remember the previous first child
            oldFirst = getChildAt(0);

            if (mNextSelectedPosition >= 0) {
                delta = mNextSelectedPosition - mSelectedPosition;
            }

            // Caution: newSel might be null
            newSel = getChildAt(index + delta);
        }


        boolean dataChanged = mDataChanged;
        if (dataChanged) {
            handleDataChanged();
        }

        // Handle the empty set by removing all views that are visible
        // and calling it a day
        if (mItemCount == 0) {
            resetList();
            invokeOnItemScrollListener();
            return;
        } else if (mItemCount != mAdapter.getCount()) {
            throw new IllegalStateException("The content of the adapter has changed but "
                    + "ListView did not receive a notification. Make sure the content of "
                    + "your adapter is not modified from a background thread, but only from "
                    + "the UI thread. Make sure your adapter calls notifyDataSetChanged() "
                    + "when its content changes. [in ListView(" + getId() + ", " + getClass()
                    + ") with Adapter(" + mAdapter.getClass() + ")]");
        }

        setSelectedPositionInt(mNextSelectedPosition);

        AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
        View accessibilityFocusLayoutRestoreView = null;
        int accessibilityFocusPosition = INVALID_POSITION;

        // Remember which child, if any, had accessibility focus. This must
        // occur before recycling any views, since that will clear
        // accessibility focus.
        final ViewRootImpl viewRootImpl = getViewRootImpl();
        if (viewRootImpl != null) {
            final View focusHost = viewRootImpl.getAccessibilityFocusedHost();
            if (focusHost != null) {
                final View focusChild = getAccessibilityFocusedChild(focusHost);
                if (focusChild != null) {
                    if (!dataChanged || isDirectChildHeaderOrFooter(focusChild)
                            || (focusChild.hasTransientState() && mAdapterHasStableIds)) {
                        // The views won't be changing, so try to maintain // focus on the current host and virtual view. accessibilityFocusLayoutRestoreView = focusHost; accessibilityFocusLayoutRestoreNode = viewRootImpl .getAccessibilityFocusedVirtualView(); } // If all else fails, maintain focus at the same // position. accessibilityFocusPosition = getPositionForView(focusChild); } } } View focusLayoutRestoreDirectChild = null; View focusLayoutRestoreView = null; // Take focus back to us temporarily to avoid the eventual call to // clear focus when removing the focused child below from messing // things up when ViewAncestor assigns focus back to someone else. final View focusedChild = getFocusedChild(); if (focusedChild != null) { // TODO: in some cases focusedChild.getParent() == null // We can remember the focused view to restore after re-layout // if the data hasn't changed, or if the focused position is a
            // header or footer.
            if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)
                    || focusedChild.hasTransientState() || mAdapterHasStableIds) {
                focusLayoutRestoreDirectChild = focusedChild;
                // Remember the specific view that had focus.
                focusLayoutRestoreView = findFocus();
                if (focusLayoutRestoreView != null) {
                    // Tell it we are going to mess with it.
                    focusLayoutRestoreView.dispatchStartTemporaryDetach();
                }
            }
            requestFocus();
        }

        // Pull all children into the RecycleBin.
        // These views will be reused if possible
        final 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 views
        detachAllViewsFromParent();
        recycleBin.removeSkippedScrap();

        switch (mLayoutMode) {
        case LAYOUT_SET_SELECTION:
            if (newSel != null) {
                sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
            } else {
                sel = fillFromMiddle(childrenTop, childrenBottom);
            }
            break;
        case LAYOUT_SYNC:
            sel = fillSpecific(mSyncPosition, mSpecificTop);
            break;
        case LAYOUT_FORCE_BOTTOM:
            sel = fillUp(mItemCount - 1, childrenBottom);
            adjustViewsUpOrDown();
            break;
        case LAYOUT_FORCE_TOP:
            mFirstPosition = 0;
            sel = fillFromTop(childrenTop);
            adjustViewsUpOrDown();
            break;
        case LAYOUT_SPECIFIC:
            final int selectedPosition = reconcileSelectedPosition();
            sel = fillSpecific(selectedPosition, mSpecificTop);
            /**
             * When ListView is resized, FocusSelector requests an async selection for the
             * previously focused item to make sure it is still visible. If the item is not
             * selectable, it won't regain focus so instead we call FocusSelector * to directly request focus on the view after it is visible. */ if (sel == null && mFocusSelector != null) { final Runnable focusRunnable = mFocusSelector .setupFocusIfValid(selectedPosition); if (focusRunnable != null) { post(focusRunnable); } } break; case LAYOUT_MOVE_SELECTION: sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom); break; 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); } } else { if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) { sel = fillSpecific(mSelectedPosition, oldSel == null ? childrenTop : oldSel.getTop()); } else if (mFirstPosition < mItemCount) { sel = fillSpecific(mFirstPosition, oldFirst == null ? childrenTop : oldFirst.getTop()); } else { sel = fillSpecific(0, childrenTop); } } break; } // Flush any cached views that did not get reused above recycleBin.scrapActiveViews(); // remove any header/footer that has been temp detached and not re-attached removeUnusedFixedViews(mHeaderViewInfos); removeUnusedFixedViews(mFooterViewInfos); if (sel != null) { // The current selected item should get focus if items are // focusable. if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) { final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild && focusLayoutRestoreView != null && focusLayoutRestoreView.requestFocus()) || sel.requestFocus(); if (!focusWasTaken) { // Selected item didn't take focus, but we still want to
                    // make sure something else outside of the selected view
                    // has focus.
                    final View focused = getFocusedChild();
                    if (focused != null) {
                        focused.clearFocus();
                    }
                    positionSelector(INVALID_POSITION, sel);
                } else {
                    sel.setSelected(false);
                    mSelectorRect.setEmpty();
                }
            } else {
                positionSelector(INVALID_POSITION, sel);
            }
            mSelectedTop = sel.getTop();
        } else {
            final boolean inTouchMode = mTouchMode == TOUCH_MODE_TAP
                    || mTouchMode == TOUCH_MODE_DONE_WAITING;
            if (inTouchMode) {
                // If the user's finger is down, select the motion position. final View child = getChildAt(mMotionPosition - mFirstPosition); if (child != null) { positionSelector(mMotionPosition, child); } } else if (mSelectorPosition != INVALID_POSITION) { // If we had previously positioned the selector somewhere, // put it back there. It might not match up with the data, // but it's transitioning out so it's not a big deal. final View child = getChildAt(mSelectorPosition - mFirstPosition); if (child != null) { positionSelector(mSelectorPosition, child); } } else { // Otherwise, clear selection. mSelectedTop = 0; mSelectorRect.setEmpty(); } // Even if there is not selected position, we may need to // restore focus (i.e. something focusable in touch mode). if (hasFocus() && focusLayoutRestoreView != null) { focusLayoutRestoreView.requestFocus(); } } // Attempt to restore accessibility focus, if necessary. if (viewRootImpl != null) { final View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost(); if (newAccessibilityFocusedView == null) { if (accessibilityFocusLayoutRestoreView != null && accessibilityFocusLayoutRestoreView.isAttachedToWindow()) { final AccessibilityNodeProvider provider = accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider(); if (accessibilityFocusLayoutRestoreNode != null && provider != null) { final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId( accessibilityFocusLayoutRestoreNode.getSourceNodeId()); provider.performAction(virtualViewId, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); } else { accessibilityFocusLayoutRestoreView.requestAccessibilityFocus(); } } else if (accessibilityFocusPosition != INVALID_POSITION) { // Bound the position within the visible children. final int position = MathUtils.constrain( accessibilityFocusPosition - mFirstPosition, 0, getChildCount() - 1); final View restoreView = getChildAt(position); if (restoreView != null) { restoreView.requestAccessibilityFocus(); } } } } // Tell focus view we are done mucking with it, if it is still in // our view hierarchy. if (focusLayoutRestoreView != null && focusLayoutRestoreView.getWindowToken() != null) { focusLayoutRestoreView.dispatchFinishTemporaryDetach(); } mLayoutMode = LAYOUT_NORMAL; mDataChanged = false; if (mPositionScrollAfterLayout != null) { post(mPositionScrollAfterLayout); mPositionScrollAfterLayout = null; } mNeedSync = false; setNextSelectedPositionInt(mSelectedPosition); updateScrollIndicators(); if (mItemCount > 0) { checkSelectionChanged(); } invokeOnItemScrollListener(); } finally { if (mFocusSelector != null) { mFocusSelector.onLayoutComplete(); } if (!blockLayoutRequests) { mBlockLayoutRequests = false; } } }複製代碼

源碼超級多,咱們走大流程分析吧。首先先看invalidate();這個是在合適的時候出發ondarw()方法,就是在Adapter中數據有變化的時候調用,final int childCount = getChildCount();第一次ListView繪製的時候,是沒有子view的,因此確定是0。
再日後就是 final RecycleBin recycleBin = mRecycler;

if (dataChanged) {
                for (int i = 0; i < childCount; i++) {
                    recycleBin.addScrapView(getChildAt(i), firstPosition+i);
                }
            } else {
                recycleBin.fillActiveViews(childCount, firstPosition);
            }複製代碼

這裏執行了一個判斷若是dataChanged,那麼會執行addScrapView把view所有廢棄。若是沒有chang那麼執行fillActiveViews,把view添加進數組。這兩個函數在recyclebin中有說過,忘了回看。可是如今尚未填充數據,因此子view中沒有東西,因此這行代碼暫時不起效果。
關於mLayoutMode這個值默認都是LAYOUT_NORMAL,因此會進入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);
                    }
                } else {
                    if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
                        sel = fillSpecific(mSelectedPosition,
                                oldSel == null ? childrenTop : oldSel.getTop());
                    } else if (mFirstPosition < mItemCount) {
                        sel = fillSpecific(mFirstPosition,
                                oldFirst == null ? childrenTop : oldFirst.getTop());
                    } else {
                        sel = fillSpecific(0, childrenTop);
                    }
                }複製代碼

首先判斷了childCount是否等於0,而後驗證了mSelectedPosition是否合規。默認佈局就是從上向下依次填充view,因此是fillTOP,而後FillFromtop的return是filldown()。

* Fills the list from top to bottom, starting with mFirstPosition
 *
 * @param nextTop The location where the top of the first item should be
 *        drawn
 *
 * @return The view that is currently selected
 */
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);
}複製代碼

可知fillDown纔是繪製的真正方法,咱們進入方法內部。

* Fills the list from pos down to the end of the list view.
 *
 * @param pos The first position to put in the list
 *
 * @param nextTop The location where the top of the item associated with pos
 *        should be drawn
 *
 * @return The view that is currently selected, if it happens to be in the
 *         range that we draw.
 */
private View fillDown(int pos, int nextTop) {
    View selectedView = null;

    int end = (mBottom - mTop);
    if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
        end -= mListPadding.bottom;
    }

    while (nextTop < end && pos < mItemCount) {
        // is this the selected item?
        boolean selected = pos == mSelectedPosition;
        View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);

        nextTop = child.getBottom() + mDividerHeight;
        if (selected) {
            selectedView = child;
        }
        pos++;
    }

    setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    return selectedView;
}複製代碼

能夠看到,這裏使用了一個while循環來執行重複邏輯,一開始nextTop的值是第一個子元素頂部距離整個ListView頂部的像素值,pos則是剛剛傳入的mFirstPosition的值,而end是ListView底部減去頂部所得的像素值,mItemCount則是Adapter中的元素數量。所以一開始的狀況下nextTop一定是小於end值的,而且pos也是小於mItemCount值的。那麼每執行一次while循環,pos的值都會加1,而且nextTop也會增長,當nextTop大於等於end時,也就是子元素已經超出當前屏幕了,或者pos大於等於mItemCount時,也就是全部Adapter中的元素都被遍歷結束了,就會跳出while循環。
跳出循環中執行了makeAndAddView()方法,進入這個方法看一下。

* Obtains the view and adds it to our list of children. The view can be
 * made fresh, converted from an unused view, or used as is if it was in
 * the recycle bin.
 *
 * @param position logical position in the list
 * @param y top or bottom edge of the view to add
 * @param flow {@code true} to align top edge to y, {@code false} to align
 *             bottom edge to y
 * @param childrenLeft left edge where children should be positioned
 * @param selected {@code true} if the position is selected, {@code false}
 *                 otherwise
 * @return the view that was added
 */
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; }複製代碼

首先看mDataChanged,前面getchildview是0,因此mDataChanged爲FALSE,而後從ActiveView數組中拿出來一個view。可是數組爲空,因此確定view爲空,因此跳出if,執行obtainView。

View obtainView(int position, boolean[] isScrap) {
    isScrap[0] = false;
    View scrapView;
    scrapView = mRecycler.getScrapView(position);
    View child;
    if (scrapView != null) {
        child = mAdapter.getView(position, scrapView, this);
        if (child != scrapView) {
            mRecycler.addScrapView(scrapView);
            if (mCacheColorHint != 0) {
                child.setDrawingCacheBackgroundColor(mCacheColorHint);
            }
        } else {
            isScrap[0] = true;
            dispatchFinishTemporaryDetach(child);
        }
    } else {
        child = mAdapter.getView(position, null, this);
        if (mCacheColorHint != 0) {
            child.setDrawingCacheBackgroundColor(mCacheColorHint);
        }
    }
    return child;
    }複製代碼

能夠看得出來,這裏確定會返回一個child,一個view。這個view怎麼來的呢,先看源碼。首先嚐試從scrapView數組中獲取一個view,可是咱們是第一次繪製listview因此確定失敗了。而後從else看,child = mAdapter.getView(position, null, this);從mAdapter中獲取一個view。就是前文提到的mAdapter中的getview了。這個時候ListView和adapter聯繫上了。
如今咱們去adapter中看getview方法。

* Get a View that displays the data at the specified position in the data set. You can either
 * create a View manually or inflate it from an XML layout file. When the View is inflated, the
 * parent View (GridView, ListView...) will apply default layout parameters unless you use
 * {@link android.view.LayoutInflater#inflate(int, android.view.ViewGroup, boolean)}
 * to specify a root view and to prevent attachment to the root.
 * 
 * @param position The position of the item within the adapter's data set of the item whose view * we want. * @param convertView The old view to reuse, if possible. Note: You should check that this view * is non-null and of an appropriate type before using. If it is not possible to convert * this view to display the correct data, this method can create a new view. * Heterogeneous lists can specify their number of view types, so that this View is * always of the right type (see {@link #getViewTypeCount()} and * {@link #getItemViewType(int)}). * @param parent The parent that this view will eventually be attached to * @return A View corresponding to the data at the specified position. */ View getView(int position, View convertView, ViewGroup parent);複製代碼

經過註釋咱們能夠看到getview在GridView, ListView...等地方都會用到,在LayoutInflater#inflate中實現,那麼咱們看getview()的三個參數,position view viewgroup。這裏咱們第一次執行getview,傳入的參數爲geview(position,null,this),主要說view爲null的狀況,咱們會調LayoutInflater.inflate()。getview返回,固然obtainView也返回了一個child。這就是爲何第一次加載頁面卡的緣由,每個view都須要經過LayoutInflater.inflate()調用出來。
而後obtainView返回了一個view,makeAndAddView的child那麼也有view了,而後執行setupChild()方法。源碼上面都有,可仔細看一下。若是不方便我爭取作一個思惟導圖。

* Adds a view as a child and make sure it is measured (if necessary) and
 * positioned properly.
 *
 * @param child the view to add
 * @param position the position of this child
 * @param y the y position relative to which this view will be positioned
 * @param flowDown {@code true} to align top edge to y, {@code false} to
 *                 align bottom edge to y
 * @param childrenLeft left edge where children should be positioned
 * @param selected {@code true} if the position is selected, {@code false}
 *                 otherwise
 * @param isAttachedToWindow {@code true} if the view is already attached
 *                           to the window, e.g. whether it was reused, or
 *                           {@code false} otherwise
 */
private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
        boolean selected, boolean isAttachedToWindow) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupListItem");

    final boolean isSelected = selected && shouldShowSelector();
    final boolean updateChildSelected = isSelected != child.isSelected();
    final int mode = mTouchMode;
    final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL
            && mMotionPosition == position;
    final boolean updateChildPressed = isPressed != child.isPressed();
    final boolean needToMeasure = !isAttachedToWindow || updateChildSelected
            || child.isLayoutRequested();

    // Respect layout params that are already in the view. Otherwise make
    // some up...
    AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
    if (p == null) {
        p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
    }
    p.viewType = mAdapter.getItemViewType(position);
    p.isEnabled = mAdapter.isEnabled(position);

    // Set up view state before attaching the view, since we may need to
    // rely on the jumpDrawablesToCurrentState() call that occurs as part
    // of view attachment.
    if (updateChildSelected) {
        child.setSelected(isSelected);
    }

    if (updateChildPressed) {
        child.setPressed(isPressed);
    }

    if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
        if (child instanceof Checkable) {
            ((Checkable) child).setChecked(mCheckStates.get(position));
        } else if (getContext().getApplicationInfo().targetSdkVersion
                >= android.os.Build.VERSION_CODES.HONEYCOMB) {
            child.setActivated(mCheckStates.get(position));
        }
    }

    if ((isAttachedToWindow && !p.forceAdd) || (p.recycledHeaderFooter
            && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
        attachViewToParent(child, flowDown ? -1 : 0, p);

        // If the view was previously attached for a different position,
        // then manually jump the drawables.
        if (isAttachedToWindow
                && (((AbsListView.LayoutParams) child.getLayoutParams()).scrappedFromPosition)
                        != position) {
            child.jumpDrawablesToCurrentState();
        }
    } else {
        p.forceAdd = false;
        if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
            p.recycledHeaderFooter = true;
        }
        addViewInLayout(child, flowDown ? -1 : 0, p, true);
        // add view in layout will reset the RTL properties. We have to re-resolve them
        child.resolveRtlPropertiesIfNeeded();
    }

    if (needToMeasure) {
        final int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
                mListPadding.left + mListPadding.right, p.width);
        final int lpHeight = p.height;
        final int childHeightSpec;
        if (lpHeight > 0) {
            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
        } else {
            childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),
                    MeasureSpec.UNSPECIFIED);
        }
        child.measure(childWidthSpec, childHeightSpec);
    } else {
        cleanupLayoutState(child);
    }

    final int w = child.getMeasuredWidth();
    final int h = child.getMeasuredHeight();
    final int childTop = flowDown ? y : y - h;

    if (needToMeasure) {
        final int childRight = childrenLeft + w;
        final int childBottom = childTop + h;
        child.layout(childrenLeft, childTop, childRight, childBottom);
    } else {
        child.offsetLeftAndRight(childrenLeft - child.getLeft());
        child.offsetTopAndBottom(childTop - child.getTop());
    }

    if (mCachingStarted && !child.isDrawingCacheEnabled()) {
        child.setDrawingCacheEnabled(true);
    }

    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}複製代碼

老樣子,先看註釋。做爲一個子view添加確保這個view已經被測量過以及位置正確。代碼比較多,咱們看關鍵的。既然要添加view,因此咱們看咱們傳進去的子view,何時被調用。attachViewToParent()和addViewInLayout()。這兩個viewgroupo的方法有調用,好咱們具體來看一下。主要是一個判斷Header和Footer,還記得前面的代碼註釋麼?第一次layout不添加Header和Footer,因此咱們先執行addViewInLayout(),把view添加到listliew中,fillDown(),更據while的魂環咱們可知,不管多少數據量,只會填充第一屏的數據。到此爲止,第一次layout執行結束。

  • 第二、3次onlayout
    爲何要把第二次和第三次放在一塊兒呢,這個我卻是沒試過,可是前人的經驗和結果告訴我,view中都會經歷至少兩次onMeasure()和兩次onLayout()的過程。對於其餘的view還行,可是對於listview這個可就是個問題了,listview若是經歷了兩次onMeasure()和onLayout(),這樣的話就會有重複數據。
    那麼問題來了,如何保證不添加劇複數據的呢。在第一次layout中的layoutchild(),第19行,geichild(),由於添加了子view,因此不爲0,這樣在第90行,第一次layout的時候由於當時沒有view,代碼不起做用。(忘了能夠回看第一次layout)如今有了子view,因此就會被添加到mActiveViews數組中,而後關鍵來了!官方也爲此添加了一份註釋。
    Clear out old views detachAllViewsFromParent();
    他就是反覆屢次onMeasure()和onLayout不會有重複數據的緣由。由於,咱們在清除前以及作了一次view的緩存因此,並不會有屢次加載浪費資源的狀況出現。而後繼續走141行if (childCount == 0) (沒找到怎麼加行數=。=尷尬),
    由於有子view了,因此childCount不等於0,而後走else,就不是filldown了,而是fillSpecific(),咱們進入這個方法。
/**
     * Put a specific item at a specific location on the screen and then build
     * up and down from there.
     *
     * @param position The reference view to use as the starting point
     * @param top Pixel offset from the top of this view to the top of the
     *        reference view.
     *
     * @return The selected view, or null if the selected view is outside the
     *         visible area.
     */
    private View fillSpecific(int position, int top) {
        boolean tempIsSelected = position == mSelectedPosition;
        View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);
        // Possibly changed again in fillUp if we add rows above this one.
        mFirstPosition = position;

        View above;
        View below;

        final int dividerHeight = mDividerHeight;
        if (!mStackFromBottom) {
            above = fillUp(position - 1, temp.getTop() - dividerHeight);
            // This will correct for the top of the first view not touching the top of the list
            adjustViewsUpOrDown();
            below = fillDown(position + 1, temp.getBottom() + dividerHeight);
            int childCount = getChildCount();
            if (childCount > 0) {
                correctTooHigh(childCount);
            }
        } else {
            below = fillDown(position + 1, temp.getBottom() + dividerHeight);
            // This will correct for the bottom of the last view not touching the bottom of the list
            adjustViewsUpOrDown();
            above = fillUp(position - 1, temp.getTop() - dividerHeight);
            int childCount = getChildCount();
            if (childCount > 0) {
                 correctTooLow(childCount);
            }
        }

        if (tempIsSelected) {
            return temp;
        } else if (above != null) {
            return above;
        } else {
            return below;
        }
    }複製代碼

老規矩,咱們先看一下官方對他的註解:添加一個特殊的item在屏幕中特殊的位置,而後在從他的上或者下添item。而後咱們一層層看,看到了個老朋友makeAndAddView();這個確定要進去看一下走什麼邏輯了。

/**
     * Obtains the view and adds it to our list of children. The view can be
     * made fresh, converted from an unused view, or used as is if it was in
     * the recycle bin.
     *
     * @param position logical position in the list
     * @param y top or bottom edge of the view to add
     * @param flow {@code true} to align top edge to y, {@code false} to align
     *             bottom edge to y
     * @param childrenLeft left edge where children should be positioned
     * @param selected {@code true} if the position is selected, {@code false}
     *                 otherwise
     * @return the view that was added
     */
    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; }複製代碼

又是在19行,仍是同樣的方法,獲取getActiveView(),這下確定沒問題了,進入if執行setupChild(),而後就直接return了。進去setupChild(),注意,這裏的setupchild(),最後一個參數是true。

/**
     * Adds a view as a child and make sure it is measured (if necessary) and
     * positioned properly.
     *
     * @param child the view to add
     * @param position the position of this child
     * @param y the y position relative to which this view will be positioned
     * @param flowDown {@code true} to align top edge to y, {@code false} to
     *                 align bottom edge to y
     * @param childrenLeft left edge where children should be positioned
     * @param selected {@code true} if the position is selected, {@code false}
     *                 otherwise
     * @param isAttachedToWindow {@code true} if the view is already attached
     *                           to the window, e.g. whether it was reused, or
     *                           {@code false} otherwise
     */
    private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
            boolean selected, boolean isAttachedToWindow) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupListItem");

        final boolean isSelected = selected && shouldShowSelector();
        final boolean updateChildSelected = isSelected != child.isSelected();
        final int mode = mTouchMode;
        final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL
                && mMotionPosition == position;
        final boolean updateChildPressed = isPressed != child.isPressed();
        final boolean needToMeasure = !isAttachedToWindow || updateChildSelected
                || child.isLayoutRequested();

        // Respect layout params that are already in the view. Otherwise make
        // some up...
        AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
        if (p == null) {
            p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
        }
        p.viewType = mAdapter.getItemViewType(position);
        p.isEnabled = mAdapter.isEnabled(position);

        // Set up view state before attaching the view, since we may need to
        // rely on the jumpDrawablesToCurrentState() call that occurs as part
        // of view attachment.
        if (updateChildSelected) {
            child.setSelected(isSelected);
        }

        if (updateChildPressed) {
            child.setPressed(isPressed);
        }

        if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
            if (child instanceof Checkable) {
                ((Checkable) child).setChecked(mCheckStates.get(position));
            } else if (getContext().getApplicationInfo().targetSdkVersion
                    >= android.os.Build.VERSION_CODES.HONEYCOMB) {
                child.setActivated(mCheckStates.get(position));
            }
        }

        if ((isAttachedToWindow && !p.forceAdd) || (p.recycledHeaderFooter
                && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
            attachViewToParent(child, flowDown ? -1 : 0, p);

            // If the view was previously attached for a different position,
            // then manually jump the drawables.
            if (isAttachedToWindow
                    && (((AbsListView.LayoutParams) child.getLayoutParams()).scrappedFromPosition)
                            != position) {
                child.jumpDrawablesToCurrentState();
            }
        } else {
            p.forceAdd = false;
            if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                p.recycledHeaderFooter = true;
            }
            addViewInLayout(child, flowDown ? -1 : 0, p, true);
            // add view in layout will reset the RTL properties. We have to re-resolve them
            child.resolveRtlPropertiesIfNeeded();
        }

        if (needToMeasure) {
            final int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
                    mListPadding.left + mListPadding.right, p.width);
            final int lpHeight = p.height;
            final int childHeightSpec;
            if (lpHeight > 0) {
                childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
            } else {
                childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),
                        MeasureSpec.UNSPECIFIED);
            }
            child.measure(childWidthSpec, childHeightSpec);
        } else {
            cleanupLayoutState(child);
        }

        final int w = child.getMeasuredWidth();
        final int h = child.getMeasuredHeight();
        final int childTop = flowDown ? y : y - h;

        if (needToMeasure) {
            final int childRight = childrenLeft + w;
            final int childBottom = childTop + h;
            child.layout(childrenLeft, childTop, childRight, childBottom);
        } else {
            child.offsetLeftAndRight(childrenLeft - child.getLeft());
            child.offsetTopAndBottom(childTop - child.getTop());
        }

        if (mCachingStarted && !child.isDrawingCacheEnabled()) {
            child.setDrawingCacheEnabled(true);
        }

        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }複製代碼

最後一個boolean值是isAttachedToWindow,不同的地方我們打高亮看,看到了if,咱們進去,由於是TRUE,因此執行attachViewToParent();由於,咱們第二次layout的view是被清除掉的,狀態爲death的view,因此咱們使用attachViewToParent()方法,爲何第一次layout使用addViewInLayout呢?
是由於viewgroup中建立view使用addViewInLayout,重用也就是狀態爲death的view用attachViewToParent()方法。再深層次就不細究了。等之後有時間再說吧。。。。。這裏兩次onlayout,才能夠說Listview真正的展示出來了。

4、滑動LIsetView

由於我是作機頂盒TV的因此我想探究一下用按鍵事件ListView的數據是怎麼變化的。這塊都是本身找的源碼,有不對請指正!

1、首先肯定監聽器,按鍵事件是onkey和onfocus。首先在AbsListView中查看。

@Override
    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
        if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
            if (!isAttachedToWindow() && mAdapter != null) {
                // Data may have changed while we were detached and it's valid // to change focus while detached. Refresh so we don't die.
                mDataChanged = true;
                mOldItemCount = mItemCount;
                mItemCount = mAdapter.getCount();
            }
            resurrectSelection();
        }
    }
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        return false;
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (KeyEvent.isConfirmKey(keyCode)) {
            if (!isEnabled()) {
                return true;
            }
            if (isClickable() && isPressed() &&
                    mSelectedPosition >= 0 && mAdapter != null &&
                    mSelectedPosition < mAdapter.getCount()) {

                final View view = getChildAt(mSelectedPosition - mFirstPosition);
                if (view != null) {
                    performItemClick(view, mSelectedPosition, mSelectedRowId);
                    view.setPressed(false);
                }
                setPressed(false);
                return true;
            }
        }
        return super.onKeyUp(keyCode, event);
    }複製代碼

然而並無什麼發現,而後去ListView中尋找。

@Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        return commonKey(keyCode, 1, event);
    }

    @Override
    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
        return commonKey(keyCode, repeatCount, event);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        return commonKey(keyCode, 1, event);
    }

    private boolean commonKey(int keyCode, int count, KeyEvent event) {
        if (mAdapter == null || !isAttachedToWindow()) {
            return false;
        }

        if (mDataChanged) {
            layoutChildren();
        }

        boolean handled = false;
        int action = event.getAction();
        if (KeyEvent.isConfirmKey(keyCode)
                && event.hasNoModifiers() && action != KeyEvent.ACTION_UP) {
            handled = resurrectSelectionIfNeeded();
            if (!handled && event.getRepeatCount() == 0 && getChildCount() > 0) {
                keyPressed();
                handled = true;
            }
        }


        if (!handled && action != KeyEvent.ACTION_UP) {
            switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_UP:
                if (event.hasNoModifiers()) {
                    handled = resurrectSelectionIfNeeded();
                    if (!handled) {
                        while (count-- > 0) {
                            if (arrowScroll(FOCUS_UP)) {
                                handled = true;
                            } else {
                                break;
                            }
                        }
                    }
                } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
                }
                break;

            case KeyEvent.KEYCODE_DPAD_DOWN:
                if (event.hasNoModifiers()) {
                    handled = resurrectSelectionIfNeeded();
                    if (!handled) {
                        while (count-- > 0) {
                            if (arrowScroll(FOCUS_DOWN)) {
                                handled = true;
                            } else {
                                break;
                            }
                        }
                    }
                } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
                }
                break;

            case KeyEvent.KEYCODE_DPAD_LEFT:
                if (event.hasNoModifiers()) {
                    handled = handleHorizontalFocusWithinListItem(View.FOCUS_LEFT);
                }
                break;

            case KeyEvent.KEYCODE_DPAD_RIGHT:
                if (event.hasNoModifiers()) {
                    handled = handleHorizontalFocusWithinListItem(View.FOCUS_RIGHT);
                }
                break;

            case KeyEvent.KEYCODE_PAGE_UP:
                if (event.hasNoModifiers()) {
                    handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
                } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
                }
                break;

            case KeyEvent.KEYCODE_PAGE_DOWN:
                if (event.hasNoModifiers()) {
                    handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
                } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
                }
                break;

            case KeyEvent.KEYCODE_MOVE_HOME:
                if (event.hasNoModifiers()) {
                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
                }
                break;

            case KeyEvent.KEYCODE_MOVE_END:
                if (event.hasNoModifiers()) {
                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
                }
                break;

            case KeyEvent.KEYCODE_TAB:
                // This creates an asymmetry in TAB navigation order. At some
                // point in the future we may decide that it's preferable to // force the list selection to the top or bottom when receiving // TAB focus from another widget, but for now this is adequate. if (event.hasNoModifiers()) { handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN); } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) { handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP); } break; } } if (handled) { return true; } if (sendToTextFilter(keyCode, count, event)) { return true; } switch (action) { case KeyEvent.ACTION_DOWN: return super.onKeyDown(keyCode, event); case KeyEvent.ACTION_UP: return super.onKeyUp(keyCode, event); case KeyEvent.ACTION_MULTIPLE: return super.onKeyMultiple(keyCode, count, event); default: // shouldn't happen
                return false;
        }
    }複製代碼

在ListView中onkey事件被分紅了3種事件:onKeyDownonKeyMultipleonKeyUp。都是調用commonKey()方法,結果沒有什麼發現。咱們再看onfocus()事件。

@Override
    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);

        final ListAdapter adapter = mAdapter;
        int closetChildIndex = -1;
        int closestChildTop = 0;
        if (adapter != null && gainFocus && previouslyFocusedRect != null) {
            previouslyFocusedRect.offset(mScrollX, mScrollY);

            // Don't cache the result of getChildCount or mFirstPosition here, // it could change in layoutChildren. if (adapter.getCount() < getChildCount() + mFirstPosition) { mLayoutMode = LAYOUT_NORMAL; layoutChildren(); } // figure out which item should be selected based on previously // focused rect Rect otherRect = mTempRect; int minDistance = Integer.MAX_VALUE; final int childCount = getChildCount(); final int firstPosition = mFirstPosition; for (int i = 0; i < childCount; i++) { // only consider selectable views if (!adapter.isEnabled(firstPosition + i)) { continue; } View other = getChildAt(i); other.getDrawingRect(otherRect); offsetDescendantRectToMyCoords(other, otherRect); int distance = getDistance(previouslyFocusedRect, otherRect, direction); if (distance < minDistance) { minDistance = distance; closetChildIndex = i; closestChildTop = other.getTop(); } } } if (closetChildIndex >= 0) { setSelectionFromTop(closetChildIndex + mFirstPosition, closestChildTop); } else { requestLayout(); } }複製代碼

代碼量不多,古話說得好,有備註就有但願,結果然被我發現了。

// Don't cache the result of getChildCount or mFirstPosition here, // it could change in layoutChildren. if (adapter.getCount() < getChildCount() + mFirstPosition) { mLayoutMode = LAYOUT_NORMAL; layoutChildren(); }複製代碼

備註是不要緩存這裏得到子節點個數或mfirstposition結果,它能夠改變layoutchildren。
這裏的if判斷就是若是adapter.getCount()小於getChildCount() + mFirstPosition。
意思就是在最後一個item變成倒數第二個item了,這個時候就要新增一個item,而且回收。咱們繼續往裏面看,前面也有說mLayoutMode的默認方式是LAYOUT_NORMAL,而後進入 layoutChildren()方法。
又回到熟悉的方法了。老樣子前面的就不說了,不過要變得是datachange了,因此咱們要把最後一個item前面的全部item加入scrpview數組,而後直接去關鍵點,fillSpecific()方法中makeAndAddView(),返回一個view就是新增的item,而後往下走。在第二次layout沒有往下走是由於咱們新增的是第一個item,因此後面的無所謂,可是如今不同了。

if (!mStackFromBottom) {
            above = fillUp(position - 1, temp.getTop() - dividerHeight);
            // This will correct for the top of the first view not touching the top of the list
            adjustViewsUpOrDown();
            below = fillDown(position + 1, temp.getBottom() + dividerHeight);
            int childCount = getChildCount();
            if (childCount > 0) {
                correctTooHigh(childCount);
            }
        } else {
            below = fillDown(position + 1, temp.getBottom() + dividerHeight);
            // This will correct for the bottom of the last view not touching the bottom of the list
            adjustViewsUpOrDown();
            above = fillUp(position - 1, temp.getTop() - dividerHeight);
            int childCount = getChildCount();
            if (childCount > 0) {
                 correctTooLow(childCount);
            }
        }複製代碼

之前都是走上面,如今是走else,先filldown,加載下一個數據,而後關鍵(註釋)來了adjustViewsUpOrDown(),這將更正最後一個視圖的底部,而不是在列表的底部。意思是將屏幕外的第一個item加載到視圖底部,而後再用fillup()加載如今最後一個item上面的其他item。
fillup()和filldown()都是經過循壞去對listview進行加載,可是涉及到加載就會使用makeAndAddView()方法。此次咱們再看一下流程,具體能夠參考第一次layout。到了obtainView()這個方法的時候就不同了。咱們看一下源碼。

/**
     * Gets a view and have it show the data associated with the specified
     * position. This is called when we have already discovered that the view
     * is not available for reuse in the recycle bin. The only choices left are
     * converting an old view or making a new one.
     *
     * @param position the position to display
     * @param outMetadata an array of at least 1 boolean where the first entry
     *                    will be set {@code true} if the view is currently
     *                    attached to the window, {@code false} otherwise (e.g.
     *                    newly-inflated or remained scrap for multiple layout
     *                    passes)
     *
     * @return A view displaying the data associated with the specified position
     */
    View obtainView(int position, boolean[] outMetadata) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");

        outMetadata[0] = false;

        // 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); } } outMetadata[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 if (child.isTemporarilyDetached()) { outMetadata[0] = true; // Finish the temporary detach started in addScrapView(). child.dispatchFinishTemporaryDetach(); } } if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); } if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } setItemViewLayoutParams(child, position); if (AccessibilityManager.getInstance(mContext).isEnabled()) { if (mAccessibilityDelegate == null) { mAccessibilityDelegate = new ListItemAccessibilityDelegate(); } if (child.getAccessibilityDelegate() == null) { child.setAccessibilityDelegate(mAccessibilityDelegate); } } Trace.traceEnd(Trace.TRACE_TAG_VIEW); return child; }複製代碼

scrapView,這裏是第一次layout沒有調到的地方,此次scrapView數組裏面是有值了。咱們一旦有新的數據須要顯示,就會從scrapView數組中取出。因此關於LIstview,無論有多少條數據,永遠只有那麼幾個,這個就是OOM不會出現的緣由。
這個是圖片總結,從康志大大的博客上取來的,偷了點懶

5、總結

這篇博客從準備到學習花了有快1個星期的事件,都是下班時間一點點看,一點點學,才畢業沒多久不想讓本身荒廢。後續我還會學習其餘的源碼,還有一些實踐app但願與你們分享。在這個知識共享的年代一塊兒進步學習!
不是打廣告,是我下一步的學習目標。
HenCoder Android 開發進階: 自定義 View隨便看了看,真的受益不淺。

相關文章
相關標籤/搜索