Android ListView緩存複用原理 源碼解析

我先解釋下,listView隨着滑動的複用邏輯!

首先:攔截先不用說;下面的文章會進行說明,直接說listView重寫的onTouchEvent()事件!

當手指觸摸move的時候,listView 最終會走 trackMotionScroll(int deltaY, int incrementalDeltaY)這一方法; incrementalDeltaY就是兩次移動的y軸座標差值!
對 incrementalDeltaY進行判斷
如果 incrementalDeltaY <0 則是下滑,
否則則是上滑!這個方法將進行判斷,

加進scrapView中,

無論是上滑還是下滑都會把移出屏幕的view(不是完全移除,當前view移除屏幕就算)都會加入scrapView中
並且detachViewsFromParent(start, count);對加入緩存的view detach掉FromParent!

而下面則繼續:

listView中的所有view進行平移

offsetChildrenTopAndBottom(incrementalDeltaY); 對listView中的所有view進行平移

然後當某一view徹底滑出屏幕的時候 也就是這個判斷
if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
fillGap(down);
}
進行view的添加;
而這面就進行了複用的邏輯;
fillGap(down);這個方法裏面都處理好了!

當你要展示最下面的view的時候,會拿你最上面剛剛完全移除屏幕的view,

當你要展示最上面的view的時候,會拿你最下面剛剛完全移除屏幕的view,

這樣就完美的用到了複用,而且剛剛移除屏幕的view,瞬間就會被用到!
這就是簡單的複用邏輯,從scrapView拿出view之後會被置爲null的(原集合)!

 final View scrap = scrapViews.remove(size - 1);
 clearScrapForRebind(scrap);
 return  scrap 

參考此篇文章:http://blog.csdn.net/guolin_blog/article/details/44996879

RecycleBin機制
其實還是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.
         */
        //佈局開始時屏幕顯示的view,這個數組會在佈局開始時填充,佈局結束後所有view被移至mScrapViews。(也包括是當前屏幕所展示的活躍view)
        private View[] mActiveViews = new View[0];

        /**
         * Unsorted views that can be used by the adapter as a convert view.
         */
        //ListView中所有的廢棄緩存。這是一個數組,每一種佈局類型的view都有一個自己的arraylist緩存
        private ArrayList<View>[] mScrapViews;
       
        //listView中不同item佈局的佈局總數
        private int mViewTypeCount;
        
        //跟mScrapViews的區別是,mScrapViews是個隊列數組,ArrayList<View>[]類型,數組長度爲mViewTypeCount,而默認ViewTypeCount = 1的情況下mCurrentScrap=mScrapViews[0]。
        private ArrayList<View> mCurrentScrap;
        
        // 被跳過的,不能複用的view集合。view type小於0或者處理transient狀態的view不能被複用
        private ArrayList<View> mSkippedScrap;

        //處於transient狀態的view集合,處於transient狀態的view不能被複用,如view的動畫正在播放, 
        // transient是瞬時、短暫的意思
        private SparseArray<View> mTransientStateViews;

        //如果adapter的hasStableIds方法返回true,處於過度狀態的view保存到這裏。
        //因爲需要保存view的position,而且處於過度狀態的view一般很少,
        //知道是保存轉換狀態view的集合就行,itemId作爲key(其實就是view的position)
        private LongSparseArray<view> mTransientStateViewsById

        public void setViewTypeCount(int viewTypeCount) {
          ........
        }

        public void markChildrenDirty() {
            if (mViewTypeCount == 1) {
              .......
            } else {
              ......
            }
            if (mTransientStateViews != null) {
             ......
            }
            if (mTransientStateViewsById != null) {
              .....
            }
        }

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

        /**
         * Clears the scrap heap.
         */
        void clear() {
         .......
        }

        /**
         * 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) {
                .......
            return null;
        }

        /**
         * Dumps and fully detaches any currently saved views with transient
         * state.
         */
        void clearTransientStateViews() {
                .......
        }

        /**
         * @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();
            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() {
         .......
        }

        /**
         * 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() {
              .......
        }

        /**
         * 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() {
           .......
        }

        /**
         * Updates the cache color hint of all known views.
         *
         * @param color The new cache color hint.
         */
        void setCacheColorHint(int 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;
            }
        }
    }
 void fillActiveViews(int childCount, int firstActivePosition) 
        }

這個方法接收兩個參數,第一個參數表示要存儲的view的數量,第二個參數表示ListView中第一個可見元素的position值。RecycleBin當中使用mActiveViews這個數組來存儲View,調用這個方法後就會根據傳入的參數來將ListView中的指定元素存儲到mActiveViews數組當中。

 View getActiveView(int position) {
        }

這個方法和fillActiveViews()是對應的,用於從mActiveViews數組當中獲取數據。該方法接收一個position參數,表示元素在ListView當中的位置,方法內部會自動將position值轉換成mActiveViews數組對應的下標值。需要注意的是,mActiveViews當中所存儲的View,一旦被獲取了之後就會從mActiveViews當中移除,下次獲取同樣位置的View將會返回null,也就是說mActiveViews不能被重複利用。

 void addScrapView(View scrap, int position) {
        }

用於將一個廢棄的View進行緩存,該方法接收一個View參數,當有某個View確定要廢棄掉的時候(比如滾動出了屏幕),就應該調用這個方法來對View進行緩存,RecycleBin當中使用mScrapViews和mCurrentScrap這兩個List來存儲廢棄View。

  View getScrapView(int position) {
        }

用於從廢棄緩存中取出一個View,這些廢棄緩存中的View是沒有順序可言的,因此getScrapView()方法中的算法也非常簡單,就是直接從mCurrentScrap當中獲取尾部的一個scrap view進行返回。

 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;
        }

我們都知道Adapter當中可以重寫一個getViewTypeCount()來表示ListView中有幾種類型的數據項,而setViewTypeCount()方法的作用就是爲每種類型的數據項都單獨啓用一個RecycleBin緩存機制。實際上,getViewTypeCount()方法通常情況下使用的並不是很多,所以我們只要知道RecycleBin當中有這樣一個功能就行了。

那麼下面我們看是怎麼複用view的

滑動加載更多數據

由於滑動部分的機制是屬於通用型的,即ListView和GridView都會使用同樣的機制,因此這部分代碼就肯定是寫在AbsListView當中的了。那麼監聽觸控事件是在onTouchEvent()方法當中進行的,我們就來看一下AbsListView中的這個方法:

 public boolean onTouchEvent(MotionEvent ev) {
        if (!isEnabled()) {
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return isClickable() || isLongClickable();
        }

        if (mPositionScroller != null) {
            mPositionScroller.stop();
        }

        if (mIsDetaching || !isAttachedToWindow()) {
            // Something isn't right.
            // Since we rely on being attached to get data set change notifications,
            // don't risk doing anything where we might try to resync and find things
            // in a bogus state.
            return false;
        }

        startNestedScroll(SCROLL_AXIS_VERTICAL);

        if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {
            return true;
        }

        initVelocityTrackerIfNotExists();
        final MotionEvent vtev = MotionEvent.obtain(ev);

        final int actionMasked = ev.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            mNestedYOffset = 0;
        }
        vtev.offsetLocation(0, mNestedYOffset);
        switch (actionMasked) {
            case MotionEvent.ACTION_DOWN: {
                onTouchDown(ev);
                break;
            }

            case MotionEvent.ACTION_MOVE: {
                onTouchMove(ev, vtev);
                break;
            }

            case MotionEvent.ACTION_UP: {
                onTouchUp(ev);
                break;
            }

            case MotionEvent.ACTION_CANCEL: {
                onTouchCancel();
                break;
            }

            case MotionEvent.ACTION_POINTER_UP: {
                onSecondaryPointerUp(ev);
                final int x = mMotionX;
                final int y = mMotionY;
                final int motionPosition = pointToPosition(x, y);
                if (motionPosition >= 0) {
                    // Remember where the motion event started
                    final View child = getChildAt(motionPosition - mFirstPosition);
                    mMotionViewOriginalTop = child.getTop();
                    mMotionPosition = motionPosition;
                }
                mLastY = y;
                break;
            }

            case MotionEvent.ACTION_POINTER_DOWN: {
                // New pointers take over dragging duties
                final int index = ev.getActionIndex();
                final int id = ev.getPointerId(index);
                final int x = (int) ev.getX(index);
                final int y = (int) ev.getY(index);
                mMotionCorrection = 0;
                mActivePointerId = id;
                mMotionX = x;
                mMotionY = y;
                final int motionPosition = pointToPosition(x, y);
                if (motionPosition >= 0) {
                    // Remember where the motion event started
                    final View child = getChildAt(motionPosition - mFirstPosition);
                    mMotionViewOriginalTop = child.getTop();
                    mMotionPosition = motionPosition;
                }
                mLastY = y;
                break;
            }
        }

        if (mVelocityTracker != null) {
            mVelocityTracker.addMovement(vtev);
        }
        vtev.recycle();
        return true;
    }

    private void onTouchDown(MotionEvent ev) {
        mHasPerformedLongPress = false;
        mActivePointerId = ev.getPointerId(0);
        hideSelector();

        if (mTouchMode == TOUCH_MODE_OVERFLING) {
            // Stopped the fling. It is a scroll.
            mFlingRunnable.endFling();
            if (mPositionScroller != null) {
                mPositionScroller.stop();
            }
            mTouchMode = TOUCH_MODE_OVERSCROLL;
            mMotionX = (int) ev.getX();
            mMotionY = (int) ev.getY();
            mLastY = mMotionY;
            mMotionCorrection = 0;
            mDirection = 0;
        } else {
            final int x = (int) ev.getX();
            final int y = (int) ev.getY();
            int motionPosition = pointToPosition(x, y);

            if (!mDataChanged) {
                if (mTouchMode == TOUCH_MODE_FLING) {
                    // Stopped a fling. It is a scroll.
                    createScrollingCache();
                    mTouchMode = TOUCH_MODE_SCROLL;
                    mMotionCorrection = 0;
                    motionPosition = findMotionRow(y);
                    mFlingRunnable.flywheelTouch();
                } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
                    // User clicked on an actual view (and was not stopping a
                    // fling). It might be a click or a scroll. Assume it is a
                    // click until proven otherwise.
                    mTouchMode = TOUCH_MODE_DOWN;

                    // FIXME Debounce
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }

                    mPendingCheckForTap.x = ev.getX();
                    mPendingCheckForTap.y = ev.getY();
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                }
            }

            if (motionPosition >= 0) {
                // Remember where the motion event started
                final View v = getChildAt(motionPosition - mFirstPosition);
                mMotionViewOriginalTop = v.getTop();
            }

            mMotionX = x;
            mMotionY = y;
            mMotionPosition = motionPosition;
            mLastY = Integer.MIN_VALUE;
        }

        if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
                && performButtonActionOnTouchDown(ev)) {
                removeCallbacks(mPendingCheckForTap);
        }
    }

    private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
        if (mHasPerformedLongPress) {
            // Consume all move events following a successful long press.
            return;
        }

        int pointerIndex = ev.findPointerIndex(mActivePointerId);
        if (pointerIndex == -1) {
            pointerIndex = 0;
            mActivePointerId = ev.getPointerId(pointerIndex);
        }

        if (mDataChanged) {
            // Re-sync everything if data has been changed
            // since the scroll operation can query the adapter.
            layoutChildren();
        }

        final int y = (int) ev.getY(pointerIndex);

        switch (mTouchMode) {
            case TOUCH_MODE_DOWN:
            case TOUCH_MODE_TAP:
            case TOUCH_MODE_DONE_WAITING:
                // Check if we have moved far enough that it looks more like a
                // scroll than a tap. If so, we'll enter scrolling mode.
                if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
                    break;
                }
                // Otherwise, check containment within list bounds. If we're
                // outside bounds, cancel any active presses.
                final View motionView = getChildAt(mMotionPosition - mFirstPosition);
                final float x = ev.getX(pointerIndex);
                if (!pointInView(x, y, mTouchSlop)) {
                    setPressed(false);
                    if (motionView != null) {
                        motionView.setPressed(false);
                    }
                    removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
                            mPendingCheckForTap : mPendingCheckForLongPress);
                    mTouchMode = TOUCH_MODE_DONE_WAITING;
                    updateSelectorState();
                } else if (motionView != null) {
                    // Still within bounds, update the hotspot.
                    final float[] point = mTmpPoint;
                    point[0] = x;
                    point[1] = y;
                    transformPointToViewLocal(point, motionView);
                    motionView.drawableHotspotChanged(point[0], point[1]);
                }
                break;
            case TOUCH_MODE_SCROLL:
            case TOUCH_MODE_OVERSCROLL:
                scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
                break;
        }
    }

    private void onTouchUp(MotionEvent ev) {
        switch (mTouchMode) {
        case TOUCH_MODE_DOWN:
        case TOUCH_MODE_TAP:
        case TOUCH_MODE_DONE_WAITING:
            final int motionPosition = mMotionPosition;
            final View child = getChildAt(motionPosition - mFirstPosition);
            if (child != null) {
                if (mTouchMode != TOUCH_MODE_DOWN) {
                    child.setPressed(false);
                }

                final float x = ev.getX();
                final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
                if (inList && !child.hasExplicitFocusable()) {
                    if (mPerformClick == null) {
                        mPerformClick = new PerformClick();
                    }

                    final AbsListView.PerformClick performClick = mPerformClick;
                    performClick.mClickMotionPosition = motionPosition;
                    performClick.rememberWindowAttachCount();

                    mResurrectToPosition = motionPosition;

                    if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
                        removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
                                mPendingCheckForTap : mPendingCheckForLongPress);
                        mLayoutMode = LAYOUT_NORMAL;
                        if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
                            mTouchMode = TOUCH_MODE_TAP;
                            setSelectedPositionInt(mMotionPosition);
                            layoutChildren();
                            child.setPressed(true);
                            positionSelector(mMotionPosition, child);
                            setPressed(true);
                            if (mSelector != null) {
                                Drawable d = mSelector.getCurrent();
                                if (d != null && d instanceof TransitionDrawable) {
                                    ((TransitionDrawable) d).resetTransition();
                                }
                                mSelector.setHotspot(x, ev.getY());
                            }
                            if (mTouchModeReset != null) {
                                removeCallbacks(mTouchModeReset);
                            }
                            mTouchModeReset = new Runnable() {
                                @Override
                                public void run() {
                                    mTouchModeReset = null;
                                    mTouchMode = TOUCH_MODE_REST;
                                    child.setPressed(false);
                                    setPressed(false);
                                    if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
                                        performClick.run();
                                    }
                                }
                            };
                            postDelayed(mTouchModeReset,
                                    ViewConfiguration.getPressedStateDuration());
                        } else {
                            mTouchMode = TOUCH_MODE_REST;
                            updateSelectorState();
                        }
                        return;
                    } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
                        performClick.run();
                    }
                }
            }
            mTouchMode = TOUCH_MODE_REST;
            updateSelectorState();
            break;
        case TOUCH_MODE_SCROLL:
            final int childCount = getChildCount();
            if (childCount > 0) {
                final int firstChildTop = getChildAt(0).getTop();
                final int lastChildBottom = getChildAt(childCount - 1).getBottom();
                final int contentTop = mListPadding.top;
                final int contentBottom = getHeight() - mListPadding.bottom;
                if (mFirstPosition == 0 && firstChildTop >= contentTop &&
                        mFirstPosition + childCount < mItemCount &&
                        lastChildBottom <= getHeight() - contentBottom) {
                    mTouchMode = TOUCH_MODE_REST;
                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
                } else {
                    final VelocityTracker velocityTracker = mVelocityTracker;
                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);

                    final int initialVelocity = (int)
                            (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
                    // Fling if we have enough velocity and we aren't at a boundary.
                    // Since we can potentially overfling more than we can overscroll, don't
                    // allow the weird behavior where you can scroll to a boundary then
                    // fling further.
                    boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;
                    if (flingVelocity &&
                            !((mFirstPosition == 0 &&
                                    firstChildTop == contentTop - mOverscrollDistance) ||
                              (mFirstPosition + childCount == mItemCount &&
                                    lastChildBottom == contentBottom + mOverscrollDistance))) {
                        if (!dispatchNestedPreFling(0, -initialVelocity)) {
                            if (mFlingRunnable == null) {
                                mFlingRunnable = new FlingRunnable();
                            }
                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
                            mFlingRunnable.start(-initialVelocity);
                            dispatchNestedFling(0, -initialVelocity, true);
                        } else {
                            mTouchMode = TOUCH_MODE_REST;
                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
                        }
                    } else {
                        mTouchMode = TOUCH_MODE_REST;
                        reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
                        if (mFlingRunnable != null) {
                            mFlingRunnable.endFling();
                        }
                        if (mPositionScroller != null) {
                            mPositionScroller.stop();
                        }
                        if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) {
                            dispatchNestedFling(0, -initialVelocity, false);
                        }
                    }
                }
            } else {
                mTouchMode = TOUCH_MODE_REST;
                reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
            }
            break;

        case TOUCH_MODE_OVERSCROLL:
            if (mFlingRunnable == null) {
                mFlingRunnable = new FlingRunnable();
            }
            final VelocityTracker velocityTracker = mVelocityTracker;
            velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
            final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);

            reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
            if (Math.abs(initialVelocity) > mMinimumVelocity) {
                mFlingRunnable.startOverfling(-initialVelocity);
            } else {
                mFlingRunnable.startSpringback();
            }

            break;
        }

        setPressed(false);

        if (mEdgeGlowTop != null) {
            mEdgeGlowTop.onRelease();
            mEdgeGlowBottom.onRelease();
        }

        // Need to redraw since we probably aren't drawing the selector anymore
        invalidate();
        removeCallbacks(mPendingCheckForLongPress);
        recycleVelocityTracker();

        mActivePointerId = INVALID_POINTER;

        if (PROFILE_SCROLLING) {
            if (mScrollProfilingStarted) {
                Debug.stopMethodTracing();
                mScrollProfilingStarted = false;
            }
        }

        if (mScrollStrictSpan != null) {
            mScrollStrictSpan.finish();
            mScrollStrictSpan = null;
        }
    }

這樣,因爲方法比較多,那我們只看

 case MotionEvent.ACTION_MOVE: {
                onTouchMove(ev, vtev);
                break;
            }

就就是move,
可以看到,ACTION_MOVE這個case裏面又嵌套了一個switch語句,是根據當前的TouchMode來選擇的。那這裏我可以直接告訴大家,當手指在屏幕上滑動時,TouchMode是等於TOUCH_MODE_SCROLL這個值的,
爲什麼TouchMode的值是TOUCH_MODE_SCROLL呢?
這面就得追朔到

 public boolean onInterceptTouchEvent(MotionEvent ev) {
                     case MotionEvent.ACTION_MOVE: {
            switch (mTouchMode) {
            case TOUCH_MODE_DOWN:
                int pointerIndex = ev.findPointerIndex(mActivePointerId);
                if (pointerIndex == -1) {
                    pointerIndex = 0;
                    mActivePointerId = ev.getPointerId(pointerIndex);
                }
                final int y = (int) ev.getY(pointerIndex);
                initVelocityTrackerIfNotExists();
                mVelocityTracker.addMovement(ev);
                if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
                    return true;
                }
                break;
            }
            break;
        }
            }

會通過startScrollIfNeeded((int) ev.getX(pointerIndex), y, null))這個方法進行賦值TouchMode TOUCH_MODE_SCROLL;
這樣就會走到

   private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
        if (mHasPerformedLongPress) {
            // Consume all move events following a successful long press.
            return;
        }

        int pointerIndex = ev.findPointerIndex(mActivePointerId);
        if (pointerIndex == -1) {
            pointerIndex = 0;
            mActivePointerId = ev.getPointerId(pointerIndex);
        }

        if (mDataChanged) {
            // Re-sync everything if data has been changed
            // since the scroll operation can query the adapter.
            layoutChildren();
        }

        final int y = (int) ev.getY(pointerIndex);

        switch (mTouchMode) {
            case TOUCH_MODE_DOWN:
            case TOUCH_MODE_TAP:
            case TOUCH_MODE_DONE_WAITING:
                // Check if we have moved far enough that it looks more like a
                // scroll than a tap. If so, we'll enter scrolling mode.
                if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
                    break;
                }
                // Otherwise, check containment within list bounds. If we're
                // outside bounds, cancel any active presses.
                final View motionView = getChildAt(mMotionPosition - mFirstPosition);
                final float x = ev.getX(pointerIndex);
                if (!pointInView(x, y, mTouchSlop)) {
                    setPressed(false);
                    if (motionView != null) {
                        motionView.setPressed(false);
                    }
                    removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
                            mPendingCheckForTap : mPendingCheckForLongPress);
                    mTouchMode = TOUCH_MODE_DONE_WAITING;
                    updateSelectorState();
                } else if (motionView != null) {
                    // Still within bounds, update the hotspot.
                    final float[] point = mTmpPoint;
                    point[0] = x;
                    point[1] = y;
                    transformPointToViewLocal(point, motionView);
                    motionView.drawableHotspotChanged(point[0], point[1]);
                }
                break;
            case TOUCH_MODE_SCROLL:
            case TOUCH_MODE_OVERSCROLL:
                scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
                break;
        }
    }

這裏面的 scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);這一方法;

   private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
        int rawDeltaY = y - mMotionY;
        int scrollOffsetCorrection = 0;
        int scrollConsumedCorrection = 0;
        if (mLastY == Integer.MIN_VALUE) {
            rawDeltaY -= mMotionCorrection;
        }
        if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY,
                mScrollConsumed, mScrollOffset)) {
            rawDeltaY += mScrollConsumed[1];
            scrollOffsetCorrection = -mScrollOffset[1];
            scrollConsumedCorrection = mScrollConsumed[1];
            if (vtev != null) {
                vtev.offsetLocation(0, mScrollOffset[1]);
                mNestedYOffset += mScrollOffset[1];
            }
        }
        final int deltaY = rawDeltaY;
        int incrementalDeltaY =
                mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
        int lastYCorrection = 0;

        if (mTouchMode == TOUCH_MODE_SCROLL) {
            if (PROFILE_SCROLLING) {
                if (!mScrollProfilingStarted) {
                    Debug.startMethodTracing("AbsListViewScroll");
                    mScrollProfilingStarted = true;
                }
            }

            if (mScrollStrictSpan == null) {
                // If it's non-null, we're already in a scroll.
                mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
            }

            if (y != mLastY) {
                // We may be here after stopping a fling and continuing to scroll.
                // If so, we haven't disallowed intercepting touch events yet.
                // Make sure that we do so in case we're in a parent that can intercept.
                if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
                        Math.abs(rawDeltaY) > mTouchSlop) {
                    final ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                }

                final int motionIndex;
                if (mMotionPosition >= 0) {
                    motionIndex = mMotionPosition - mFirstPosition;
                } else {
                    // If we don't have a motion position that we can reliably track,
                    // pick something in the middle to make a best guess at things below.
                    motionIndex = getChildCount() / 2;
                }

                int motionViewPrevTop = 0;
                View motionView = this.getChildAt(motionIndex);
                if (motionView != null) {
                    motionViewPrevTop = motionView.getTop();
                }

                // No need to do all this work if we're not going to move anyway
                boolean atEdge = false;
                if (incrementalDeltaY != 0) {
                    atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
                }

                // Check to see if we have bumped into the scroll limit
                motionView = this.getChildAt(motionIndex);
                if (motionView != null) {
                    // Check if the top of the motion view is where it is
                    // supposed to be
                    final int motionViewRealTop = motionView.getTop();
                    if (atEdge) {
                        // Apply overscroll

                        int overscroll = -incrementalDeltaY -
                                (motionViewRealTop - motionViewPrevTop);
                        if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll,
                                mScrollOffset)) {
                            lastYCorrection -= mScrollOffset[1];
                            if (vtev != null) {
                                vtev.offsetLocation(0, mScrollOffset[1]);
                                mNestedYOffset += mScrollOffset[1];
                            }
                        } else {
                            final boolean atOverscrollEdge = overScrollBy(0, overscroll,
                                    0, mScrollY, 0, 0, 0, mOverscrollDistance, true);

                            if (atOverscrollEdge && mVelocityTracker != null) {
                                // Don't allow overfling if we're at the edge
                                mVelocityTracker.clear();
                            }

                            final int overscrollMode = getOverScrollMode();
                            if (overscrollMode == OVER_SCROLL_ALWAYS ||
                                    (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
                                            !contentFits())) {
                                if (!atOverscrollEdge) {
                                    mDirection = 0; // Reset when entering overscroll.
                                    mTouchMode = TOUCH_MODE_OVERSCROLL;
                                }
                                if (incrementalDeltaY > 0) {
                                    mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
                                            (float) x / getWidth());
                                    if (!mEdgeGlowBottom.isFinished()) {
                                        mEdgeGlowBottom.onRelease();
                                    }
                                    invalidateTopGlow();
                                } else if (incrementalDeltaY < 0) {
                                    mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
                                            1.f - (float) x / getWidth());
                                    if (!mEdgeGlowTop.isFinished()) {
                                        mEdgeGlowTop.onRelease();
                                    }
                                    invalidateBottomGlow();
                                }
                            }
                        }
                    }
                    mMotionY = y + lastYCorrection + scrollOffsetCorrection;
                }
                mLastY = y + lastYCorrection + scrollOffsetCorrection;
            }
        } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
            if (y != mLastY) {
                final int oldScroll = mScrollY;
                final int newScroll = oldScroll - incrementalDeltaY;
                int newDirection = y > mLastY ? 1 : -1;

                if (mDirection == 0) {
                    mDirection = newDirection;
                }

                int overScrollDistance = -incrementalDeltaY;
                if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
                    overScrollDistance = -oldScroll;
                    incrementalDeltaY += overScrollDistance;
                } else {
                    incrementalDeltaY = 0;
                }

                if (overScrollDistance != 0) {
                    overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
                            0, mOverscrollDistance, true);
                    final int overscrollMode = getOverScrollMode();
                    if (overscrollMode == OVER_SCROLL_ALWAYS ||
                            (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
                                    !contentFits())) {
                        if (rawDeltaY > 0) {
                            mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),
                                    (float) x / getWidth());
                            if (!mEdgeGlowBottom.isFinished()) {
                                mEdgeGlowBottom.onRelease();
                            }
                            invalidateTopGlow();
                        } else if (rawDeltaY < 0) {
                            mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
                                    1.f - (float) x / getWidth());
                            if (!mEdgeGlowTop.isFinished()) {
                                mEdgeGlowTop.onRelease();
                            }
                            invalidateBottomGlow();
                        }
                    }
                }

                if (incrementalDeltaY != 0) {
                    // Coming back to 'real' list scrolling
                    if (mScrollY != 0) {
                        mScrollY = 0;
                        invalidateParentIfNeeded();
                    }

                    trackMotionScroll(incrementalDeltaY, incrementalDeltaY);

                    mTouchMode = TOUCH_MODE_SCROLL;

                    // We did not scroll the full amount. Treat this essentially like the
                    // start of a new touch scroll
                    final int motionPosition = findClosestMotionRow(y);

                    mMotionCorrection = 0;
                    View motionView = getChildAt(motionPosition - mFirstPosition);
                    mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
                    mMotionY =  y + scrollOffsetCorrection;
                    mMotionPosition = motionPosition;
                }
                mLastY = y + lastYCorrection + scrollOffsetCorrection;
                mDirection = newDirection;
            }
        }
    }

然後在走向下面的方法

boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
        final int childCount = getChildCount();
        if (childCount == 0) {
            return true;
        }

        final int firstTop = getChildAt(0).getTop();
        final int lastBottom = getChildAt(childCount - 1).getBottom();

        final Rect listPadding = mListPadding;

        // "effective padding" In this case is the amount of padding that affects
        // how much space should not be filled by items. If we don't clip to padding
        // there is no effective padding.
        int effectivePaddingTop = 0;
        int effectivePaddingBottom = 0;
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            effectivePaddingTop = listPadding.top;
            effectivePaddingBottom = listPadding.bottom;
        }

         // FIXME account for grid vertical spacing too?
        final int spaceAbove = effectivePaddingTop - firstTop;
        final int end = getHeight() - effectivePaddingBottom;
        final int spaceBelow = lastBottom - end;

        final int height = getHeight() - mPaddingBottom - mPaddingTop;
        if (deltaY < 0) {
            deltaY = Math.max(-(height - 1), deltaY);
        } else {
            deltaY = Math.min(height - 1, deltaY);
        }

        if (incrementalDeltaY < 0) {
            incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
        } else {
            incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
        }

        final int firstPosition = mFirstPosition;

        // Update our guesses for where the first and last views are
        if (firstPosition == 0) {
            mFirstPositionDistanceGuess = firstTop - listPadding.top;
        } else {
            mFirstPositionDistanceGuess += incrementalDeltaY;
        }
        if (firstPosition + childCount == mItemCount) {
            mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
        } else {
            mLastPositionDistanceGuess += incrementalDeltaY;
        }

        final boolean cannotScrollDown = (firstPosition == 0 &&
                firstTop >= listPadding.top && incrementalDeltaY >= 0);
        final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
                lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);

        if (cannotScrollDown || cannotScrollUp) {
            return incrementalDeltaY != 0;
        }

        final boolean down = incrementalDeltaY < 0;

        final boolean inTouchMode = isInTouchMode();
        if (inTouchMode) {
            hideSelector();
        }

        final int headerViewsCount = getHeaderViewsCount();
        final int footerViewsStart = mItemCount - getFooterViewsCount();

        int start = 0;
        int count = 0;

        if (down) {
            int top = -incrementalDeltaY;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                top += listPadding.top;
            }
            for (int i = 0; i < childCount; i++) {
                final View child = getChildAt(i);
                if (child.getBottom() >= top) {
                    break;
                } else {
                    count++;
                    int position = firstPosition + i;
                    if (position >= headerViewsCount && position < footerViewsStart) {
                        // The view will be rebound to new data, clear any
                        // system-managed transient state.
                        child.clearAccessibilityFocus();
                        mRecycler.addScrapView(child, position);
                    }
                }
            }
        } else {
            int bottom = getHeight() - incrementalDeltaY;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                bottom -= listPadding.bottom;
            }
            for (int i = childCount - 1; i >= 0; i--) {
                final View child = getChildAt(i);
                if (child.getTop() <= bottom) {
                    break;
                } else {
                    start = i;
                    count++;
                    int position = firstPosition + i;
                    if (position >= headerViewsCount && position < footerViewsStart) {
                        // The view will be rebound to new data, clear any
                        // system-managed transient state.
                        child.clearAccessibilityFocus();
                        mRecycler.addScrapView(child, position);
                    }
                }
            }
        }

        mMotionViewNewTop = mMotionViewOriginalTop + deltaY;

        mBlockLayoutRequests = true;

        if (count > 0) {
            detachViewsFromParent(start, count);
            mRecycler.removeSkippedScrap();
        }

        // invalidate before moving the children to avoid unnecessary invalidate
        // calls to bubble up from the children all the way to the top
        if (!awakenScrollBars()) {
           invalidate();
        }

        offsetChildrenTopAndBottom(incrementalDeltaY);

        if (down) {
            mFirstPosition += count;
        }

        final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
        if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
            fillGap(down);
        }

        mRecycler.fullyDetachScrapViews();
        boolean selectorOnScreen = false;
        if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
            final int childIndex = mSelectedPosition - mFirstPosition;
            if (childIndex >= 0 && childIndex < getChildCount()) {
                positionSelector(mSelectedPosition, getChildAt(childIndex));
                selectorOnScreen = true;
            }
        } else if (mSelectorPosition != INVALID_POSITION) {
            final int childIndex = mSelectorPosition - mFirstPosition;
            if (childIndex >= 0 && childIndex < getChildCount()) {
                positionSelector(mSelectorPosition, getChildAt(childIndex));
                selectorOnScreen = true;
            }
        }
        if (!selectorOnScreen) {
            mSelectorRect.setEmpty();
        }

        mBlockLayoutRequests = false;

        invokeOnItemScrollListener();

        return false;
    }

這個方法接收兩個參數,deltaY表示從手指按下時的位置到當前手指位置的距離,incrementalDeltaY則表示據上次觸發event事件手指在Y方向上位置的改變量,那麼其實我們就可以通過incrementalDeltaY的正負值情況來判斷用戶是向上還是向下滑動的了。

如看代碼:

     if (incrementalDeltaY < 0) {
            incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
        } else {
            incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
        }

,如果incrementalDeltaY小於0,說明是向下滑動,否則就是向上滑動。

     final boolean down = incrementalDeltaY < 0;

進行判斷:
下面將會進行一個邊界值檢測的過程,可以看到,從上面判斷開始,當ListView向下滑動的時候,就會進入一個for循環當中,從上往下依次獲取子View,第47行當中,如果該子View的bottom值已經小於top值了,就說明這個子View已經移出屏幕了,所以會調用RecycleBin的addScrapView()方法將這個View加入到廢棄緩存當中,並將count計數器加1,計數器用於記錄有多少個子View被移出了屏幕。那麼如果是ListView向上滑動的話,其實過程是基本相同的,只不過變成了從下往上依次獲取子View,然後判斷該子View的top值是不是大於bottom值了,如果大於的話說明子View已經移出了屏幕,同樣把它加入到廢棄緩存中,並將計數器加1。

接下來在

if (count > 0) {
            detachViewsFromParent(start, count);
            mRecycler.removeSkippedScrap();
        }

,會根據當前計數器的值來進行一個detach操作,它的作用就是把所有移出屏幕的子View全部detach掉,在ListView的概念當中,所有看不到的View就沒有必要爲它進行保存,因爲屏幕外還有成百上千條數據等着顯示呢,一個好的回收策略才能保證ListView的高性能和高效率。

offsetChildrenTopAndBottom(incrementalDeltaY);

這個方法,並將incrementalDeltaY作爲參數傳入,這個方法的作用是讓ListView中所有的子View都按照傳入的參數值進行相應的偏移,這樣就實現了隨着手指的拖動,ListView的內容也會隨着滾動的效果。

 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
            fillGap(down);
        }

然後在進行判斷,如果ListView中最後一個View的底部已經移入了屏幕,或者ListView中第一個View的頂部移入了屏幕,就會調用fillGap()方法,那麼因此我們就可以猜出fillGap()方法是用來加載屏幕外數據的,進入到這個方法中瞧一瞧,如下所示:

void fillGap(boolean down) {
        final int count = getChildCount();
        if (down) {
            int paddingTop = 0;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                paddingTop = getListPaddingTop();
            }
            final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :
                    paddingTop;
            fillDown(mFirstPosition + count, startOffset);
            correctTooHigh(getChildCount());
        } else {
            int paddingBottom = 0;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                paddingBottom = getListPaddingBottom();
            }
            final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :
                    getHeight() - paddingBottom;
            fillUp(mFirstPosition - 1, startOffset);
            correctTooLow(getChildCount());
        }
    }

down參數用於表示ListView是向下滑動還是向上滑動的,可以看到,如果是向下滑動的話就會調用fillDown()方法,而如果是向上滑動的話就會調用fillUp()方法。那麼這兩個方法我們都已經非常熟悉了,內部都是通過一個循環來去對ListView進行填充,所以這兩個方法我們就不看了,但是填充ListView會通過調用makeAndAddView()方法來完成,又是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;
    }

不管怎麼說,這裏首先仍然是會嘗試調用RecycleBin的getActiveView()方法來獲取子佈局,只不過肯定是獲取不到的了,因爲在第二次Layout過程中我們已經從mActiveViews中獲取過了數據,而根據RecycleBin的機制,mActiveViews是不能夠重複利用的,因此這裏返回的值肯定是null。
所以接下來應該會走

final View child = obtainView(position, mIsScrap);

obtainView(position, mIsScrap)方法中

      final View scrapView = mRecycler.getScrapView(position);
      final View child = mAdapter.getView(position, scrapView, this);
View obtainView(int position, boolean[] outMetadata) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
        outMetadata[0] = false;
        final View transientView = mRecycler.getTransientStateView(position);
        if (transientView != null) {
            final LayoutParams params = (LayoutParams) transientView.getLayoutParams();
            if (params.viewType == mAdapter.getItemViewType(position)) {
                final View updatedView = mAdapter.getView(position, transientView, this);
                if (updatedView != transientView) {
                    setItemViewLayoutParams(updatedView, position);
                    mRecycler.addScrapView(updatedView, position);
                }
            }
            outMetadata[0] = true;
            transientView.dispatchFinishTemporaryDetach();
            return transientView;
        }
        final View scrapView = mRecycler.getScrapView(position);
        final View child = mAdapter.getView(position, scrapView, this);
        return child;
    }

這裏在調用RecyleBin的getScrapView()方法來嘗試從廢棄緩存中獲取一個View,那麼廢棄緩存有沒有View呢?當然有,因爲剛纔在trackMotionScroll()方法中我們就已經看到了,一旦有任何子View被移出了屏幕,就會將它加入到廢棄緩存中,而從obtainView()方法中的邏輯來看,一旦有新的數據需要顯示到屏幕上,就會嘗試從廢棄緩存中獲取View。所以它們之間就形成了一個生產者和消費者的模式,那麼ListView神奇的地方也就在這裏體現出來了,不管你有任意多條數據需要顯示,ListView中的子View其實來來回回就那麼幾個,移出屏幕的子View會很快被移入屏幕的數據重新利用起來,因而不管我們加載多少數據都不會出現OOM的情況,甚至內存都不會有所增加。

那麼另外還有一點是需要大家留意的,這裏獲取到了一個scrapView,然後我們將它作爲第二個參數傳入到了Adapter的getView()方法當中。那麼第二個參數是什麼意思呢?我們再次看一下一個簡單的getView()方法示例:

public View getView(int position, View convertView, ViewGroup parent) {
    Fruit fruit = getItem(position);
    View view;
    if (convertView == null) {
        view = LayoutInflater.from(getContext()).inflate(resourceId, null);
    } else {
        view = convertView;
    }
    return view;
}

第二個參數就是我們最熟悉的convertView呀,難怪平時我們在寫getView()方法是要判斷一下convertView是不是等於null,如果等於null才調用inflate()方法來加載佈局,不等於null就可以直接利用convertView,因爲convertView就是我們之前用過的View,只不過被移出屏幕後進入到了廢棄緩存中,現在又重新拿出來使用而已。然後我們只需要把convertView中的數據更新成當前位置上應該顯示的數據,那麼看起來就好像是全新加載出來的一個佈局一樣,這背後的道理你是不是已經完全搞明白了?

之後的代碼又都是我們熟悉的流程了,從緩存中拿到子View之後再調用setupChild()方法將它重新attach到ListView當中,因爲緩存中的View也是之前從ListView中detach掉的,這部分代碼就不再重複進行分析了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章