zoukankan      html  css  js  c++  java
  • 关于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,详情如下:

     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     <TextView
    11         android:layout_width="fill_parent"
    12         android:layout_height="20dp"
    13         android:text="ListView Item convertView test" />
    14 
    15     <ListView
    16         android:id="@+id/listview"
    17         android:layout_width="fill_parent"
    18         android:layout_height="300dp" >
    19     </ListView>
    20 
    21 </LinearLayout>
    View Code

    我将其中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>
    View Code

    同时也是为了验证测试方便的缘故,将其中的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         @Override
    12         public int getCount() {
    13             // TODO Auto-generated method stub
    14             return list.size();
    15         }
    16 
    17         @Override
    18         public String getItem(int position) {
    19             // TODO Auto-generated method stub
    20             return list.get(position);
    21         }
    22 
    23         @Override
    24         public long getItemId(int position) {
    25             // TODO Auto-generated method stub
    26             return position;
    27         }
    28 
    29         @Override
    30         public View getView(int position, View convertView, ViewGroup parent) {
    31             // TODO Auto-generated method stub
    32             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) convertView
    39                         .findViewById(R.id.content);
    40                 convertView.setTag(contentHolder);
    41             } else {
    42                 contentHolder = (ViewHolder1) convertView.getTag();
    43             }
    44             contentHolder.content
    45                     .setText(list.get(position) + "---" + position);
    46             return convertView;
    47         }
    48 
    49         private static class ViewHolder1 {
    50             TextView content;
    51         }
    52 
    53     }
    View Code

    其中的代码块

    • 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): 4
    10 
    11 10-08 15:39:25.751: I/Null ConvertView(30721): 5
    View Code

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

    此时一定要注意的是:因为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): 4
    10 
    11 10-08 15:39:25.751: I/Null ConvertView(30721): 5
    12 
    13 10-08 15:39:29.631: I/Null ConvertView(30721): 6
    View Code

    对,就是又打印出了一条记录!并且,此后无论如何滑动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>
    View Code

    为了与之前的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     }
    View Code

    其中,需要注意的有:

    • 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                 @Override
    2         public int getItemViewType(int position) {
    3             // TODO Auto-generated method stub
    4             if (position % 5 == 0) {
    5                 return TYPE_TITLE;
    6             } else {
    7                 return TYPE_CONTENT;
    8             }
    9         }
    View Code

    代码段的作用。

    同时,有两个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---Content
    10 
    11 10-08 15:42:59.071: I/Null ConvertView(30928): 1---Title
    View Code

    此时的记录结果跟所看到的效果是一致的。然后稍微向上滑动一下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---Content
    10 
    11 10-08 15:42:59.071: I/Null ConvertView(30928): 1---Title
    12 
    13 10-08 15:44:13.891: I/Null ConvertView(30928): 1---Title---1---Content
    View Code

    是的,此时又新创建了一个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---Content
    10 
    11 10-08 15:42:59.071: I/Null ConvertView(30928): 1---Title
    12 
    13 10-08 15:44:13.891: I/Null ConvertView(30928): 1---Title---1---Content
    14 
    15 10-08 15:44:45.751: I/Null ConvertView(30928): 1---Title---2---Content
    View Code

    是的,没错,又新建了一个content布局!此后,无论再如何滑动listview,title布局和content布局都没有再生成新的convertView!因为,在初始状态下,两个title显示出来是该listview范围内最大数目的同时出现title布局,而在滑动的过程中content最大可同时出现数目为6个。

    综述:

    listview中通过recycler缓存已经生成的convertView来实现对item中不同的布局的复用。无论是单一类型,还是多种类型的item布局,其原理都是一样的,同一种布局在滑动的过程中,最多在listview的显示范围内能同时显示的最大数目,即为要生成的convertView的数目,其余就可以有足够数量的布局来进行复用了。在有多种不同布局的情况下,getView通过首先调用getItemViewType(position)来查找不同类型的布局的缓存。当然,是在正确覆盖adapter中关键方法的前提下,缓存都会正常的工作!

    测试用工程源码下载

  • 相关阅读:
    搜索回车跳转页面
    登录验证码
    【排序算法】排序算法之插入排序
    PAT 乙级 1044 火星数字 (20 分)
    PAT 甲级 1035 Password (20 分)
    PAT 甲级 1041 Be Unique (20 分)
    PAT 甲级 1054 The Dominant Color (20 分)
    PAT 甲级 1027 Colors in Mars (20 分)
    PAT 甲级 1083 List Grades (25 分)
    PAT 甲级 1005 Spell It Right (20 分)
  • 原文地址:https://www.cnblogs.com/littlepanpc/p/4011602.html
Copyright © 2011-2022 走看看