listview 源码解析乱弹

安卓中listview源码分析是每个人绕不开的点。

listview 的复用机制是基于父类 absListview实现的。其工作中主要涉及ListView AbsListView RecycleBin AdapterView ViewGroup几个大类以及一个很重要的obtainview方法。

listview的实现的重头戏份是其静态加载和滑动加载两部分。静态加载即其首次加载,调用Listview的layoutChildren方法,而且其需要实现两次layoutChildren方法。

listview的静态加载

第一次layoutChildren

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

        mBlockLayoutRequests = true;

        try {
            super.layoutChildren();

            invalidate();

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

           ...省略非必要代码

            // Pull all children into the RecycleBin.
            // These views will be reused if possible
            final int firstPosition = mFirstPosition;
            final RecycleBin recycleBin = mRecycler;
            if (dataChanged) {
                for (int i = 0; i < childCount; i++) {
                    recycleBin.addScrapView(getChildAt(i), firstPosition+i);
                }
            } else {
                recycleBin.fillActiveViews(childCount, firstPosition);
            }

            // Clear out old views
            detachAllViewsFromParent();
            recycleBin.removeSkippedScrap();

            switch (mLayoutMode) {
            case LAYOUT_SET_SELECTION:
                if (newSel != null) {
                    sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
                } else {
                    sel = fillFromMiddle(childrenTop, childrenBottom);
                }
                break;
            case LAYOUT_SYNC:
                sel = fillSpecific(mSyncPosition, mSpecificTop);
                break;
            case LAYOUT_FORCE_BOTTOM:
                sel = fillUp(mItemCount - 1, childrenBottom);
                adjustViewsUpOrDown();
                break;
            case LAYOUT_FORCE_TOP:
                mFirstPosition = 0;
                sel = fillFromTop(childrenTop);
                adjustViewsUpOrDown();
                break;
            case LAYOUT_SPECIFIC:
                final int selectedPosition = reconcileSelectedPosition();
                sel = fillSpecific(selectedPosition, mSpecificTop);
                /**
                 * When ListView is resized, FocusSelector requests an async selection for the
                 * previously focused item to make sure it is still visible. If the item is not
                 * selectable, it won't regain focus so instead we call FocusSelector
                 * to directly request focus on the view after it is visible.
                 */
                if (sel == null && mFocusSelector != null) {
                    final Runnable focusRunnable = mFocusSelector
                            .setupFocusIfValid(selectedPosition);
                    if (focusRunnable != null) {
                        post(focusRunnable);
                    }
                }
                break;
            case LAYOUT_MOVE_SELECTION:
                sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
                break;
            default:
                if (childCount == 0) {
                    if (!mStackFromBottom) {
                        final int position = lookForSelectablePosition(0, true);
                        setSelectedPositionInt(position);
                        sel = fillFromTop(childrenTop);
                    } else {
                        final int position = lookForSelectablePosition(mItemCount - 1, false);
                        setSelectedPositionInt(position);
                        sel = fillUp(mItemCount - 1, childrenBottom);
                    }
                } else {
                    if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
                        sel = fillSpecific(mSelectedPosition,
                                oldSel == null ? childrenTop : oldSel.getTop());
                    } else if (mFirstPosition < mItemCount) {
                        sel = fillSpecific(mFirstPosition,
                                oldFirst == null ? childrenTop : oldFirst.getTop());
                    } else {
                        sel = fillSpecific(0, childrenTop);
                    }
                }
                break;
            }
           recycleBin.scrapActiveViews();
           ...省略非必要代码
               }

有以下几个重点事件:
1.recyclerBin的activeview也由recycleBin.fillActiveViews(childCount, firstPosition)赋值;但此时childCount=0,recyclerBin内的activeview也没有生成对应的值。

2.mLayoutMode的值是在AbsListview中赋值。其初始化值为 LAYOUT_NORMAL。由此我们可以看到在上面代码中的判断中关于case的各种第一次判断都不成立,自然走都到了对应的default中。

3.在default后,有个recycleBin.scrapActiveViews();这个是废弃activeViews的代码。会把activeview给废弃并移入scrapview中。这里要标注好省的遗忘。

回到代码的顺序中继续查看default。

default:
         if (childCount == 0) {
                    if (!mStackFromBottom) {
                        final int position = lookForSelectablePosition(0, true);
                        setSelectedPositionInt(position);
                        sel = fillFromTop(childrenTop);
                    } else {
                        final int position = lookForSelectablePosition(mItemCount - 1, false);
                        setSelectedPositionInt(position);
                        sel = fillUp(mItemCount - 1, childrenBottom);
                    }
                }

啥child都没生成,肯定是childCount == 0了,我们可以看到代码走到了这里,进入fillFromTop()方法中跟进。

private View fillFromTop(int nextTop) {
        mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
        mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
        if (mFirstPosition < 0) {
            mFirstPosition = 0;
        }
        return fillDown(mFirstPosition, nextTop);
    }

可以看到进行一个初始位置的判断后返回一个filldown的方法。该方法返回一个view,至此我们猜测listview的第一次加载便是通过该方法返回一个个对应的item的view。接下来让我们跟进

    private View fillDown(int pos, int nextTop) {
    View selectedView = null;

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

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

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

    setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    return selectedView;
}

我们可以看到这个地方进行了一次while循环,此时我们可以猜测其作用为通过循环判断加载屏幕中是否还有空间显示的view进而摆放view。而此view的来源便是通过makeAndAddView的方法获得的,跟入代码。

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

从中我们可以发现,有两种方式可以获取view。一、mRecycler.getActiveView(position)的方式获取一个view,二、obtainView(position, mIsScrap);的方式获取一个view。

由于我们是第一次layout 数据源未发生改变所以进入所以必然通过mRecycler.getActiveView(position);的方式获取一个view但此时activeview仅仅在第一次layoutChildren中赋值且为空值,所以get到的view是空。所以最终通过obtainView的方式获取view。

之后我们聚焦obtainView方法。

View obtainView(int position, boolean[] isScrap) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");

    isScrap[0] = false;

    // Check whether we have a transient state view. Attempt to re-bind the
    // data and discard the view if we fail.
    final View transientView = mRecycler.getTransientStateView(position);
    if (transientView != null) {
        final LayoutParams params = (LayoutParams) transientView.getLayoutParams();

        // If the view type hasn't changed, attempt to re-bind the data.
        if (params.viewType == mAdapter.getItemViewType(position)) {
            final View updatedView = mAdapter.getView(position, transientView, this);

            // If we failed to re-bind the data, scrap the obtained view.
            if (updatedView != transientView) {
                setItemViewLayoutParams(updatedView, position);
                mRecycler.addScrapView(updatedView, position);
            }
        }

        isScrap[0] = true;

        // Finish the temporary detach started in addScrapView().
        transientView.dispatchFinishTemporaryDetach();
        return transientView;
    }

    final View scrapView = mRecycler.getScrapView(position);
    final View child = mAdapter.getView(position, scrapView, this);
    if (scrapView != null) {
        if (child != scrapView) {
            // Failed to re-bind the data, return scrap to the heap.
            mRecycler.addScrapView(scrapView, position);
        } else {
            if (child.isTemporarilyDetached()) {
                isScrap[0] = true;

                // Finish the temporary detach started in addScrapView().
                child.dispatchFinishTemporaryDetach();
            } else {
                // we set isScrap to "true" only if the view is temporarily detached.
                // if the view is fully detached, it is as good as a view created by the
                // adapter
                isScrap[0] = false;
            }

        }
    }
  ...省略非必要代码

    return child;
}

//这个地方是个大重点啊。但逻辑涉及太多在此仅仅给出最易懂的view解释,保证代码的顺利进行:

可见此方法返回一个view对象,返回transientView或者child。但是此时mRecycler.getTransientStateView(position)为空值,

//具体原因请跟进mRecycler.getScrapView(position)。后面代码中详解。

而且第一次加载的时候addScrapView和scrapActiveViews都没有来得及调用。因此transientView也是个空。回到obtainView方法内。
其返回的view便只能通过adapter的getview的方式来返回。由此child来源解释清楚。回头再看一眼makeAndAddView方法,有一个setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
boolean selected, boolean recycled)的方法。跟进

 /**
 * Add a view as a child and make sure it is measured (if necessary) and
 * positioned properly.
 */ 

 private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
   ...省略非必要代码
    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);
    }
     ...省略非必要代码

}

而此方法的作用也入注释中所讲一样,再次测量以用于保证view的位置正确。
其中 addViewInLayout(child, flowDown ? -1 : 0, p, true);此方法即为将第一次的layoutChildren的item添加至listview,

此时layoutChildren 走到了 recycleBin.scrapActiveViews();跟进其中

  /**
     * 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
   
     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
    
     (); } long id = mAdapter.getItemId(mFirstActivePosition + i); mTransientStateViewsById.put(id, victim); } else if (!mDataChanged) { if (mTransientStateViews == null) { mTransientStateViews = new SparseArray
     
      (); } 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(); }
     
    
   

方法的主要功能在备注中表明将mActiveViews废弃到mScrapViews中。但是这里有个非常重要的赋值操作mTransientStateViews.put(mFirstActivePosition + i, victim);和mTransientStateViewsById.put(id, victim);而瞬态的来源是view中hasTransientState方法,其方法作用看源码解释就行,item中如果有view正在进行动画之类的动态改变发生。
此item就被放在了上面的两个集合中。

/**
 * Indicates whether the view is currently tracking transient state that the
 * app should not need to concern itself with saving and restoring, but that
 * the framework should take special note to preserve when possible.
 *
 * 

A view with transient state cannot be trivially rebound from an external * data source, such as an adapter binding item views in a list. This may be * because the view is performing an animation, tracking user selection * of content, or similar.

* * @return true if the view has transient state */

至此第一次layoutchildern 结束

第二次layoutChildren

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

    mBlockLayoutRequests = true;

    try {
        super.layoutChildren();

        invalidate();

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

        ...省略非必要代码

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

        final RecycleBin recycleBin = mRecycler;
        if (dataChanged) {
            for (int i = 0; i < childCount; i++) {
                recycleBin.addScrapView(getChildAt(i), firstPosition+i);
            }
        } else {
            recycleBin.fillActiveViews(childCount, firstPosition);
        }

        // Clear out old views
        detachAllViewsFromParent();
        recycleBin.removeSkippedScrap();

        switch (mLayoutMode) {
        case LAYOUT_SET_SELECTION:
            if (newSel != null) {
                sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
            } else {
                sel = fillFromMiddle(childrenTop, childrenBottom);
            }
            break;
        case LAYOUT_SYNC:
            sel = fillSpecific(mSyncPosition, mSpecificTop);
            break;
        case LAYOUT_FORCE_BOTTOM:
            sel = fillUp(mItemCount - 1, childrenBottom);
            adjustViewsUpOrDown();
            break;
        case LAYOUT_FORCE_TOP:
            mFirstPosition = 0;
            sel = fillFromTop(childrenTop);
            adjustViewsUpOrDown();
            break;
        case LAYOUT_SPECIFIC:
            final int selectedPosition = reconcileSelectedPosition();
            sel = fillSpecific(selectedPosition, mSpecificTop);
            /**
             * When ListView is resized, FocusSelector requests an async selection for the
             * previously focused item to make sure it is still visible. If the item is not
             * selectable, it won't regain focus so instead we call FocusSelector
             * to directly request focus on the view after it is visible.
             */
            if (sel == null && mFocusSelector != null) {
                final Runnable focusRunnable = mFocusSelector
                        .setupFocusIfValid(selectedPosition);
                if (focusRunnable != null) {
                    post(focusRunnable);
                }
            }
            break;
        case LAYOUT_MOVE_SELECTION:
            sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
            break;
        default:
            if (childCount == 0) {
                if (!mStackFromBottom) {
                    final int position = lookForSelectablePosition(0, true);
                    setSelectedPositionInt(position);
                    sel = fillFromTop(childrenTop);
                } else {
                    final int position = lookForSelectablePosition(mItemCount - 1, false);
                    setSelectedPositionInt(position);
                    sel = fillUp(mItemCount - 1, childrenBottom);
                }
            } else {
                if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
                    sel = fillSpecific(mSelectedPosition,
                            oldSel == null ? childrenTop : oldSel.getTop());
                } else if (mFirstPosition < mItemCount) {
                    sel = fillSpecific(mFirstPosition,
                            oldFirst == null ? childrenTop : oldFirst.getTop());
                } else {
                    sel = fillSpecific(0, childrenTop);
                }
            }
            break;
        }
}

此时我们首先确定AbsListView中的mLayoutMode还是LAYOUT_NORMAL。
之后我们判断了dataChanged是否改变如果改变则recycleBin.addScrapView(getChildAt(i), firstPosition+i);否则 recycleBin.fillActiveViews(childCount, firstPosition);跟进fillActiveViews。

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

我们发现此方法的作用便是给activeview添加数据。将第一次加载的view放在了activeViews中。

之后回到layoutchildren中发现其调用
detachAllViewsFromParent();
recycleBin.removeSkippedScrap();//将子view和listview给detach掉。

 void removeSkippedScrap() {
        if (mSkippedScrap == null) {
            return;
        }
        final int count = mSkippedScrap.size();
        for (int i = 0; i < count; i++) {
            removeDetachedView(mSkippedScrap.get(i), false);
        }
        mSkippedScrap.clear();
    }

主要mSkippedScrap.clear();

再次回到layoutChildren 此时又判断了一次childCount是否==0此时。此判断为false 进入fillSpecific方法。跟进。

  private View fillSpecific(int position, int top) {
    boolean tempIsSelected = position == mSelectedPosition;
    View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);
    // Possibly changed again in fillUp if we add rows above this one.
    mFirstPosition = position;

    View above;
    View below;

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

  。。省略非重要代码
}

below为底部的item, above为头部item。
此时可见fillSpecific()方法会优先将指定位置的子View先加载到屏幕上,然后再加载该子View往上以及往下的其它子View。
另外,又见老朋友makeAndAddView 。再次跟进

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

此时 mDataChanged未改变,child = mRecycler.getActiveView(position); 跟进

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

我们发现mActiveViews不为空而且在第二次layout的时候其被赋值。将activeview的对应item给置空,也说明了activeview仅仅只能使用一次。

回到makeAndAddView中再次调用setupChild重新测量及摆放view。至此第二次加载结束。

最后重点讲解一个方法obtainView

View obtainView(int position, boolean[] isScrap) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");

    isScrap[0] = false;

    // Check whether we have a transient state view. Attempt to re-bind the
    // data and discard the view if we fail.
    final View transientView = mRecycler.getTransientStateView(position);
    if (transientView != null) {
        final LayoutParams params = (LayoutParams) transientView.getLayoutParams();

        // If the view type hasn't changed, attempt to re-bind the data.
        if (params.viewType == mAdapter.getItemViewType(position)) {
            final View updatedView = mAdapter.getView(position, transientView, this);

            // If we failed to re-bind the data, scrap the obtained view.
            if (updatedView != transientView) {
                setItemViewLayoutParams(updatedView, position);
                mRecycler.addScrapView(updatedView, position);
            }
        }

        isScrap[0] = true;

        // Finish the temporary detach started in addScrapView().
        transientView.dispatchFinishTemporaryDetach();
        return transientView;
    }

    final View scrapView = mRecycler.getScrapView(position);
    final View child = mAdapter.getView(position, scrapView, this);
    if (scrapView != null) {
        if (child != scrapView) {
            // Failed to re-bind the data, return scrap to the heap.
            mRecycler.addScrapView(scrapView, position);
        } else {
            if (child.isTemporarilyDetached()) {
                isScrap[0] = true;

                // Finish the temporary detach started in addScrapView().
                child.dispatchFinishTemporaryDetach();
            } else {
                // we set isScrap to "true" only if the view is temporarily detached.
                // if the view is fully detached, it is as good as a view created by the
                // adapter
                isScrap[0] = false;
            }

        }
    }
  ...省略非必要代码

    return child;
}

final View transientView = mRecycler.getTransientStateView(position);这句话是一个重难点。
先让我们跟进其中

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

此时我们发现transientView的来源是mRecycler.getTransientStateView,此时我们发现返回的对象是mTransientStateViewsById或者mTransientStateViews中的对应的数值,而两个数据集合的来源是

mTransientStateViewsById的来源是scrapActiveViews

if (mAdapter != null && mAdapterHasStableIds) {
                        if (mTransientStateViewsById == null) {
                            mTransientStateViewsById = new LongSparseArray
   
    (); } long id = mAdapter.getItemId(mFirstActivePosition + i); mTransientStateViewsById.put(id, victim); }
   

以及addScrapView

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

mTransientStateViews的来源是addScrapView

    else if (!mDataChanged) {
                        if (mTransientStateViews == null) {
                            mTransientStateViews = new SparseArray
   
    (); } mTransientStateViews.put(mFirstActivePosition + i, victim); }
   

完整的addScrapView

   void addScrapView(View scrap, int position) {
        final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
        if (lp == null) {
            // Can't recycle, but we don't know anything about the view.
            // Ignore it completely.
            return;
        }

        lp.scrappedFromPosition = position;

        // Remove but don't scrap header or footer views, or views that
        // should otherwise not be recycled.
        final int viewType = lp.viewType;
        if (!shouldRecycleViewType(viewType)) {
            // Can't recycle. If it's not a header or footer, which have
            // special handling and should be ignored, then skip the scrap
            // heap and we'll fully detach the view later.
            if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                getSkippedScrap().add(scrap);
            }
            return;
        }

        scrap.dispatchStartTemporaryDetach();

        // The the accessibility state of the view may change while temporary
        // detached and we do not allow detached views to fire accessibility
        // events. So we are announcing that the subtree changed giving a chance
        // to clients holding on to a view in this subtree to refresh it.
        notifyViewAccessibilityStateChangedIfNeeded(
                AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);

        // Don't scrap views that have transient state.
        final boolean scrapHasTransientState = scrap.hasTransientState();
        if (scrapHasTransientState) {
            if (mAdapter != null && mAdapterHasStableIds) {
                // If the adapter has stable IDs, we can reuse the view for
                // the same data.
                if (mTransientStateViewsById == null) {
                    mTransientStateViewsById = new LongSparseArray<>();
                }
                mTransientStateViewsById.put(lp.itemId, scrap);
            } else if (!mDataChanged) {
                // If the data hasn't changed, we can reuse the views at
                // their old positions.
                if (mTransientStateViews == null) {
                    mTransientStateViews = new SparseArray<>();
                }
                mTransientStateViews.put(position, scrap);
            } else {
                // Otherwise, we'll have to remove the view and start over.
                getSkippedScrap().add(scrap);
            }
        } else {
            if (mViewTypeCount == 1) {
                mCurrentScrap.add(scrap);
            } else {
                mScrapViews[viewType].add(scrap);
            }

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

关于mTransientStateViewsById或者mTransientStateViews这两个的集合代码中有英文解释,

mTransientStateViews: If the data hasn’t changed, we can reuse the views at their old positions.

而mTransientStateViewsById: If the adapter has stable IDs,we can reuse the view forthe same data.

我们可以发现数据的添加依据是scrap.hasTransientState()跟入

 /**
 * Indicates whether the view is currently tracking transient state that the
 * app should not need to concern itself with saving and restoring, but that
 * the framework should take special note to preserve when possible.
 *
 * 

A view with transient state cannot be trivially rebound from an external * data source, such as an adapter binding item views in a list. This may be * because the view is performing an animation, tracking user selection * of content, or similar.

* * @return true if the view has transient state */ @ViewDebug.ExportedProperty(category = "layout") public boolean hasTransientState() { return (mPrivateFlags2 & PFLAG2_HAS_TRANSIENT_STATE) == PFLAG2_HAS_TRANSIENT_STATE; }

我们发现他是在view中的方法,通过注解我们也可以认为是view的一个基本属性,该属性的作用就是标注当前view是否正在变化中。
所以我们可以这么理解,如果我的某个item是在变化的时候,我希望我的listview对应的item不应该是静态的,返回的是我那个正在发生变化的状态。

现在我们再次回到obtainView中代码进行到final View updatedView = mAdapter.getView(position, transientView, this);此处就是生成一个静态的,并没有发生任何动态改变和展现任何正在发生中的动画的item。if (updatedView != transientView),mRecycler.addScrapView(updatedView, position);scrapView此时存放的应是静态的view,毕竟再次重复使用的时候它刚加载成功是静止状态的。

一会发一篇listview的滑动,及数据加载部分源码分析文章。

    原文作者:Android源码分析
    原文地址: https://juejin.im/entry/58413cbb61ff4b006b8b0eb8
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞