ListView中convertView的缓存个数的探究

来源:互联网 发布:国家要打仗了知乎 编辑:程序博客网 时间:2024/05/16 17:00

在面试的时候经常会被问到一个有关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,详情如下:

复制代码
 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2     xmlns:tools="http://schemas.android.com/tools" 3     android:id="@+id/container" 4     android:layout_width="match_parent" 5     android:layout_height="match_parent" 6     android:orientation="vertical" 7     tools:context="com.example.lvitemcounttest.MainActivity" 8     tools:ignore="MergeRootFrame" > 9 10     <TextView11         android:layout_width="fill_parent"12         android:layout_height="20dp"13         android:text="ListView Item convertView test" />14 15     <ListView16         android:id="@+id/listview"17         android:layout_width="fill_parent"18         android:layout_height="300dp" >19     </ListView>20 21 </LinearLayout>
复制代码

我将其中ListView的高度设置为“300dp”,是为了测试的便利性考虑的。

其中的单一item的布局文件名字为listview_item_layout_1.xml,详情如下:

复制代码
 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2     xmlns:tools="http://schemas.android.com/tools" 3     android:layout_width="match_parent" 4     android:layout_height="wrap_content" 5     android:orientation="vertical" > 6  7     <TextView 8         android:id="@+id/content" 9         android:layout_width="match_parent"10         android:layout_height="50dp"11         android:background="#b4917d"12         android:gravity="center"13         android:text="AA"14         android:textColor="#009933"15         android:textSize="20sp" />16 17 </LinearLayout>
复制代码

同时也是为了验证测试方便的缘故,将其中的TextView的高度设置为“50dp”,背景设置为“#b4917d”,字体颜色设置为“#009933”,而字体的大小设置为“20sp”。

然后通过继承BaseAdapter新建了一个Adapter类为SingleTypeItemAdapter,代码详情如下:

复制代码
 1     private static class SingleTypeItemAdapter extends BaseAdapter { 2         private Context context; 3         private List<String> list; 4  5         public SingleTypeItemAdapter(Context context, List<String> list) { 6             // TODO Auto-generated constructor stub 7             this.context = context; 8             this.list = list; 9         }10 11         @Override12         public int getCount() {13             // TODO Auto-generated method stub14             return list.size();15         }16 17         @Override18         public String getItem(int position) {19             // TODO Auto-generated method stub20             return list.get(position);21         }22 23         @Override24         public long getItemId(int position) {25             // TODO Auto-generated method stub26             return position;27         }28 29         @Override30         public View getView(int position, View convertView, ViewGroup parent) {31             // TODO Auto-generated method stub32             ViewHolder1 contentHolder = null;33             if (convertView == null) {34                 Log.i("Null ConvertView", position + "");35                 convertView = LayoutInflater.from(context).inflate(36                         R.layout.listview_item_layout_1, null);37                 contentHolder = new ViewHolder1();38                 contentHolder.content = (TextView) convertView39                         .findViewById(R.id.content);40                 convertView.setTag(contentHolder);41             } else {42                 contentHolder = (ViewHolder1) convertView.getTag();43             }44             contentHolder.content45                     .setText(list.get(position) + "---" + position);46             return convertView;47         }48 49         private static class ViewHolder1 {50             TextView content;51         }52 53     }
复制代码

其中的代码块

  • Log.i("Null ConvertView", position + "");

是为了在LogCat中查看测试结果。

同时,为了方便地在LogCat中查看测试的记录,新建了一个Log Filter,名字为LVItemCountTest,其中的“By Log Cat”的值设置为“Null ConvertView”。

然后通过设置给布局中的listview,应用刚打开时,其中的效果为:

从中可以看到,此时listview中显示了6个item,符合300dp/50dp=6的计算。同时LogCat中显示如下的记录:

复制代码
 1 10-08 15:39:25.711: I/Null ConvertView(30721): 0 2  3 10-08 15:39:25.721: I/Null ConvertView(30721): 1 4  5 10-08 15:39:25.721: I/Null ConvertView(30721): 2 6  7 10-08 15:39:25.731: I/Null ConvertView(30721): 3 8  9 10-08 15:39:25.741: I/Null ConvertView(30721): 410 11 10-08 15:39:25.751: I/Null ConvertView(30721): 5
复制代码

记录显示跟我们的预计相符。

此时一定要注意的是:因为listview的高度恰好显示了6条item,而没有出现有顶部的item显示一半,同时底部也只显示item的一半的情况。如果出现了这种情况,记录会出现什么呢?

好的,我们轻轻地将listview下滑,下滑的距离不超过一个item的高度,效果图如下:

然后查看Logcat,显示如下:

复制代码
 1 10-08 15:39:25.711: I/Null ConvertView(30721): 0 2  3 10-08 15:39:25.721: I/Null ConvertView(30721): 1 4  5 10-08 15:39:25.721: I/Null ConvertView(30721): 2 6  7 10-08 15:39:25.731: I/Null ConvertView(30721): 3 8  9 10-08 15:39:25.741: I/Null ConvertView(30721): 410 11 10-08 15:39:25.751: I/Null ConvertView(30721): 512 13 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,详情为:

复制代码
 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2     xmlns:tools="http://schemas.android.com/tools" 3     android:layout_width="match_parent" 4     android:layout_height="wrap_content" 5     android:orientation="vertical" > 6  7     <TextView 8         android:id="@+id/title" 9         android:layout_width="match_parent"10         android:layout_height="50dp"11         android:background="#d0db96"12         android:gravity="left|center_vertical"13         android:text="BB"14         android:textColor="#990033"15         android:textSize="25sp" />16 17 </LinearLayout>
复制代码

为了与之前的item布局相区分,将其中的TextView的高度设置为“50dp”,背景设置为“#d0db96”,字体颜色设置为“#990033”,而字体的大小设置为“25sp”。

然后,新建了一个BaseAdapter,名字为MultiTypeItemAdapter,代码细节如下:

复制代码
  1     private static class MultiTypeItemAdapter extends BaseAdapter {  2         private Context context;  3         private List<String> list;  4         private static final int TYPE_TITLE = 0x000;  5         private static final int TYPE_CONTENT = 0x001;  6         private static final int TYPE_COUNT = 2;  7   8         public MultiTypeItemAdapter(Context context, List<String> list) {  9             // TODO Auto-generated constructor stub 10             this.context = context; 11             this.list = list; 12         } 13  14         @Override 15         public int getItemViewType(int position) { 16             // TODO Auto-generated method stub 17             if (position % 5 == 0) { 18                 return TYPE_TITLE; 19             } else { 20                 return TYPE_CONTENT; 21             } 22         } 23  24         @Override 25         public int getViewTypeCount() { 26             // TODO Auto-generated method stub 27             return TYPE_COUNT; 28         } 29  30         @Override 31         public int getCount() { 32             // TODO Auto-generated method stub 33             return list.size(); 34         } 35  36         @Override 37         public String getItem(int position) { 38             // TODO Auto-generated method stub 39             return list.get(position); 40         } 41  42         @Override 43         public long getItemId(int position) { 44             // TODO Auto-generated method stub 45             return position; 46         } 47  48         @Override 49         public View getView(int position, View convertView, ViewGroup parent) { 50             // TODO Auto-generated method stub 51             ViewHolder1 contentHolder = null; 52             ViewHolder2 titleHolder = null; 53             switch (getItemViewType(position)) { 54             case TYPE_TITLE: 55                 if (convertView == null) { 56                     Log.i("Null ConvertView", position / 5 + "---Title"); 57                     convertView = LayoutInflater.from(context).inflate( 58                             R.layout.listview_item_layout_2, null); 59                     titleHolder = new ViewHolder2(); 60                     titleHolder.title = (TextView) convertView 61                             .findViewById(R.id.title); 62                     convertView.setTag(titleHolder); 63                 } else { 64                     titleHolder = (ViewHolder2) convertView.getTag(); 65                 } 66                 break; 67             case TYPE_CONTENT: 68                 if (convertView == null) { 69                     Log.i("Null ConvertView", position / 5 + "---Title---" 70                             + position % 5 + "---Content"); 71                     convertView = LayoutInflater.from(context).inflate( 72                             R.layout.listview_item_layout_1, null); 73                     contentHolder = new ViewHolder1(); 74                     contentHolder.content = (TextView) convertView 75                             .findViewById(R.id.content); 76                     convertView.setTag(contentHolder); 77                 } else { 78                     contentHolder = (ViewHolder1) convertView.getTag(); 79                 } 80                 break; 81  82             default: 83                 break; 84             } 85             switch (getItemViewType(position)) { 86             case TYPE_TITLE: 87                 titleHolder.title.setText(list.get(position) + "---" + position 88                         / 5); 89                 break; 90             case TYPE_CONTENT: 91                 contentHolder.content.setText(list.get(position) + "---" 92                         + position / 5 + "---" + position % 5); 93                 break; 94  95             default: 96                 break; 97             } 98             return convertView; 99         }100 101         private static class ViewHolder1 {102             TextView content;103         }104 105         private static class ViewHolder2 {106             TextView title;107         }108 109     }
复制代码

其中,需要注意的有:

  • 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的布局。即

复制代码
1                 @Override2         public int getItemViewType(int position) {3             // TODO Auto-generated method stub4             if (position % 5 == 0) {5                 return TYPE_TITLE;6             } else {7                 return TYPE_CONTENT;8             }9         }
复制代码

代码段的作用。

同时,有两个static的内部类ViewHolder1和ViewHolder2分别用来保存content和title的可复用TextView控件。然后通过在getView中通过getItemViewType(position)来对不同的布局进行分别处理。

通过将该Adapter设置给listview,运行,初始情况下的效果图显示如图:

此时,恰好显示了6个item,其中有2个title,4个content布局。此时的LogCat显示如下 :

复制代码
 1 10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title 2  3 10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title---1---Content 4  5 10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title---2---Content 6  7 10-08 15:42:59.071: I/Null ConvertView(30928): 0---Title---3---Content 8  9 10-08 15:42:59.071: I/Null ConvertView(30928): 0---Title---4---Content10 11 10-08 15:42:59.071: I/Null ConvertView(30928): 1---Title
复制代码

此时的记录结果跟所看到的效果是一致的。然后稍微向上滑动一下listview,达到如下图所示的效果:

此时,顶部的title并没有完全隐藏掉,而底部的content也没有完全显示出来。再看此时的log,显示如下 :

复制代码
 1 10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title 2  3 10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title---1---Content 4  5 10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title---2---Content 6  7 10-08 15:42:59.071: I/Null ConvertView(30928): 0---Title---3---Content 8  9 10-08 15:42:59.071: I/Null ConvertView(30928): 0---Title---4---Content10 11 10-08 15:42:59.071: I/Null ConvertView(30928): 1---Title12 13 10-08 15:44:13.891: I/Null ConvertView(30928): 1---Title---1---Content
复制代码

是的,此时又新创建了一个content布局!然后继续向上滑动,达到如下的效果:

是的,此时第一个title布局已经完全隐藏,但是第一个content布局还没有完全隐藏,底部的content也还没有完全显示出来!再看此时的log,显示如下:

复制代码
 1 10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title 2  3 10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title---1---Content 4  5 10-08 15:42:59.061: I/Null ConvertView(30928): 0---Title---2---Content 6  7 10-08 15:42:59.071: I/Null ConvertView(30928): 0---Title---3---Content 8  9 10-08 15:42:59.071: I/Null ConvertView(30928): 0---Title---4---Content10 11 10-08 15:42:59.071: I/Null ConvertView(30928): 1---Title12 13 10-08 15:44:13.891: I/Null ConvertView(30928): 1---Title---1---Content14 15 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中关键方法的前提下,缓存都会正常的工作!

测试用工程源码下载


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 汉购通过期了怎么办 天猫退货要检测报告怎么办 京东退货后发票怎么办 京东金融被盗刷怎么办 京东e卡丢了怎么办 京东e卡没有密码怎么办 同一个订单微信付款两次怎么办 京东购物卡掉了怎么办 京东购物卡丢了怎么办 京东快递丢了怎么办 京东e卡支付多了怎么办 京东帐号忘了怎么办 京东白条风控怎么办 我有个破袄…没有衣服怎么办办 轩辕传奇手游灵宠融合错了怎么办 各尧学生不能用怎么办? 山东一卡通商务卡丢了怎么办 和信通过期了怎么办 和信通过期怎么办延期 和信通过期余额怎么办 超市储蓄卡丢了怎么办 提现提到注销卡怎么办 美通卡过期2年了怎么办 物美美通卡丢失怎么办 网上购物电话留错了怎么办 微信斗牛一直输怎么办 微信斗牛输了钱怎么办 微信举报诈骗不成功该怎么办 沙河拿服装太贵怎么办 包上的暗扣掉了怎么办 银手镯暗扣松老是掉怎么办 包包纽扣锁坏了怎么办 包的纽扣坏了怎么办 包上的纽扣坏了怎么办 洗衣服不小心用了色渍净怎么办 洗衣服不小心沾了卫生纸怎么办 麻料裤子扎皮肤怎么办 衣服没洗干净有点发光怎么办 桑蚕丝衣服脏了发光洗不掉怎么办 厨师衣服的油味怎么办 看上夜场的小姐了怎么办