ListView 源码分析(三)

在上一篇文章中,我们提到过ListView是通过makeAndAddView来填充子view的。而这就涉及到了ListView最重要的View复用机制。这个复用机制是通过RecycleBin来实现的。
一、RecycleBin
RecycleBin是AbsListView的一个内部类。作用是来复用AbsListView的子view, 它就像一个cell view的池子,来给AbsListView提供可复用的cell view.
实际上,RecycleBin包含了四种cell view的缓存池。
1.private View[] mActiveViews,存储的cell view可以直接使用,不需要再绑定数据。
2.private ArrayList[] mScrapViews,存储的cell view是被废弃的,在使用前需要重新绑定数据,但是不需要重新创建view,就是我们在getView中的convertView参数。
3.private SparseArray mTransientStateViews,存储临时状态的cell view, 处于动画状态中的view具有临时状态。
4.private ArrayList mSkippedScrap; 保存了需要真正从RecycleBin的池子中删除的cell view.

RecycleBin主要使用的是mActiveViews和mScrapViews两个池子。在开始布局的时候,如果ListView的数据没有发生改变,直接把所有的cell view添加到mActiveViews中,如果数据发生了改变,就把所有的cell view添加到mScrapViews中。然后ListView开始布局,如果数据没有改变,就从mActiveViews中重用视图,如果改变了,就从mScrapViews中重用。布局结束后,检查mActiveViews中是否还有cell view,如果有就把他们全部添加到mScrapViews。

mActiveViews和mScrapViews都非常重要,他们的差别在于,mScrapViews需要重新调用adapter的getView来对cell view和item数据进行绑定,而mActiveViews的cell view可以直接使用。

1.测量过程中调用方法
ListView在进行测量时,会获取一个cell view的宽度和高度,这个获取的方法需要RecycleBin的配合,从池子中得一个view,在使用之后还需要把它放回到相应的位置。

从RecycleBin中获取cell view时,如果得到的是null,会通过getView来得到一个cell view. 使用完成后会把它放到RecycleBin中。

获取和放回都是由Recycle的getScrapView和addScrapView来完成的。

getScrapView

在测量时获取cell view, 会通过getScrapView来得到,如果mScrapViews中没有对应的cell view,返回null.

mScrapViews是List的数组,可以区分不同的type。每种类型是数组的一个元素,是一个cell view的List.

getScrapView会根据对应的位置来得到对应的类型,然后根据类型从mScrapViews中取得一个cell view返回。

        /**
         * @return A view from the ScrapViews collection. These are unordered.
         */
        View getScrapView(int position) {
            if (mViewTypeCount == 1) {
                
                return retrieveFromScrap(mCurrentScrap, position);
            } else {
                int whichScrap = mAdapter.getItemViewType(position);
                if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
                    
                    return retrieveFromScrap(mScrapViews[whichScrap], position);
                }
            }
            return null;
        }
    static View retrieveFromScrap(ArrayList scrapViews, int position) {
        int size = scrapViews.size();
        if (size > 0) {
            // See if we still have a view for this position.
            for (int i=0; i
                View view = scrapViews.get(i);
                if (((AbsListView.LayoutParams)view.getLayoutParams())
                        .scrappedFromPosition == position) {
                    scrapViews.remove(i);
                    return view;
                }
            }
            
            return scrapViews.remove(size - 1);
        } else {
            return null;
        }
    }

addScrapView

addScrapView是把cell view添加到mScrapViews中,这些cell view可以是显示状态、临时状态或者废弃状态三种类型。如果cell view是临时状态,会直接放入到对应的mTransientStateViews或mSkippedScrap中。

        void addScrapView(View scrap, int position) {
            AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
            if (lp == null) {
                
                return;
            }

            lp.scrappedFromPosition = position;
            
            // Don't put header or footer views or views that should be ignored
            // into the scrap heap
            int viewType = lp.viewType;
            final boolean scrapHasTransientState = scrap.hasTransientState();
            if (!shouldRecycleViewType(viewType) || scrapHasTransientState) {
                
                if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER || scrapHasTransientState) {
                    
                    if (mSkippedScrap == null) {
                        mSkippedScrap = new ArrayList();
                    }
                    mSkippedScrap.add(scrap);
                }
                if (scrapHasTransientState) {
                    
                    if (mTransientStateViews == null) {
                        mTransientStateViews = new SparseArray();
                    }
                    scrap.dispatchStartTemporaryDetach();
                    mTransientStateViews.put(position, scrap);
                }
                return;
            }

            scrap.dispatchStartTemporaryDetach();
            
            if (mViewTypeCount == 1) {
                mCurrentScrap.add(scrap);
            } else {
                mScrapViews[viewType].add(scrap);
            }

            scrap.setAccessibilityDelegate(null);
            
            if (mRecyclerListener != null) {
                mRecyclerListener.onMovedToScrapHeap(scrap);
            }
        }

2.布局前调用的方法
主要是layoutChildren方法。
布局时,先判断ListView的数据是否改变,如果改变,调用addScrapView把所有的cell view全部加入到mScrapViews,这一步也是cell view的显示状态转化为废弃状态。

如果数据没有发生变化。调用fillActiveViews把所有的cell view全部加入到mActiveViews中,这一步是cell view显示状态转化为活跃状态。

从以上过程也可以得出mActiveViews和mScrapViews的区别就是在于是否需要再次绑定数据。

在完成添加操作之后,会调用removeSkippedScrap()来把mSkippedScrap中的所有cell view清空。

        void fillActiveViews(int childCount, int firstActivePosition) {
            if (mActiveViews.length < childCount) {
                
                mActiveViews = new View[childCount];
            }
            mFirstActivePosition = firstActivePosition;

            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;
                }
            }
        }
  1. 布局中调用makeAndAddView方法
    makeAndAddView首先判断ListView的数据是否发生了改变,如果没有改变,直接mActiveViews转化为显示视图。调用getActiveView方法取得对应的cell view 即可。

如果数据发生了变化,会调用adapter的getView方法,其中需要去对应的mScrapViews中找到缓存的cell view传入作为getView的参数。

  1. 布局后调用方法

布局之前把所有显示状态的cell view加入到mActiveViews或者mScrapViews中。布局中,从重用视图池中获取相应的cell view转化成显示的cell view. 布局之后,主要来处理mActiveViews中没有使用的cell view,因为这次布局之后,这些余下的cell view没有被重用,所以把它们转化为废弃状态,使用的方法是scrapActiveViews().

        void scrapActiveViews() {
            final View[] activeViews = mActiveViews;
            final boolean hasListener = mRecyclerListener != null;
            final boolean multipleScraps = mViewTypeCount > 1;

            ArrayList 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();
                    int whichScrap = lp.viewType;
                    
                    activeViews[i] = null;

                    final boolean scrapHasTransientState = victim.hasTransientState();
                    if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState) {
                        
                        // Do not move views that should be ignored
                        if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER ||
                                scrapHasTransientState) {
                            
                            removeDetachedView(victim, false);
                        }
                        if (scrapHasTransientState) {
                            if (mTransientStateViews == null) {
                                mTransientStateViews = new SparseArray();
                            }
                            
                            mTransientStateViews.put(mFirstActivePosition + i, victim);
                        }
                        continue;
                    }
                    
                    if (multipleScraps) {
                        
                        scrapViews = mScrapViews[whichScrap];
                    }
                    victim.dispatchStartTemporaryDetach();
                    lp.scrappedFromPosition = mFirstActivePosition + i;
                    scrapViews.add(victim);

                    victim.setAccessibilityDelegate(null);
                    if (hasListener) {
                        mRecyclerListener.onMovedToScrapHeap(victim);
                    }
                }
            }

            pruneScrapViews();
        }

这个方法遍历mActiveViews,找到没有被重用的cell view。对于没有被重用的cell view,如果有临时状态,把cell view加入mTransientStateViews直接并从父视图中移除。如果不需要加回收就直接从父视图中移除。如果应该回收就添加到mScrapViews。

二、makeAndAddView流程
makeAndAddView先获取一个cell view,然后把它添加到ListView中。添加主要是通过setupChild方法来设置,而获取cell view需要根据数据有没有改变来判断,如果没有变化,直接从mActiveViews中取得,如果有变化需要从RecycleBin的mScrapViews或mTransientStateViews中取得。

    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        View child;


        if (!mDataChanged) {
            
            // Try to use an existing view for this position
            child = mRecycler.getActiveView(position);
            if (child != null) {
                // Found it -- we're using an existing child
                // This just needs to be positioned
                setupChild(child, position, y, flow, childrenLeft, selected, true);
                
                return child;
            }
        }

        // Make a new view for this position, or convert an unused view if possible
        
        child = obtainView(position, mIsScrap);

        // This needs to be positioned and measured
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

        return child;
    }

三、obtainView流程
如果在makeAndAddView中数据发生了变化,或者没有改变但是从mActiveViews中没有取到对应的cell view时,就需要使用obtainView。

    View obtainView(int position, boolean[] isScrap) {
        isScrap[0] = false;
        View scrapView;

        scrapView = mRecycler.getTransientStateView(position);
        if (scrapView != null) {
            
            return scrapView;
        }

        scrapView = mRecycler.getScrapView(position);

        View child;
        if (scrapView != null) {
        
            child = mAdapter.getView(position, scrapView, this);
            
            if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
            }

            if (child != scrapView) {
                mRecycler.addScrapView(scrapView, position);
                if (mCacheColorHint != 0) {
                    child.setDrawingCacheBackgroundColor(mCacheColorHint);
                }
            } else {
                isScrap[0] = true;
                child.dispatchFinishTemporaryDetach();
            }
        } else {
        
            child = mAdapter.getView(position, null, this);
            
            if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
            }

            if (mCacheColorHint != 0) {
                child.setDrawingCacheBackgroundColor(mCacheColorHint);
            }
        }

        if (mAdapterHasStableIds) {
            final ViewGroup.LayoutParams vlp = child.getLayoutParams();
            LayoutParams lp;
            if (vlp == null) {
                lp = (LayoutParams) generateDefaultLayoutParams();
            } else if (!checkLayoutParams(vlp)) {
                lp = (LayoutParams) generateLayoutParams(vlp);
            } else {
                lp = (LayoutParams) vlp;
            }
            lp.itemId = mAdapter.getItemId(position);
            child.setLayoutParams(lp);
        }

        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
            if (mAccessibilityDelegate == null) {
                mAccessibilityDelegate = new ListItemAccessibilityDelegate();
            }
            child.setAccessibilityDelegate(mAccessibilityDelegate);
        }

        return child;
    }

obtainView时,首先需要检查mTransientStateViews,如果有对应的cell view 直接返回。没有就去检查mScrapViews。然后调用adapter的getView方法,如果得到对应的view就使用getView来绑定数据,如果没有就生成一个新的cell view.

四、setupChild方法
setupChild主要是把cell view添加到ListView中,并放到正确的位置。

    private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
            boolean selected, boolean recycled) {
        final boolean isSelected = selected && shouldShowSelector();
        final boolean updateChildSelected = isSelected != child.isSelected();
        final int mode = mTouchMode;
        final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
                mMotionPosition == position;
        final boolean updateChildPressed = isPressed != child.isPressed();
        final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();

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

        if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&
                p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
            
            attachViewToParent(child, flowDown ? -1 : 0, p);
        } else {
            p.forceAdd = false;
            if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                p.recycledHeaderFooter = true;
            }
            
            addViewInLayout(child, flowDown ? -1 : 0, p, true);
        }

        if (updateChildSelected) {
            child.setSelected(isSelected);
        }

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

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

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

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

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

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

        if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
                != position) {
            child.jumpDrawablesToCurrentState();
        }
    }

简单来说,setupChild有三个步骤,添加子视图,测量子视图,布局了视图。测量和布局可能会会跳过。

首先,确定子视图是否被选择,是否更新选择状态,是否按下,是否更新按下状态。根据这四个状态来确定是否需要测量,如果这个cell view不是重用来的或者需要更新选择状态,或者更新按下状态,那么就需要重新测量。

如果cell view是重用的,调用attachViewToParent添加到ListView中。否则就调用addViewInLayout来添加。

总结一下就是ListView通过RecycleBin来实现cell view的缓存。缓存有四种状态,加上显示状态,这些状态大多都在layoutChildren中转化。

makeAndAddView主要有两个功能,一是生成一个cell view,二是把cell view添加到ListView中。

你可能感兴趣的:(ListView 源码分析(三))