ListView加载不同布局时的复用及原理分析

当加载不同布局时,需要使用到getViewTypeCount和getItemViewType。首先来看看如何来实现加载不同而已时的复用

步骤:
重(@Override)写 getViewTypeCount() – 返回你有多少个不同的布局
重写 getItemViewType(int) – 由position返回view type id
根据view item的类型,在getView中创建正确的convertView

代码如下:

private class MyAdapter extends BaseAdapter {

@Override
public int getCount() {
return list.size();
        }

@Override
public Object getItem(int position) {
return position;
        }

@Override
public long getItemId(int position) {
return 0;
        }

/**
         * 视图类型的个数
* @return
*/
@Override
public int getViewTypeCount() {
return 2;
        }

/**
         * 返回不同位置的视图的类型
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {

if (position % 2 == 0) {
return 1;
            } else {
return 2;
            }

        }

@Override
public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
if (convertView == null) {
                holder = new ViewHolder();
if (getItemViewType(position) == 1) {
                    convertView = View.inflate(MutipleItemListActivity.this, R.layout.item_listview_left, null);
                } else {
                    convertView = View.inflate(MutipleItemListActivity.this, R.layout.item_listview_right, null);
                }
                holder.textView = (TextView) convertView.findViewById(R.id.textView);
                convertView.setTag(holder);

            } else {
                holder = (ViewHolder) convertView.getTag();
            }


            String text = list.get(position);
            holder.textView.setText(text);

return convertView;
        }
    }


static class ViewHolder {
        TextView textView;
    }
}

其实很简单,无非就是在getView方法中调用调用getItemViewType方法去判断类型,从而填充不同的布局。

下面来分析一下利用的原理
既然有复用,就有缓存,那么ListView的item是在哪里缓存的呢?
分析原码要有一个入口,ListView的入口无疑是setAdapter方法,因为我们第一个调用的就是它。
源码的中有一名
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
getViewTypeCount方法设置的视图类型数量被mRecycler获取。继续追踪,可以发现, mRecycler为AbListView的一个内部类,而它就是我们要找的缓存所在RecycleBin 。RecycleBin 中有两个成员变量数组, mActiveViews与mScrapView 。从字面上就可以理解,mActiveViews为活动中的view,即显示在界面上的item, 而mScrapView则缓存着用来利用的item.
而外还有一个LongSparseArray mTransientStateViewsById, 保存着mScrapView中空闲view的引用 。
继续看setViewTypeCount

public void setViewTypeCount(int viewTypeCount) {
    if (viewTypeCount < 1) {
        throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
    }
    //noinspection unchecked
    ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
    for (int i = 0; i < viewTypeCount; i++) {
        scrapViews[i] = new ArrayList<View>();
    }
    mViewTypeCount = viewTypeCount;
    mCurrentScrap = scrapViews[0];
    mScrapViews = scrapViews;
}

这个方法只是对mScrapView作的一个初始化。而mScrapView的大小 就是viewTypeCount. 接下来要通过搜索mRecycler.addScrapView发现第一次调用在obtainView方法中,这个方法会调用getView完成视图的初始化工作.

final View transientView = mRecycler.getTransientStateView(position);
if (transientView != null) {
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);
    }
}
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();
    }
}

代码分两部分,一部分是从空闲的集合mTransientStateViewsById中到取view时行利用,
如果没取到,则直接从mScrapViews中取。可以发现,这里并没有填充mScrapViews. 还有一个地方执行addScrapView方法的则是在
trackMotionScroll方法中,这个方法会在界面滑动时会被调用。

for (int i = childCount - 1; i >= 0; i--) {
    final View child = getChildAt(i);
    if (child.getTop() <= bottom) {
        break;
    } else {
        start = i;
        count++;
        int position = firstPosition + i;
        if (position >= headerViewsCount && position < footerViewsStart) {
            // The view will be rebound to new data, clear any
            // system-managed transient state.
            child.clearAccessibilityFocus();
            mRecycler.addScrapView(child, position);
        }
    }
}

mScrapViews会把所有滑过的item的条目的引用都保存起来,以便以后的复用,

View getScrapView(int position) {
    final int whichScrap = mAdapter.getItemViewType(position);
    if (whichScrap < 0) {
        return null;
    }
    if (mViewTypeCount == 1) {
        return retrieveFromScrap(mCurrentScrap, position);
    } else if (whichScrap < mScrapViews.length) {
        return retrieveFromScrap(mScrapViews[whichScrap], position);
    }
    return null;
}

getScrapView先判断类型,然后调用retrieveFromScrap取出数组中相应位置的view, 同时删除当前位置的引用。

fillActivityViews则会填充所有活动中item

void fillActiveViews(int childCount, int firstActivePosition) {
    if (mActiveViews.length < childCount) {
        mActiveViews = new View[childCount];
    }
    mFirstActivePosition = firstActivePosition;

    //noinspection MismatchedReadAndWriteOfArray
    final View[] activeViews = mActiveViews;
    for (int i = 0; i < childCount; i++) {
        View child = getChildAt(i);
        AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
        // Don't put header or footer views into the scrap heap
        if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
            // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
            //        However, we will NOT place them into scrap views.
            activeViews[i] = child;
            // Remember the position so that setupChild() doesn't reset state.
            lp.scrappedFromPosition = firstActivePosition + i;
        }
    }
}

那么 mActiveViews与mScrapView的添加还是要回到ListView中

  protected void layoutChildren()   
    {  
        if (dataChanged)   
        {  
            for (int i = 0; i < childCount; i++)   
            {  
                recycleBin.addScrapView(getChildAt(i));     
            }  
        } else   
        {  
            recycleBin.fillActiveViews(childCount, firstPosition);  
        }  
        ....  
    }  

当ListView执行OnLayout时,会调用layoutChildren方法,并填充 mActiveViews与mScrapView。 当数据项发生变化时,调用addScrapView。

再看一个重要的方法makeAndAddView,这个方法将决定从哪里取取出child,并绘制出来。

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;
}

分析的有点乱。总结下,也就是mActiveViews保存活动中的item, mScrapView 保存着未被显示出来的位置的item.当数据项发生变化,或者滑动时,会取出 mScrapView中的item进行复用,并对缓存进行重新的设置。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值