美文网首页Android开发经验谈Android技术知识程序员
Android ListView缓存复用原理 源码解析

Android ListView缓存复用原理 源码解析

作者: MrLgc | 来源:发表于2019-04-24 18:15 被阅读25次

    我先解释下,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掉的,这部分代码就不再重复进行分析了。

    相关文章

      网友评论

        本文标题:Android ListView缓存复用原理 源码解析

        本文链接:https://www.haomeiwen.com/subject/qtcggqtx.html