//宽度测量逻辑 if (widthMode == MeasureSpec.UNSPECIFIED) { widthSize = mListPadding.left + mListPadding.right + childWidth + getVerticalScrollbarWidth(); } else { //初始化childState = combineMeasuredStates(childState, child.getMeasuredState()) widthSize |= (childState & MEASURED_STATE_MASK); } //高度测量逻辑,这里若是测量模式为UNSPECIFIED,listview的高度就只会显示一个childHeight的高度 if (heightMode == MeasureSpec.UNSPECIFIED) { //初始化 childHeight = child.getMeasuredHeight(); heightSize = mListPadding.top + mListPadding.bottom + childHeight + getVerticalFadingEdgeLength() * 2; } //正常状况下测量模式应该是AT_MOST,此时会去累加每个children的高度 if (heightMode == MeasureSpec.AT_MOST) { heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1); }
listview经过RecycleBin进行view的复用.复用的时机在layoutChildren中进行java
2.1 RecycleBin原理数组
RecycleBin两个比较重要的view数组缓存
//存储当前显示的view内容 private View[] mActiveViews = new View[0]; //根据不一样的type废弃的viewlist private ArrayList<View>[] mScrapViews;
在listview的layoutChildren()方法中 RecycleBin初始化mActiveViews方式markdown
if (dataChanged) { for (int i = 0; i < childCount; i++) { //数据改变后把view放置废弃的复用池里 recycleBin.addScrapView(getChildAt(i), firstPosition+i); } } else { //childCount为有效显示的child数量 recycleBin.fillActiveViews(childCount, firstPosition); } ... //将目前全部的ActiveViews降级为ScrapViews,并将以前的全部ScrapViews清除,为新产生的 //ActiveViews作好准备 recycleBin.scrapActiveViews();
RecycleBin复用过程 :
与用户进行交互的View,那么这些View会经过RecycleBin直接存储到mActivityView数组当中,以便为了直接复用. 当咱们滑动ListView的时候,有些View被滑动到屏幕以外(offScreen) View,那么这些View就成为了ScrapView,也就是废弃的View,已经没法与用户进行交互了,这样在UI视图改变的时候就没有绘制这些无用视图的必要了。他将会被RecycleBin存储到mScrapView数组当中,可是没有被销毁掉,目的是为了二次复用,也就是间接复用。当新的View须要显示的时候,先判断mActivityView中是否存在,若是存在那么咱们就能够从mActivityView数组当中直接取出复用,也就是直接复用,不然的话从mScrapView数组当中进行判断,若是存在,那么二次复用当前的视图,若是不存在,那么就须要inflate View了。网络
搬挪自网络的总结图片:ide
获取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逻辑方法 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; }
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) { if (!mDataChanged) { //获取ActiveView逻辑 // 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. /** *若是mActivityView[]数组中没有可用的View,那么尝试从mScrapView数组中读取.而后从新布局. *若是能够从mScrapView数组中能够获取到,那么直接返回调用mAdapter.getView(position,scrapView,this); *若是获取不到那么执行mAdapter.getView(position,null,this)方法. */ final View child = obtainView(position, mIsScrap); // This needs to be positioned and measured. setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; }
这里能够看到若是数据源没有变化的时候,会从mActivityView数组中判断是否存在能够直接复用的View,可能不少读者都不太明白直接复用究竟是怎么个过程,举个例子,好比说咱们ListView一页能够显示10条数据,那么咱们在这个时候滑动一个Item的距离,也就是说把position = 0的Item移除屏幕,将position = 10 的Item移入屏幕,那么position = 1的Item是否是就直接可以从mActivityView数组中拿到呢?这是能够的,咱们在第一次加载Item数据的时候,已经将position = 0~9的Item加入到了mActivityView数组当中,那么在第二次加载的时候,因为position = 1 的Item仍是ActivityView,那么这里就能够直接从数组中获取,而后从新布局。这里也就表示的是Item的直接复用。post
若是咱们在mActivityView数组中获取不到position对应的View,那么就尝试从mScrapView废弃View数组中尝试去获取,还拿刚才的例子来讲当position = 0的Item被移除屏幕的时候,首先会Detach让View和视图进行分离,清空children,而后将废弃View添加到mScrapView数组当中,当加载position = 10的Item时,mActivityView数组确定是没有的,也就没法获取到,一样mScrapView中也是不存在postion = 10与之对应的废弃View,说白了就是mScrapView数组只有mScrapView[0]这一项数据,确定是没有mScrapView[10]这项数据的,那么咱们就会这样想,确定是从Adapter中的getView方法获取新的数据喽,其实并非这样,虽然mScrapView中虽然没有与之对应的废弃View,可是会返回最后一个缓存的View传递给convertview。那么也就是将mScrapView[0]对应的View返回。整体的流程就是这样。this
抄自网络图片:
spa