ListView 复用学习

1.listview的测量说明

//宽度测量逻辑
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);
}

2.listview的复用原理

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

相关文章
相关标签/搜索