ListView作为android中最常使用的控件,可以以条目的形式显示大量的数据,经常被用于显示最近联系人列表,对于每一个 Item,均要求adapter的getView方法返回一个View,因此ListView的实现是离不开Adapter的,如果以MVC的思想来看ListView的话,ListView的显示相当于V,Adapter部分相当于C,而数据部分就相当于M了,接下来的几篇博客计划对ListView自己所了解的一些优化措施总结一下,希望能够帮助到大家;

先来看看如果我们不使用任何优化措施的话,使用ListView的方法:

这里先补充下获得LayoutInflater的三种方法:

(1)如果是在Activity中的话,可以调用

LayoutInflater inflater = getLayoutInflater();

(2)如果不是在Activity中,则可以将context上下文作为参数,通过

context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

(3)如果不是在Activity中,则可以将context上下文作为参数,通过

LayoutInflater inflater = LayoutInflater.from(context);

首先定义Activity界面布局listview.xml,很简单,里面就只有一个ListView

  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="match_parent"
  5.  
    tools:context=".MainActivity" >
  6.  
    <ListView
  7.  
    android:id="@+id/listView"
  8.  
    android:layout_width="match_parent"
  9.  
    android:layout_height="match_parent" />
  10.  
    </LinearLayout>

再定义ListView的每个item的布局item.xml,也很简单,只有一个TextView

  1.  
    <?xml version="1.0" encoding="utf-8"?>
  2.  
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.  
    android:layout_width="match_parent"
  4.  
    android:layout_height="match_parent"
  5.  
    android:orientation="vertical" >
  6.  
    <TextView
  7.  
    android:id="@+id/textView"
  8.  
    android:layout_width="wrap_content"
  9.  
    android:layout_height="50dp"/>
  10.  
    </LinearLayout>

接下来定义一个Adapter,继承自BaseAdapter,用来为ListView填充数据

  1.  
    public class ListViewAdapter extends BaseAdapter{
  2.  
     
  3.  
    List<String> list = new ArrayList<String>();
  4.  
    LayoutInflater inflater = null;
  5.  
     
  6.  
    public ListViewAdapter(List<String> list,Context context) {
  7.  
    this.list = list;
  8.  
    inflater = LayoutInflater.from(context);
  9.  
    }
  10.  
    @Override
  11.  
    public int getCount() {
  12.  
    return list.size();
  13.  
    }
  14.  
     
  15.  
    @Override
  16.  
    public Object getItem(int position) {
  17.  
    return list.get(position);
  18.  
    }
  19.  
     
  20.  
    @Override
  21.  
    public long getItemId(int position) {
  22.  
    return position;
  23.  
    }
  24.  
     
  25.  
    @Override
  26.  
    public View getView(int position, View convertView, ViewGroup parent) {
  27.  
    View view = null;
  28.  
    System.out.println("before: "+list.get(position)+"------- "+convertView);
  29.  
    view = inflater.inflate(R.layout.item, null);
  30.  
    System.out.println("after: "+list.get(position)+"------- "+view);
  31.  
    TextView textView = (TextView) view.findViewById(R.id.textView);
  32.  
    textView.setText(list.get(position));
  33.  
    return view;
  34.  
    }
  35.  
    }

可以发现,我们在getView里并没有做任何的优化,待会我们看看这样的方式会出什么问题;

定义Activity,将Adapter绑定到ListView上面

  1.  
    public class MainActivity extends Activity {
  2.  
     
  3.  
    List<String> list = new ArrayList<String>();
  4.  
    ListView listView = null;
  5.  
    @Override
  6.  
    protected void onCreate(Bundle savedInstanceState) {
  7.  
    super.onCreate(savedInstanceState);
  8.  
    setContentView(R.layout.listview);
  9.  
    listView = (ListView) findViewById(R.id.listView);
  10.  
    for(int i = 1;i < 50;i++)
  11.  
    {
  12.  
    list.add("item "+i);
  13.  
    }
  14.  
    ListViewAdapter adapter = new ListViewAdapter(list, this);
  15.  
    listView.setAdapter(adapter);
  16.  
    }
  17.  
    }

很简单,我们模拟了有50个条目的ListView,并且通过ListViewAdapter的构造函数将其传递给Adapter用于在界面展示这些数据;

运行效果图:

对应的Logcat输出:

可以发现初始状态有10个item显示在界面上,并且他们的before view均是null的,调用inflate方法之后生成了新的view,因此after view非空了;

接着我们向上拖动屏幕,可以在Logcat看到有如下输出:

可以发现此时的item 19和item 20在before之前convertView得地址都是@4181a590,这个view是已经划出屏幕的item 9的,这点可以从刚开始第一屏幕的输出看出来,因为item 9已经被加入到了RecycleBin缓存中了,所以在调用getView方法的时候convertView获取到的是缓存中的随机一个view,item 19和item 20完全有可能获取到同一个view,但是他们的after view是不可能出现相同的,也就是说我们这个例子中的after view的地址值是不可能相同的,因为我们模拟了50个item,所以会有50个view被放到RecycleBin缓存中,也许现在对于我们显示来说没什么,那么如果有大量的信息需要显示的话,直接就会报内存的;

通过上面我们会发现一点,在调用getView方法的时候,他的第二个参数可能不会是null的,原因就是RecycleBin缓存帮我们暂存了那些划出屏幕的view,所以我们在convertView非空的情况下我们也没什么必要重新调用inflate方法加载布局了,因为这个方法毕竟也是要解析xml文件的,至少是要花时间的,直接使用从缓存中取出的view即可啦,先来看看ListView提供的RecycleBin缓存图解:

图片来自于:https://hit-alibaba.github.io/interview/Android/basic/ListView-Optimize.html

从上面的图上可以看到当item 1被划出屏幕之后,会被放到Recycle缓存中,当item 8要划入屏幕的时候,如果他和item 1的类型相同的话,则直接从Recycle中获得即可,即此时的convertView不再是null;如果他和item 1类型不一致的话,则会新建view视图,即此时convertView等于null;

好的,那我们接下来就该充分使用android提供给我们的RecycleBin机制来优化ListView了;

只需要修改ListViewAdapter类的getView方法即可了:

  1.  
    @Override
  2.  
    public View getView(int position, View convertView, ViewGroup parent) {
  3.  
    View view = null;
  4.  
    System.out.println("before: "+list.get(position)+"------- "+convertView);
  5.  
    if(convertView == null)
  6.  
    {
  7.  
    view = inflater.inflate(R.layout.item, null);
  8.  
    }else
  9.  
    view = convertView;
  10.  
    TextView textView = (TextView) view.findViewById(R.id.textView);
  11.  
    textView.setText(list.get(position));
  12.  
    return view;
  13.  
    }

不滑动屏幕的时候,程序输出:

接着我们滑动屏幕,查看输出:

注意红色部分,发现没经过11个item,都会复用之前的view,这也就是说我们的RecycleBin缓存中将只有11个view了,不像前面那样有50个view,这在很大程度上节约了内存,想想如果有上千万条数据需要显示,每个数据条目都有一个view在RecycleBin中是一件多么可怕的事情,进行了convertView是否为null的判断之后,将只会缓存一屏幕的view,当然有可能会多那么几个吧;

上面我们通过判断convertView是否为空对ListView进行了优化,接下来我们看看getView方法,里面在获取TextView的时候,我们使用了findViewById方法,这个方法是与IO有关的操作,想必也会影响性能吧,他只要的目的是获得某一个view的布局罢了,我们如果有了view的话,其实只需要第一次将该view和其布局绑定到一起就可以了,没必要每次都为view设置布局了,这也就是使用setTag的目的了;

这里也仅仅只是对ListViewAdapter的geyView方法进行修改即可:

  1.  
    @Override
  2.  
    public View getView(int position, View convertView, ViewGroup parent) {
  3.  
    View view = null;
  4.  
    ViewHolder viewHolder = null;
  5.  
    System.out.println("before: "+list.get(position)+"------- "+convertView);
  6.  
    if(convertView == null)
  7.  
    {
  8.  
    view = inflater.inflate(R.layout.item, null);
  9.  
    viewHolder = new ViewHolder();
  10.  
    viewHolder.textView = (TextView) view.findViewById(R.id.textView);
  11.  
    view.setTag(viewHolder);
  12.  
    }else
  13.  
    {
  14.  
    view = convertView;
  15.  
    viewHolder = (ViewHolder) view.getTag();
  16.  
    }
  17.  
    viewHolder.textView.setText("item "+list.get(position));
  18.  
    return view;
  19.  
    }
  20.  
    static class ViewHolder
  21.  
    {
  22.  
    TextView textView;
  23.  
    }

这里采用静态内部类的方式用于定义item 的各个控件 ,如果convertView非空的话,表示该view对应的布局已经存在了,只需要调用getTag获取到即可了,如果convertView为空的话,则需要通过findViewById来获取这个布局中的控件,并且最后将该布局通过setTag设置到view上面即可;

程序的输出结果和上面是一样的,这里不再列出;

我们平常的实际应用中,每个条目有可能不都是一样的,这种情况下会出现不同的项目布局,那么这时候该怎么办呢?同样我们通过实例来学习一下这时候的RecycleBin机制是怎么实现缓存的;

在此,我们增加一个只显示一个按钮的条目,布局文件button.xml

  1.  
    <?xml version="1.0" encoding="utf-8"?>
  2.  
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.  
    android:layout_width="match_parent"
  4.  
    android:layout_height="match_parent"
  5.  
    android:orientation="vertical" >
  6.  
    <Button
  7.  
    android:id="@+id/button"
  8.  
    android:layout_width="wrap_content"
  9.  
    android:layout_height="wrap_content"
  10.  
    />
  11.  
    </LinearLayout>

修改ListViewAdapter类如下:

  1.  
    public class ListViewAdapter extends BaseAdapter{
  2.  
     
  3.  
    List<String> list = new ArrayList<String>();
  4.  
    LayoutInflater inflater = null;
  5.  
     
  6.  
    public ListViewAdapter(List<String> list,Context context) {
  7.  
    this.list = list;
  8.  
    inflater = LayoutInflater.from(context);
  9.  
    }
  10.  
    @Override
  11.  
    public int getItemViewType(int position) {
  12.  
    //0表示显示的是TextView,1表示显示的是Button
  13.  
    if(position % 4 != 0)
  14.  
    return 0;
  15.  
    else
  16.  
    return 1;
  17.  
    }
  18.  
    @Override
  19.  
    public int getViewTypeCount() {
  20.  
    //表示有两种类型的item布局
  21.  
    return 2;
  22.  
    }
  23.  
    @Override
  24.  
    public int getCount() {
  25.  
    return list.size();
  26.  
    }
  27.  
     
  28.  
    @Override
  29.  
    public Object getItem(int position) {
  30.  
    return list.get(position);
  31.  
    }
  32.  
     
  33.  
    @Override
  34.  
    public long getItemId(int position) {
  35.  
    return position;
  36.  
    }
  37.  
     
  38.  
    @Override
  39.  
    public View getView(int position, View convertView, ViewGroup parent) {
  40.  
    View view = null;
  41.  
    TextViewHolder textViewHolder = null;
  42.  
    ButtonViewHolder buttonViewHolder = null;
  43.  
    System.out.println("before: "+list.get(position)+"------- "+convertView);
  44.  
    int type = getItemViewType(position);
  45.  
    if(convertView == null)
  46.  
    {
  47.  
    switch (type) {
  48.  
    case 0:
  49.  
    view = inflater.inflate(R.layout.item, null);
  50.  
    textViewHolder = new TextViewHolder();
  51.  
    textViewHolder.textView = (TextView) view.findViewById(R.id.textView);
  52.  
    view.setTag(textViewHolder);
  53.  
    textViewHolder.textView.setText(list.get(position));
  54.  
    break;
  55.  
    case 1:
  56.  
    view = inflater.inflate(R.layout.button, null);
  57.  
    buttonViewHolder = new ButtonViewHolder();
  58.  
    buttonViewHolder.button = (Button) view.findViewById(R.id.button);
  59.  
    view.setTag(buttonViewHolder);
  60.  
    buttonViewHolder.button.setText("button "+list.get(position));
  61.  
    break;
  62.  
    default:
  63.  
    break;
  64.  
    }
  65.  
    }else
  66.  
    {
  67.  
    view = convertView;
  68.  
    switch (type) {
  69.  
    case 0:
  70.  
    textViewHolder = (TextViewHolder) view.getTag();
  71.  
    textViewHolder.textView.setText(list.get(position));
  72.  
    break;
  73.  
    case 1:
  74.  
    buttonViewHolder = (ButtonViewHolder) view.getTag();
  75.  
    buttonViewHolder.button.setText("button "+list.get(position));
  76.  
    break;
  77.  
    default:
  78.  
    break;
  79.  
    }
  80.  
    }
  81.  
    return view;
  82.  
    }
  83.  
    static class TextViewHolder
  84.  
    {
  85.  
    TextView textView;
  86.  
    }
  87.  
    static class ButtonViewHolder
  88.  
    {
  89.  
    Button button;
  90.  
    }
  91.  
    }

通过getViewTypeCount()返回的是你到底有多少种布局,我们这里有两种

通过getItemViewType(int)返回的是根据你的position得到的对应布局的ID,当然这个ID是可以由你来定的;

修改Activity类

  1.  
    public class MainActivity extends Activity {
  2.  
     
  3.  
    List<String> list = new ArrayList<String>();
  4.  
    ListView listView = null;
  5.  
    @Override
  6.  
    protected void onCreate(Bundle savedInstanceState) {
  7.  
    super.onCreate(savedInstanceState);
  8.  
    setContentView(R.layout.listview);
  9.  
    listView = (ListView) findViewById(R.id.listView);
  10.  
    for(int i = 1;i < 50;i++)
  11.  
    {
  12.  
    if(i % 4 == 0)
  13.  
    {
  14.  
    list.add("button "+i);
  15.  
    }else
  16.  
    list.add("item "+i);
  17.  
    }
  18.  
    ListViewAdapter adapter = new ListViewAdapter(list, this);
  19.  
    listView.setAdapter(adapter);
  20.  
    }
  21.  
    }

界面运行效果图为:

可以发现每隔3个条目我们都会加载另一个不同的条目,接着查看Logcat输出:

在我们滑动屏幕之后,输出结果为:

注意图中突出显示部分,可以发现对于button item,我们也得到了复用,当然这个复用顺序并不一定是按顺序来的,因为RecycleBin机制只会把你已经滑出屏幕的item缓存下来,但是在从缓存中取得时候,并不一定就是按你存进去的顺序取出来的,这点要注意啦,到此一般的ListView优化测试结束了,我们来总结一下:

(1)通过复用view的方式来充分利用android系统本身自带的RecycleBin缓存机制,能够保证即使有再多的item实际中也仅仅会有有限多个item,大大节省内存;

(2)使用静态内部类以及setTag方式,将view与其对应的控件绑定起来,避免了每次得到view以后都需要通过findViewById的方式来获取控件;

(3)对于有多种布局的ListView来说,我们可以通过getViewTypeCount()获得布局的种类,通过getItemViewType(int)获得当前位置上的布局到底是属于哪一类;

好了,这篇先介绍到这里,接下来的一篇将重点介绍ListView加载图片方面的优化措施;

--------------------- 本文来自 她说巷尾的樱花开了 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/hzw19920329/article/details/51383864?utm_source=copy

android-----带你一步一步优化ListView(一)的更多相关文章

  1. android 带表头,左右两个联动的ListView

    package com.rytong.mylist; import java.util.ArrayList; import java.util.HashMap; import java.util.Li ...

  2. Ace教你一步一步做Android新闻客户端(五) 优化Listview

    今天写存货了 调试一些动画参数花了些时间 ,嘿嘿存货不多了就没法做教程了,今天来教大家优化listview,等下我把代码编辑下 这次代码有些多 所以我把条理给大家理清楚.思路就是把加载图片的权利交给O ...

  3. Android中RecyclerView用法,一步一步教你如何使用RecyclerView以及带你走过编码中可能会出现的坑~

    首先,要明白RecyclerView是做什么的?其次是为什么要用RecyclerView?这里牵扯到RecyclerView和ListView的区别,这里不废话,大家自行百度即可! 以下示例我用的An ...

  4. 一步一步打造自己的Android图片浏览器(原创)

    今天我们试着来制作一个自己的Android图片浏览器. 图片浏览器应该具有什么功能呢?鉴于不同的人不同的理解,这里提出一个基本的需求: 搜索手机内的所有图片,展示于一个列表中: 列表中展示的是图片的缩 ...

  5. Ace教你一步一步做Android新闻客户端(一)

    复制粘贴了那么多博文很不好意思没点自己原创的也说不出去,现在写一篇一步一步教你做安卓新闻客户端,借此机会也是让自己把相关的技术再复习一遍,大神莫笑,专门做给新手看. 手里存了两篇,一个包括软件视图 和 ...

  6. 大流量网站性能优化:一步一步打造一个适合自己的BigRender插件

    BigRender 当一个网站越来越庞大,加载速度越来越慢的时候,开发者们不得不对其进行优化,谁愿意访问一个需要等待 10 秒,20 秒才能出现的网页呢? 常见的也是相对简单易行的一个优化方案是 图片 ...

  7. 一步一步了解Cocos2dx 3.0 正式版本开发环境搭建(Win32/Android)

    cocos2d-x 3.0发布有一段时间了,作为一个初学者,我一直觉得cocos2d-x很坑.每个比较大的版本变动,都会有不一样的项目创建方式,每次的跨度都挺大…… 但是凭心而论,3.0RC版本开始 ...

  8. 4.4 CUDA prefix sum一步一步优化

    1. Prefix Sum 前缀求和由一个二元操作符和一个输入向量组成,虽然名字叫求和,但操作符不一定是加法.先解释一下,以加法为例: 第一行是输入,第二行是对应的输出.可以看到,Output[1] ...

  9. 一步一步学android控件(之十五) —— DegitalClock & AnalogClock

    原本计划DigitalClock和AnalogClock单独各一篇来写,但是想想,两个控件的作用都一样,就和在一起写一篇了. DegitalClock和AnalogClock控件主要用于显示当前时间信 ...

  10. 一步一步学android控件(之十六)—— CheckBox

    根据使用场景不同,有时候使用系统默认的CheckBox样式就可以了,但是有时候就需要自定义CheckBox的样式.今天主要学习如何自定义CheckBox样式.在CheckBox状态改变时有时需要做一些 ...

随机推荐

  1. module模块和包(十七)

    在前面的几个章节中我们脚本上是用 python 解释器来编程,如果你从 Python 解释器退出再进入,那么你定义的所有的方法和变量就都消失了. 为此 Python 提供了一个办法,把这些定义存放在文 ...

  2. Markdown语法整理

    标题 语法格式:'#'+'空格'+'文本',一共6级 # 一级标题 ## 二级标题 ### 三级标题 #### 四级标题 ##### 五级标题 ###### 六级标题 斜体 语法格式:1个星号包裹,我 ...

  3. HTML中使用<input>添加的按钮打开一个链接

    在HTML中,<form>表单的<input type="button">可以添加一个按钮.如果想让该按钮实现<a> 的超链接功能,需要如下实现 ...

  4. js的各种验证

    验证手机号格式是否正确 // 判断是否为手机号 isPoneAvailable: function (pone) { var myreg = /^[1][3,4,5,7,8][0-9]{9}$/; i ...

  5. Hive记录-配置远程连接(JAVA/beeline)

    1.修改配置hive-site.xml    hadoop core-site.xml限制---参考Hive记录-部署Hive环境 2.启动hadoop #sh /usr/app/hadoop/sbi ...

  6. .NET Framework自带的文件内存映射类

    最近一直为文件内存映射发愁,整个两周一直折腾这个东西.在64位系统和32位系统还要针对内存的高低位进行计算.好麻烦..还是没搞定 偶然从MSDN上发现.NET 4.0把内存文件映射加到了.NET类库中 ...

  7. GBK-----UTF-8编码格式问题浅析

    首先,想必大家在不同环境下切换写代码的时候,都会遇见乱码的时候(读取二进制的时候采用的编码和最初将字符转换成二进制时的编码不一致.),大多数人都知道,只需要把项目工程的编码格式调整一下为最初的编码就可 ...

  8. js 获取属性名称

    $(function ()        {            myfun();        })        function myfun()        {            var ...

  9. 复选框QCheckBox

    复选框一共有三种状态:全选中.半选中和无选中.若一个父选项的子选项全部为选中状态,则该父选项为全选中:若子选项全部为无选中状态,则该父选项为无选中状态:若子选项既有全选中和无选中状态,则该父选项为半选 ...

  10. Pycharm+Anaconda安装及配置

    Pycharm是一款功能非常强大的IDE,配合Anaconda使用会非常的方便. 在安装Pycharm之前,我们的电脑上已经安装了Anaconda. 我们从官网下载Pycharm社区版.(https: ...