ListView加载不同布局时的复用及原理分析
2016-03-06 16:35
465 查看
当加载不同布局时,需要使用到getViewTypeCount和getItemViewType。首先来看看如何来实现加载不同而已时的复用
步骤:
重(@Override)写 getViewTypeCount() – 返回你有多少个不同的布局
重写 getItemViewType(int) – 由position返回view type id
根据view item的类型,在getView中创建正确的convertView
代码如下:
其实很简单,无非就是在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
这个方法只是对mScrapView作的一个初始化。而mScrapView的大小 就是viewTypeCount. 接下来要通过搜索mRecycler.addScrapView发现第一次调用在obtainView方法中,这个方法会调用getView完成视图的初始化工作.
代码分两部分,一部分是从空闲的集合mTransientStateViewsById中到取view时行利用,
如果没取到,则直接从mScrapViews中取。可以发现,这里并没有填充mScrapViews. 还有一个地方执行addScrapView方法的则是在
trackMotionScroll方法中,这个方法会在界面滑动时会被调用。
mScrapViews会把所有滑过的item的条目的引用都保存起来,以便以后的复用,
getScrapView先判断类型,然后调用retrieveFromScrap取出数组中相应位置的view, 同时删除当前位置的引用。
fillActivityViews则会填充所有活动中item
那么 mActiveViews与mScrapView的添加还是要回到ListView中
当ListView执行OnLayout时,会调用layoutChildren方法,并填充 mActiveViews与mScrapView。 当数据项发生变化时,调用addScrapView。
再看一个重要的方法makeAndAddView,这个方法将决定从哪里取取出child,并绘制出来。
分析的有点乱。总结下,也就是mActiveViews保存活动中的item, mScrapView 保存着未被显示出来的位置的item.当数据项发生变化,或者滑动时,会取出 mScrapView中的item进行复用,并对缓存进行重新的设置。
步骤:
重(@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进行复用,并对缓存进行重新的设置。
相关文章推荐
- 完美实现Android ListView中的TextView的跑马灯效果
- android上改变listView的选中颜色
- Delphi7中Listview的常用功能汇总
- Delphi控件ListView的属性及使用方法详解
- android中ListView数据刷新时的同步方法
- Android提高之ListView实现自适应表格的方法
- Android中实现水平滑动(横向滑动)ListView示例
- C#实现ListView选中项向上或向下移动的方法
- Listview加载的性能优化是如何实现的
- C#实现listview Group收缩扩展的方法
- C# listview添加combobox到单元格的实现代码
- ListView 百分比进度条(delphi版)
- Android listview多视图嵌套多视图
- ListView Adapter优化 实例
- Android用ListView显示SDCard文件列表的小例子
- Adapter实现ListView带多选框等状态的自定义控件的注意事项
- asp.net ListView 数据绑定
- Android之ScrollView嵌套ListView和GridView冲突的解决方法
- android ListView和ProgressBar(进度条控件)的使用方法
- Android实现ListView异步加载图片的方法