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中进行

2.1 RecycleBin原理

RecycleBin两个比较重要的view数组

//存储当前显示的view内容
 private View[] mActiveViews = new View[0];
 //根据不同的type废弃的viewlist
 private ArrayList<View>[] mScrapViews;

在listview的layoutChildren()方法中 RecycleBin初始化mActiveViews方式

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了。

搬挪自网络的总结图片:

这里写图片描述

获取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的直接复用。

如果我们在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返回。总体的流程就是这样。

抄自网络图片:
这里写图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值