ListView的刷新与复用

之前我们说过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(猜测)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值