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中关键方法的前提下,缓存都会正常的工作!

    测试用工程源码下载

  • 相关阅读:
    HDU 5247
    HDU 4965
    CodeForces 445B
    HDU 5835
    CodeForces 731C
    HDU 5783
    CodeForces 660D
    POJ 1631
    HDU 6112
    HDU 5860
  • 原文地址:https://www.cnblogs.com/littlepanpc/p/4011602.html
Copyright © 2011-2022 走看看