ListView的刷新与复用

来源:互联网 发布:mac 财务管理软件 编辑:程序博客网 时间:2024/05/16 16:24

之前我们说过listView的刷新,最终调用的是view.requestLayout。然后经过一系列android机制,最后其实就是view的绘制过程。只其中的过程这里就不描述了,咱们直接看listView的绘制过程吧。

listView.onLayout

绘制的基本流程,onMeasure-->onLayout-->onDraw,对于listView,measure、draw都没什么特别的,终点在onLayout中(你就先这么记着)。

listView是继承了absListView的onLayout

@Override   protected void onLayout(boolean changed, int l, int t, int r, int b) {       super.onLayout(changed, l, t, r, b);       mInLayout = true;       final int childCount = getChildCount();       if (changed) {           for (int i = 0; i < childCount; i++) {               getChildAt(i).forceLayout();           }           mRecycler.markChildrenDirty();       }       layoutChildren();       mInLayout = false;       mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;       // TODO: Move somewhere sane. This doesn't belong in onLayout().       if (mFastScroll != null) {           mFastScroll.onItemCountChanged(getChildCount(), mItemCount);       }   }

核心方法是layoutChildren,它的具体实现又是在各自子类中实现的,根据方法名这个很好理解。

listView.layoutChildren:

protected void layoutChildren() {       ......     // Pull all children into the RecycleBin.           // These views will be reused if possible           final int firstPosition = mFirstPosition;           final RecycleBin recycleBin = mRecycler;//我们的主角出现了           //dataChanged 是adapterView的变量,在数据变化,即调用notifyDataChange的时候,为true           if (dataChanged) {                //childCount 屏幕内展示的item的个数(我的猜测)               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:             sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);             break;         case LAYOUT_MOVE_SELECTION:             sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);             break;         default:             if (childCount == 0) {                 if (!mStackFromBottom) {                     final int position = lookForSelectablePosition(0, true);                     setSelectedPositionInt(position);                     sel = fillFromTop(childrenTop);                 } else {                     final int position = lookForSelectablePosition(mItemCount - 1, false);                     setSelectedPositionInt(position);                     sel = fillUp(mItemCount - 1, childrenBottom);                 }             } else {                 if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {                     sel = fillSpecific(mSelectedPosition,                             oldSel == null ? childrenTop : oldSel.getTop());                 } else if (mFirstPosition < mItemCount) {                     sel = fillSpecific(mFirstPosition,                             oldFirst == null ? childrenTop : oldFirst.getTop());                 } else {                     sel = fillSpecific(0, childrenTop);                 }             }             break;         }         // Flush any cached views that did not get reused above         recycleBin.scrapActiveViews();        ......}

这个只列出了我们这次要分析的代码,其他部分需要的时候再看,毕竟太长了。
在listView首次展示的时候,dataChanged 为false,childCount==0,所以,recycleBin中ScrapView、ActiveViews都是空。接线来,mLayoutMode 默认是LAYOUT_NORMAL,所以走default分支。
mStackFromBottom是AbsListView的变量

/**   * Indicates whether the list is stacked from the bottom edge or   * the top edge.   */  boolean mStackFromBottom;

因为布局是从上而下,所以mStackFromBottom==false,然后进fillFromTop方法;

/** * Fills the list from top to bottom, starting with mFirstPosition  *  * @param nextTop The location where the top of the first item should be drawn  *  * @return The view that is currently selected  */ private View fillFromTop(int nextTop) {     mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);     mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);     if (mFirstPosition < 0) {         mFirstPosition = 0;     }     return fillDown(mFirstPosition, nextTop); }

从这里开始对listView进行填充
fillDown:

/*** Fills the list from pos down to the end of the list view. * * @param pos The first position to put in the list * * @param nextTop The location where the top of the item associated with pos *        should be drawn * * @return The view that is currently selected, if it happens to be in the *         range that we draw. */private View fillDown(int pos, int nextTop) {    View selectedView = null;    int end = (mBottom - mTop);    if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {        end -= mListPadding.bottom;    }    while (nextTop < end && pos < mItemCount) {        // is this the selected item?        boolean selected = pos == mSelectedPosition;        View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);        nextTop = child.getBottom() + mDividerHeight;        if (selected) {            selectedView = child;        }        pos++;    }    setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);    return selectedView;}

核心方法,makeAndAddView,while循环,遍历创建屏幕内的itemView。根据名字makeAndAddView就是创建item并把它添加到屏幕上
makeAndAddView:

/** * Obtain the view and add it to our list of children. The view can be made * fresh, converted from an unused view, or used as is if it was in the * recycle bin. * * @param position Logical position in the list * @param y Top or bottom edge of the view to add * @param flow If flow is true, align top edge to y. If false, align bottom *        edge to y. * @param childrenLeft Left edge where children should be positioned * @param selected Is this position selected? * @return View that was added */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;}

makeAndAddView是我们要讨论的listView之所以能item复用功能,最重要的方法。
当listView首次加载 or scroll的时候,mDataChanged==false,进入if判断。
getActiveView有可能返回null(目前知道的,只有在首次加载的第二次layout的时候不是null)。
如果ActiveView中没有,则进obtainView(这也是个很重要的方法),寻找可用的view。

obtainView:

/**  * Get a view and have it show the data associated with the specified  * position. This is called when we have already discovered that the view is  * not available for reuse in the recycle bin. The only choices left are  * converting an old view or making a new one.  *  * @param position The position to display  * @param isScrap Array of at least 1 boolean, the first entry will become true if  *                the returned view was taken from the scrap heap, false if otherwise.  *  * @return A view displaying the data associated with the specified position  */ 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 {             isScrap[0] = true;             // Finish the temporary detach started in addScrapView().             child.dispatchFinishTemporaryDetach();         }     }     if (mCacheColorHint != 0) {         child.setDrawingCacheBackgroundColor(mCacheColorHint);     }     if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {         child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);     }     setItemViewLayoutParams(child, position);     if (AccessibilityManager.getInstance(mContext).isEnabled()) {         if (mAccessibilityDelegate == null) {             mAccessibilityDelegate = new ListItemAccessibilityDelegate();         }         if (child.getAccessibilityDelegate() == null) {             child.setAccessibilityDelegate(mAccessibilityDelegate);         }     }     Trace.traceEnd(Trace.TRACE_TAG_VIEW);     return child; }

通过obtainView返回最后的item,中间的关键是调用adapter.getView(这个我们应该都很熟悉了,写adapter的时候必须要重写的),在getView里面选择使用重用view。然后判断getView的返回和recycle的scapView是否一致,如果不一致,更新scapView(猜测)。

原创粉丝点击