1. 首先思考一个问题ListView如何才能提高效率 ?

  当convertView为空时候,用setTag()方法为每个View绑定一个存放控件的ViewHolder对象。当convertView不为空,重复利用已经创建的View的时候,使用getTag()方法获取绑定的ViewHolder对象,这样就避免了findViewById对控件的层层查询,而是快速定位到控件。

鉴于上面分析,可以通过如下方法对ListView进行性能优化:

(1)复用convertView,使用历史的View,提高效率%

(2)自定义静态类ViewHolder,减少findViewById的次数,提高效率%

(3)异步加载数据,分页加载数据

(4)使用WeakRefrence 引用ImageView对象(采用WeakRefrence (弱引用),防止在不断刷新当前界面View时候产生内存泄露

2. ListView的性能优化之 使用convertView和ViewHolder

(1)自定义ListView的Adapter时候,里面有个实现方法为getView(),这个方法专门用来加载View的,优化getView()方法,内容如下:

  1. static class ViewHolder {
  2. TextView text;
  3. ImageView icon;
  4. }
  5. public View getView(int position, View convertView, ViewGroup parent)
  6. {
  7. ViewHolder holder;
  8. if (convertView == null) {
  9. convertView = mInflater.inflate(R.layout.list_item_icon_text,parent, false);
  10. holder = new ViewHolder();
  11. holder.text = (TextView) convertView.findViewById(R.id.text);
  12. holder.icon = (ImageView) convertView.findViewById(R.id.icon);
  13. convertView.setTag(holder);
  14. } else {
  15. holder = (ViewHolder) convertView.getTag();
  16. }
  17. holder.text.setText(DATA[position]);
  18. holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);
  19. return convertView;
  20. }

  先讲下ListView的原理:ListView中的每一个Item显示都需要Adapter调用一次getView的方法,这个方法会传入一个convertView的参数,返回的View就是这个Item显示的View。如果当Item的数量足够大,再为每一个Item都创建一个View对象,必将占用很多内存,创建View对象(mInflater.inflate(R.layout.lv_item, null);从xml中生成View,这是属于IO操作)也是耗时操作,所以必将影响性能。Android提供了一个叫做Recycler(反复循环器)的构件,就是当ListView的Item从上方滚出屏幕视角之外,对应Item的View会被缓存到Recycler中,相应的会从下方生成一个Item,而此时调用的getView中的convertView参数就是滚出屏幕的Item的View,所以说如果能重用这个convertView,就会大大改善性能。

  我们都知道在getView方法中的操作是这样的:先从xml中创建view对象(inflate操作,我们采用了重用convertView方法优化),然后在这个view去findViewById,找到每一个子View,如:一个TextView等。这里的findViewById操作是一个树查找过程,也是一个耗时的操作,所以这里也需要优化,就是使用viewHolder,把每一个子View都放在Holder中,当第一次创建convertView对象时,把这些子view找出来。然后用convertView的setTag将viewHolder设置到Tag中,以便系统第二次绘制ListView时从Tag中取出。当第二次重用convertView时,只需从convertView中getTag取出来就可以。

3. ListView的性能优化之 使用异步加载:

参见:Android中ListView异步加载数据

4. ListView性能优化之 使用分页加载数据:

 通常来说,一个应用在展现大量数据时,不会将全部的可用数据都呈现给用户,因为这不管对于服务端还是客户端来说都是不小的压力,因此,很多应用都是采用分批次加载的形式来获取用户所需的数据。比如:微博客户端可能会在用户滑动至列表底端时自动加载下一页数据,也可能在底部放置一个“加载更多”按钮,用户点击后,加载下一页数据。

我们今天就结合实例来演示一下使用ListView获取数据的过程,当然你使用GridView也是类似的。

(1)新建一个"ListView分页加载"工程,看一下 结构图 和 最终效果图:

结构图:

最终效果图:

上面结构图之中,包含了三个布局文件,一个Adapter和一个Activity

(2)首先我们来到主布局main.xml,它包含一个ListView组件,代码如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:orientation="vertical"
  6. android:paddingLeft="3dp"
  7. android:paddingRight="3dp" >
  8. <ListView
  9. android:id="@id/android:list"
  10. android:layout_width="fill_parent"
  11. android:layout_height="wrap_content" />
  12. </LinearLayout>

这里我们引用了Android内置的名为list的id,因为我们后面要使用到ListActivity,我们的MainActivity继承于它。

 

然后是list_item.xml,它是ListView中单个列表项的布局文件,从效果图中可以看到,这里只使用一个TextView组件:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:orientation="vertical" >
  6. <TextView
  7. android:id="@+id/list_item_text"
  8. android:layout_width="fill_parent"
  9. android:layout_height="fill_parent"
  10. android:gravity="center"
  11. android:paddingBottom="10dp"
  12. android:paddingTop="10dp"
  13. android:textSize="20sp" />
  14. </LinearLayout>

我们注意到在右图中列表底部有一个按钮不同于其他的列表项,这是什么情况?事实上这个按钮是我们在ListView底部添加的一个视图。ListView组件提供了两个很实用的功能,那就是可以在顶部和底部添加自定义的视图。我们在此处ListView的底部添加了一个视图用来加载更多数据,这个视图对应着load_more.xml布局文件,代码如下:

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

(3)接下来我们了解一下我们的Adapter,ListViewAdapter代码如下:

  1. package com.himi.listviewload;
  2. import java.util.List;
  3. import android.content.Context;
  4. import android.view.LayoutInflater;
  5. import android.view.View;
  6. import android.view.ViewGroup;
  7. import android.widget.BaseAdapter;
  8. import android.widget.TextView;
  9. public class ListViewAdapter extends BaseAdapter {
  10. private List<String> items;
  11. private LayoutInflater inflater;
  12. public ListViewAdapter(Context context, List<String> items) {
  13. this.items = items;
  14. inflater = (LayoutInflater) context.getSystemService(Context
  15. .LAYOUT_INFLATER_SERVICE);
  16. }
  17. @Override
  18. public int getCount() {
  19. return items.size();
  20. }
  21. @Override
  22. public Object getItem(int position) {
  23. return items.get(position);
  24. }
  25. @Override
  26. public long getItemId(int position) {
  27. return position;
  28. }
  29. @Override
  30. public View getView(int position, View convertView, ViewGroup parent) {
  31. if (convertView == null) {
  32. convertView = inflater.inflate(R.layout.list_item, null);
  33. }
  34. TextView text = (TextView) convertView.findViewById(R.id.list_item_text);
  35. text.setText(items.get(position));
  36. return view;
  37. }
  38. /**
  39. * 添加列表项
  40. * @param item
  41. */
  42. public void addItem(String item) {
  43. items.add(item);
  44. }
  45. }

 这个ListViewAdapter是我们自定义适配器,它继承自BaseAdapter,实例化此适配器需要一个Context对象来获取LayoutInflater实例和一个集合对象来充当适配器的数据集;在getView方法中我们填充list_item.xml布局文件,完成列表每一项的数据显示;addItem方法用来在加载数据时向数据集中添加新数据。

(4)最后我们看看一个MainActivity,如下:

  1. package com.himi.listviewload;
  2. import java.util.ArrayList;
  3. import android.app.ListActivity;
  4. import android.os.Bundle;
  5. import android.os.Handler;
  6. import android.util.Log;
  7. import android.view.View;
  8. import android.widget.AbsListView;
  9. import android.widget.AbsListView.OnScrollListener;
  10. import android.widget.Button;
  11. import android.widget.ListView;
  12. public class MainActivity extends ListActivity implements OnScrollListener {
  13. private ListView listView;
  14. private int visibleLastIndex = 0; //最后的可视项索引
  15. private int visibleItemCount; //当前窗口可见项总数
  16. private ListViewAdapter adapter;
  17. private View loadMoreView;
  18. private Button loadMoreButton;
  19. private Handler handler = new Handler();
  20. @Override
  21. public void onCreate(Bundle savedInstanceState) {
  22. super.onCreate(savedInstanceState);
  23. setContentView(R.layout.main);
  24. loadMoreView = getLayoutInflater().inflate(R.layout.load_more, null);
  25. loadMoreButton = (Button) loadMoreView.findViewById(R.id.loadMoreButton);
  26. listView = getListView(); //获取id是list的ListView
  27. listView.addFooterView(loadMoreView); //设置列表底部视图
  28. initAdapter();
  29. setListAdapter(adapter); //自动为id是list的ListView设置适配器
  30. listView.setOnScrollListener(this); //添加滑动监听
  31. }
  32. /**
  33. * 初始化适配器
  34. */
  35. private void initAdapter() {
  36. ArrayList<String> items = new ArrayList<String>();
  37. for (int i = 0; i < 10; i++) {
  38. items.add(String.valueOf(i + 1));
  39. }
  40. adapter = new ListViewAdapter(this, items);
  41. }
  42. /**
  43. * 滑动时被调用
  44. */
  45. @Override
  46. public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
  47. this.visibleItemCount = visibleItemCount;
  48. visibleLastIndex = firstVisibleItem + visibleItemCount - 1;
  49. //System.out.println("onScroll:"+visibleLastIndex);
  50. }
  51. /**
  52. * 滑动状态改变时被调用
  53. */
  54. @Override
  55. public void onScrollStateChanged(AbsListView view, int scrollState) {
  56. int itemsLastIndex = adapter.getCount() - 1; //数据集最后一项的索引
  57. int lastIndex = itemsLastIndex + 1; //加上底部的loadMoreView项
  58. if (scrollState == OnScrollListener.SCROLL_STATE_IDLE && visibleLastIndex == lastIndex) {
  59. //如果是自动加载,可以在这里放置异步加载数据的代码
  60. Log.e("LOADMORE", "loading...");
  61. }
  62. }
  63. /**
  64. * 点击按钮事件
  65. * @param view
  66. */
  67. public void loadMore(View view) {
  68. loadMoreButton.setText("loading..."); //设置按钮文字loading
  69. handler.postDelayed(new Runnable() {
  70. @Override
  71. public void run() {
  72. loadData();
  73. adapter.notifyDataSetChanged(); //数据集变化后,通知adapter
  74. listView.setSelection(visibleLastIndex - visibleItemCount + 2); //设置选中项:ListView第一个可视Item
  75. System.out.println("loadMore(visibleLastIndex):"+visibleLastIndex);
  76. loadMoreButton.setText("load more"); //恢复按钮文字
  77. }
  78. }, 2000);
  79. }
  80. /**
  81. * 模拟加载数据
  82. */
  83. private void loadData() {
  84. int count = adapter.getCount();
  85. for (int i = count; i < count + 10; i++) {
  86. adapter.addItem(String.valueOf(i + 1));
  87. }
  88. }
  89. }

如代码所示,我们在onCreate方法被调用时获取listView组件,设置其底部视图为loadMoreView,它包含一个按钮,点击时会触发loadMore方法调用,另外在为listView设置完适配器时,又为其设置了滑动事件监听器,滑动列表时onScroll会被调用,滑动状态改变时onScrollStateChanged会被调用

 

上面设置ListView的滚动监听器:setOnScrollListener(new OnScrollListener{……})

在上面的监听器中有两个方法:滚动状态发生变化的方法(onScrollStateChanged)和 ListView被滚动的时候调用的方法(onScroll)。

滚动状态发生改变的方法(onScrollStateChanged)之中,有三种状态:

> 手指按下移动的状态:SCROLL_STATE_TOUCH_SCROLL(触摸滑动

> 惯性滚动:SCROLL_STATE_FLING(滑翔

> 静止状态:SCROLL_STATE_IDLE(静止

分页(分批)加载数据,我们只关心静止状态(SCROLL_STATE_IDLE)只关心最后一个可见的条目,如果最后一个可见条目就是数据适配器(集合)里面的最后一个数据,此时可加载更多的数据。在每次加载的时候,计算滚动的数量,当滚动的数量大于等于总数量的时候,可以提示用户没有更多的数据了

(5)部署程序到手机上,演示一下:

如图,当点击完按钮后,出现加载动作,加载完之后如右图所示,新数据紧接在原数据之后。然后我们滑动到底部,加载按钮仍可工作:

最后,我们测试一下滑动列表到底部,然后松开,控制台打印如下:

我们看到onScrollStateChanged方法里的if语句里代码执行了,所以如果我们希望自动加载的话,可以把加载代码放于此处。

Android进阶笔记11:ListView篇之ListView性能优化的更多相关文章

  1. Android进阶笔记11:ListView篇之ListView显示多种类型的条目(item)

    ListView可以显示多种类型的条目布局,这里写显示两种布局的情况,其他类似. 1. 这是MainActivity,MainActivity的布局就是一个ListView,太简单了这里就不写了,直接 ...

  2. Android进阶笔记:Messenger源码详解

    Messenger可以理解为一个是用于发送消息的一个类用法也很多,这里主要分析一下再跨进程的情况下Messenger的实现流程与源码分析.相信结合前面两篇关于aidl解析文章能够更好的对aidl有一个 ...

  3. Android进阶笔记:AIDL内部实现详解 (二)

    接着上一篇分析的aidl的流程解析.知道了aidl主要就是利用Ibinder来实现跨进程通信的.既然是通过对Binder各种方法的封装,那也可以不使用aidl自己通过Binder来实现跨进程通讯.那么 ...

  4. 一步一步实现listview加载的性能优化

    listview加载的核心是其adapter,本文针对listview加载的性能优化就是对adpter的优化,总共分四个层次: 0.最原始的加载 1.利用convertView 2.利用ViewHol ...

  5. 原博客地址http://blog.chinaunix.net/uid/20656672.html不再维护(10年前数百篇oracle/teradata性能优化、故障处理案例)

    原博客地址http://blog.chinaunix.net/uid/20656672.html不再维护(数百篇oracle/teradata性能优化.故障处理原创文章) 858871 top 500 ...

  6. Android进阶笔记16:ListView篇之ListView刷新显示(全局 和 局部)

    一.ListView内容变化后,动态刷新的步骤(全局刷新): (1)更新适配器Adapter数据源:(不要使用匿名内部类) (2)调用适配器Adapter的刷新方法notifyDataSetChang ...

  7. Android进阶笔记14:ListView篇之ListView性能优化

    1. 首先思考一个问题ListView如何才能提高效率 ? 当convertView为空时候,用setTag()方法为每个View绑定一个存放控件的ViewHolder对象.当convertView不 ...

  8. Android进阶笔记13:ListView篇之ListView刷新显示(全局 和 局部)

    一.ListView内容变化后,动态刷新的步骤(全局刷新): (1)更新适配器Adapter数据源:(不要使用匿名内部类) (2)调用适配器Adapter的刷新方法notifyDataSetChang ...

  9. iOS开发UI篇—UITableviewcell的性能优化和缓存机制

    iOS开发UI篇—UITableviewcell的性能问题 一.UITableviewcell的一些介绍 UITableView的每一行都是一个UITableViewCell,通过dataSource ...

随机推荐

  1. 磁盘挂载 fdisk

    fdisk  -l 查看是否  已加入磁盘 fdisk  /dev/sdb fdisk   -cu  /dev/sdb    取消兼容模式 添加一个硬盘,只创建一个分区,然后把分区挂载到/data 第 ...

  2. DbUtils(二) 结果集实例

    单行数据处理:ScalarHandler    ArrayHandler    MapHandler    BeanHandler 多行数据处理:BeanListHandler    Abstract ...

  3. 获得Windows系统的远程桌面连接历史记录

    转载:http://www.mottoin.com/tech/109219.html 渗透技巧—获得Windows系统的远程桌面连接历史记录 0x00 前言 在渗透测试中,远程桌面连接的历史记录不可忽 ...

  4. 邮件email

    参考地址:https://blog.csdn.net/baidu_30000217/article/details/52942258 邮箱配置地址:http://service.exmail.qq.c ...

  5. NFS 网络文件系统

    1, NFS存储服务概念介绍    NFS是Network File System的缩写,中文意思是网络文件系统,    它的主要功能是通过网络(一般是局域网)让不同的主机系统之间可以共享文件或目录  ...

  6. vuex中filter的使用 && 快速判断一个数是否在一个数组中

    vue中filter的使用 computed: mapState({ items: state => state.items.filter(function (value, index, arr ...

  7. 迪米特法則 Law of Demeter

    又稱為"最小知識"原則, 若對Law of Demeter做一個簡單總結: 任何對象的任何方法只能調用以下對象中的方法: (1) 該對象本身 (2) 所傳入的參數對象 (3) 它所 ...

  8. React.js 小书 Lesson11 - 配置组件的 props

    作者:胡子大哈 原文链接:http://huziketang.com/books/react/lesson11 转载请注明出处,保留原文链接和作者信息. 组件是相互独立.可复用的单元,一个组件可能在不 ...

  9. [DForm]我也来做自定义Winform之另类标题栏重绘

    据说得有楔子 按照惯例,先来几张样例图(注:为了展示窗口阴影效果,截图范围向外扩展了些,各位凭想象吧).                   还要来个序 其实,很多年没写过Winform了,前端时间在 ...

  10. Windows的任务管理器怎么显示进程的图标

    博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:Windows的任务管理器怎么显示进程的图标.