关于ListView中convertView的缓存个数的探究
在面试的时候经常会被问到一个有关ListView的问题:一个ListView的高度最多可以显示5个item,但是却有20条数据要显示,问最多会有多少个convertView会被复用?或者如在ListView的Adapter中,在以Google推荐的方式进行view的复用时,convertView为null时要对convertView进行新建,那么新建的convertView最多会有多少个?或者convertView为null的情况下最多的个数是多少?
对这个问题的原理酝酿了好久,今天终于有时间对其进行验证。写了个测试用的Demo,用于分析两种情况下null的convertView的个数:单一种类item和多种类item。
首先对最简单、最常见的情况进行验证:单一种类item。
首先建了一个测试工程:LVItemCountTest。里面只有一个Activity---MainActivity。
其中的布局文件非常简单,activity_main.xml,详情如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.lvitemcounttest.MainActivity"
tools:ignore="MergeRootFrame" > <TextView
android:layout_width="fill_parent"
android:layout_height="20dp"
android:text="ListView Item convertView test" /> <ListView
android:id="@+id/listview"
android:layout_width="fill_parent"
android:layout_height="300dp" >
</ListView> </LinearLayout>
我将其中ListView的高度设置为“300dp”,是为了测试的便利性考虑的。
其中的单一item的布局文件名字为listview_item_layout_1.xml,详情如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" > <TextView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#b4917d"
android:gravity="center"
android:text="AA"
android:textColor="#009933"
android:textSize="20sp" /> </LinearLayout>
同时也是为了验证测试方便的缘故,将其中的TextView的高度设置为“50dp”,背景设置为“#b4917d”,字体颜色设置为“#009933”,而字体的大小设置为“20sp”。
然后通过继承BaseAdapter新建了一个Adapter类为SingleTypeItemAdapter,代码详情如下:
private static class SingleTypeItemAdapter extends BaseAdapter {
private Context context;
private List<String> list;
public SingleTypeItemAdapter(Context context, List<String> list) {
// TODO Auto-generated constructor stub
this.context = context;
this.list = list;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return list.size();
}
@Override
public String getItem(int position) {
// TODO Auto-generated method stub
return list.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder1 contentHolder = null;
if (convertView == null) {
Log.i("Null ConvertView", position + "");
convertView = LayoutInflater.from(context).inflate(
R.layout.listview_item_layout_1, null);
contentHolder = new ViewHolder1();
contentHolder.content = (TextView) convertView
.findViewById(R.id.content);
convertView.setTag(contentHolder);
} else {
contentHolder = (ViewHolder1) convertView.getTag();
}
contentHolder.content
.setText(list.get(position) + "---" + position);
return convertView;
}
private static class ViewHolder1 {
TextView content;
}
}
其中的代码块
- Log.i("Null ConvertView", position + "");
是为了在LogCat中查看测试结果。
同时,为了方便地在LogCat中查看测试的记录,新建了一个Log Filter,名字为LVItemCountTest,其中的“By Log Cat”的值设置为“Null ConvertView”。
然后通过设置给布局中的listview,应用刚打开时,其中的效果为:

从中可以看到,此时listview中显示了6个item,符合300dp/50dp=6的计算。同时LogCat中显示如下的记录:
10-08 15:39:25.711: I/Null ConvertView(30721): 0 10-08 15:39:25.721: I/Null ConvertView(30721): 1 10-08 15:39:25.721: I/Null ConvertView(30721): 2 10-08 15:39:25.731: I/Null ConvertView(30721): 3 10-08 15:39:25.741: I/Null ConvertView(30721): 4 10-08 15:39:25.751: I/Null ConvertView(30721): 5
记录显示跟我们的预计相符。
此时一定要注意的是:因为listview的高度恰好显示了6条item,而没有出现有顶部的item显示一半,同时底部也只显示item的一半的情况。如果出现了这种情况,记录会出现什么呢?
好的,我们轻轻地将listview下滑,下滑的距离不超过一个item的高度,效果图如下:

然后查看Logcat,显示如下:
10-08 15:39:25.711: I/Null ConvertView(30721): 0 10-08 15:39:25.721: I/Null ConvertView(30721): 1 10-08 15:39:25.721: I/Null ConvertView(30721): 2 10-08 15:39:25.731: I/Null ConvertView(30721): 3 10-08 15:39:25.741: I/Null ConvertView(30721): 4 10-08 15:39:25.751: I/Null ConvertView(30721): 5 10-08 15:39:29.631: I/Null ConvertView(30721): 6
对,就是又打印出了一条记录!并且,此后无论如何滑动listview,缓慢滑动也好,快速滑到底或者滑到顶也好,记录不会再次发生变化,这说明这个listview总共生成了7个convertView!
好的,总结一下:因为listview高度为300dp,而一个item的高度为50dp,所以,在刚显示的时候恰好显示了6条记录,而在滑动的过程中,因为出现了顶部和底部同时显示不完整的item,此时屏幕中最多出现了7个item。此后,所有的7个item的convertView可以进行复用了,就不再新建convertView。
那么好,当listview可以显示多种item的时候,情况又是怎么样的呢?
同样是出于方便测试的原因,我们再次新建了一个与之前item的布局高度相同的新的布局,来模拟另外一种item显示效果,同时,只设置了两种item布局(因为多种布局的时候,原理是跟两种布局的原理一致的)。
新的item布局为listview_item_layout_2.xml,详情为:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" > <TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#d0db96"
android:gravity="left|center_vertical"
android:text="BB"
android:textColor="#990033"
android:textSize="25sp" /> </LinearLayout>
为了与之前的item布局相区分,将其中的TextView的高度设置为“50dp”,背景设置为“#d0db96”,字体颜色设置为“#990033”,而字体的大小设置为“25sp”。
然后,新建了一个BaseAdapter,名字为MultiTypeItemAdapter,代码细节如下:
private static class MultiTypeItemAdapter extends BaseAdapter {
private Context context;
private List<String> list;
private static final int TYPE_TITLE = 0x000;
private static final int TYPE_CONTENT = 0x001;
private static final int TYPE_COUNT = 2;
public MultiTypeItemAdapter(Context context, List<String> list) {
// TODO Auto-generated constructor stub
this.context = context;
this.list = list;
}
@Override
public int getItemViewType(int position) {
// TODO Auto-generated method stub
if (position % 5 == 0) {
return TYPE_TITLE;
} else {
return TYPE_CONTENT;
}
}
@Override
public int getViewTypeCount() {
// TODO Auto-generated method stub
return TYPE_COUNT;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return list.size();
}
@Override
public String getItem(int position) {
// TODO Auto-generated method stub
return list.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder1 contentHolder = null;
ViewHolder2 titleHolder = null;
switch (getItemViewType(position)) {
case TYPE_TITLE:
if (convertView == null) {
Log.i("Null ConvertView", position / 5 + "---Title");
convertView = LayoutInflater.from(context).inflate(
R.layout.listview_item_layout_2, null);
titleHolder = new ViewHolder2();
titleHolder.title = (TextView) convertView
.findViewById(R.id.title);
convertView.setTag(titleHolder);
} else {
titleHolder = (ViewHolder2) convertView.getTag();
}
break;
case TYPE_CONTENT:
if (convertView == null) {
Log.i("Null ConvertView", position / 5 + "---Title---"
+ position % 5 + "---Content");
convertView = LayoutInflater.from(context).inflate(
R.layout.listview_item_layout_1, null);
contentHolder = new ViewHolder1();
contentHolder.content = (TextView) convertView
.findViewById(R.id.content);
convertView.setTag(contentHolder);
} else {
contentHolder = (ViewHolder1) convertView.getTag();
}
break;
default:
break;
}
switch (getItemViewType(position)) {
case TYPE_TITLE:
titleHolder.title.setText(list.get(position) + "---" + position
/ 5);
break;
case TYPE_CONTENT:
contentHolder.content.setText(list.get(position) + "---"
+ position / 5 + "---" + position % 5);
break;
default:
break;
}
return convertView;
}
private static class ViewHolder1 {
TextView content;
}
private static class ViewHolder2 {
TextView title;
}
}
其中,需要注意的有:
- private static final int TYPE_TITLE = 0x000;//表示类别Title,从0开始。
- private static final int TYPE_CONTENT = 0x001;//表示类别Content,为1。
- private static final int TYPE_COUNT = 2;//表示类别的数目,本例中只安排了两种item布局效果。
特别注意:其中类别的int类型表示要从0开始,如果两种类别分别为1和2的话,会抛出IndexOutOfBoundException,显示“length is 2, index is 2”的异常信息。
同时,每隔4条content,显示一个title的布局。即
@Override
public int getItemViewType(int position) {
// TODO Auto-generated method stub
if (position % 5 == 0) {
return TYPE_TITLE;
} else {
return TYPE_CONTENT;
}
}
代码段的作用。
同时,有两个static的内部类ViewHolder1和ViewHolder2分别用来保存content和title的可复用TextView控件。然后通过在getView中通过getItemViewType(position)来对不同的布局进行分别处理。
通过将该Adapter设置给listview,运行,初始情况下的效果图显示如图:

此时,恰好显示了6个item,其中有2个title,4个content布局。此时的LogCat显示如下 :
10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title 10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title---1---Content 10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title---2---Content 10-08 15:42:59.071: I/Null ConvertView(30928): 0---Title---3---Content 10-08 15:42:59.071: I/Null ConvertView(30928): 0---Title---4---Content 10-08 15:42:59.071: I/Null ConvertView(30928): 1---Title
此时的记录结果跟所看到的效果是一致的。然后稍微向上滑动一下listview,达到如下图所示的效果:

此时,顶部的title并没有完全隐藏掉,而底部的content也没有完全显示出来。再看此时的log,显示如下 :
10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title 10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title---1---Content 10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title---2---Content 10-08 15:42:59.071: I/Null ConvertView(30928): 0---Title---3---Content 10-08 15:42:59.071: I/Null ConvertView(30928): 0---Title---4---Content 10-08 15:42:59.071: I/Null ConvertView(30928): 1---Title 10-08 15:44:13.891: I/Null ConvertView(30928): 1---Title---1---Content
是的,此时又新创建了一个content布局!然后继续向上滑动,达到如下的效果:

是的,此时第一个title布局已经完全隐藏,但是第一个content布局还没有完全隐藏,底部的content也还没有完全显示出来!再看此时的log,显示如下:
10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title 10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title---1---Content 10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title---2---Content 10-08 15:42:59.071: I/Null ConvertView(30928): 0---Title---3---Content 10-08 15:42:59.071: I/Null ConvertView(30928): 0---Title---4---Content 10-08 15:42:59.071: I/Null ConvertView(30928): 1---Title 10-08 15:44:13.891: I/Null ConvertView(30928): 1---Title---1---Content 10-08 15:44:45.751: I/Null ConvertView(30928): 1---Title---2---Content
是的,没错,又新建了一个content布局!此后,无论再如何滑动listview,title布局和content布局都没有再生成新的convertView!因为,在初始状态下,两个title显示出来是该listview范围内最大数目的同时出现title布局,而在滑动的过程中content最大可同时出现数目为6个。
综述:
listview中通过recycler缓存已经生成的convertView来实现对item中不同的布局的复用。无论是单一类型,还是多种类型的item布局,其原理都是一样的,同一种布局在滑动的过程中,最多在listview的显示范围内能同时显示的最大数目,即为要生成的convertView的数目,其余就可以有足够数量的布局来进行复用了。在有多种不同布局的情况下,getView通过首先调用getItemViewType(position)来查找不同类型的布局的缓存。当然,是在正确覆盖adapter中关键方法的前提下,缓存都会正常的工作!
关于ListView中convertView的缓存个数的探究的更多相关文章
- ListView中ConvertView和ViewHolder
1.概述 ListView是Android中非常常见的控件通过Adapter架起数据与界面显示的桥梁,MVC思想在其中得到了很好地体现: M:model 数据模型 添加到ListView中显示的 ...
- ListView中convertView和ViewHolder的工作原理
http://blog.csdn.net/bill_ming/article/details/8817172
- 根据url路径获取图片并显示到ListView中
项目开发中我们需要从网络获取图片显示到控件中,很多开源框架如Picasso可以实现图片下载和缓存功能.这里介绍的是一种简易的网络图片获取方式并把它显示到ListView中. 本案例实现的效果如下: 项 ...
- Android 如何在 ListView 中更新 ProgressBar 进度
=======================ListView原理============================== Android 的 ListView 的原理打个简单的比喻就是: 演员演 ...
- Android之使用Volley框架在ListView中加载大量图片
1.listview 中的条目要用 Volley 中的 NetworkImageView,如果直接用ImageView也可以,但是要在getView方法中使用url地址设置为imageView的tag ...
- 43.Android之ListView中BaseAdapter学习
实际开发中个人觉得用的比较多是BaseAdapter,尽管使用起来比其他适配器有些麻烦,但是使用它却能实现很多自己喜欢的列表布局,比如ListView.GridView.Gallery.Spinner ...
- 在ListView中使用多个布局
要想在一个ListView中使用多个布局文件,比如一个信息List包含了一个信息标题和每个信息对应的时间. 关键的步骤是实现Adapter类的getItemViewType 和getViewTypeC ...
- Android ListView 中的checkbox
Q:ListView + CheckBox 当上下滚动的时候有事会自动选中或取消 A:这个与ListView的缓存机制有关.当你屏幕滚动后,ListView中的item选项视图先检查缓存中是否有视图, ...
- listview中的adapter学习小结
概述 Adapter是数据和UI之间的一个桥梁,在listview,gridview等控件中都会使用到,android给我们提拱了4个adapte供我们使用: BaseAdapter是一个抽象类,继承 ...
随机推荐
- jQuery语音播放插件
自己做jQuery插件:将audio5js封装成jQuery语音播放插件 日前的一个项目需要用到语音播放功能.发现Audio5js符合需求且使用简单,又鉴于jQuery控件便于开发操作,于是有了以 ...
- git stash用法
使用场景: 当前修改的代码还不足以提交commit,但又必须切换到其他分支,要想完成这样的操作就可以使用git stash git stash意思就是备份当前的工作区的内容,从最近的一次提交中读取相关 ...
- 标签(Tag)的各种设计方案
标签(Tag)的各种设计方案 首先,标签(Tag)是什么? 我的理解:用来具体区分某一类内容的标识,和标签类似的一个概念是分类(Category),有一个示例可以很好的区分它们两个,比如人类分为:白种 ...
- 为代码减负之<二>存储过程(SQL)
在上篇博客中介绍到了触发器的使用,而且当中也提到了触发器是个特殊的存储过程,那么什么是存储过程呢?他们 两个又究竟有什么差别呢? 事实上最基本的差别就是,触发器是当满足条件时系统自己主动运行的,而存储 ...
- Codeforces Round #272 (Div. 1)D(字符串DP)
D. Dreamoon and Binary time limit per test 2 seconds memory limit per test 512 megabytes input stand ...
- PHP memcache实现消息队列实例
现在,memcache于server缓存广泛应用.下面我来介绍一下memcache消息队列中等待的样本实现,有需要了解的朋友可以参考. memche消息队列原则key上做文章.后消息或者日志. 然后通 ...
- 第17章 中介者模式(Mediator Pattern)
原文 第17章 中介者模式(Mediator Pattern) 中介者模式 概述: 在软件开发中,我们有时会碰上许多对象互相联系互相交互的情况,对象之间存在复杂的引用关系,当需求更改时,对系统进 ...
- c++的vector容器
c++还有一个很常用的容器就是vector容器,他是数组实现的,是一种可变长的容器,在很多的时候可以简化我们的编程.可学习的链接:http://www.cnblogs.com/mr-wid/archi ...
- C#操作Xml:通过XmlDocument读写Xml文档
什么是Xml? Xml是扩展标记语言的简写,是一种开发的文本格式.关于它的更多情况可以通过w3组织了解http://www.w3.org/TR/1998/REC-xml-19980210.如果你不知道 ...
- Android开发:怎样定制界面风格
统一的用户界面是可以使得应用程序更友好.要做到用户界面的统一,我们就必须用到风格(style)和主题(theme).OPhone系统提供了很多系统默认的风格和主题,但是很多情况下,这些不能满足我们的需 ...