概述
在Android开发过程中ListView是一个很常用的组件,但当使用ListView加载大量数据时,可能会出现卡顿的现象,那么我们该如何优化ListView使之变得流畅易用呢?
ListView优化主要有下面几个方面:
- convertView重用(减少创建View的消耗)
- ViewHolder的子View复用(减少创建 findViewById() 的消耗)
- 分页加载
- 缓存数据复用
ConvertView重用
首先讲下ListView的原理:
ListView中的每一个Item显示都需要Adapter调用一次 getView() 的方法,这个方法会传入一个 convertView 的参数,这个方法返回的View就是这个Item显示的View。
如果当Item的数量足够大,再为每一个Item都创建一个View对象,必将占用很多内存空间,即创建View对象(如以下代码,从xml中生成View,这是属于IO操作)是耗时操作,所以必将影响性能。
mInflater.inflate(R.layout.lv_item, null);
Android提供了一个叫做 Recycler (反复循环)的构件,就是当ListView的Item从滚出屏幕视角之外,对应Item的View会被缓存到Recycler中,相应的会从生成一个Item,而此时调用的getView中的convertView参数就是滚出屏幕的缓存Item的View,所以说如果能重用这个convertView,就会大大改善性能。
如果你有10亿个项目(item),其中只有可见的项目存在内存中,其他的在Recycler中。
ListView先请求一个type1视图(getView)然后请求其他可见的项目。convertView在getView中是空(null)的。
当item1滚出屏幕,并且一个新的项目从屏幕低端上来时,ListView再请求一个type1视图。convertView此时不是空值了,它的值是item1。你只需设定新的数据然后返回convertView,不必重新创建一个视图。
那么,我们怎么重用它呢?贴代码
@Override
public View getView(int position, View convertView, ViewGroup parent) {
/**
* (!!!优化的部分)
* 如果 convertView 是 空(null) 的话 ,就创建新的 convertView , 否则直接使用convertView
*/
if(convertView == null){
convertView = LayoutInflater.from(context).inflate(R.layout.adapter_item, null,true);
}
/**
* 初始化 convertView 中的组件(可进一步优化)
*/
TextView mLeftTv = (TextView) convertView.findViewById(R.id.id_tv_left);
TextView mRightTv = (TextView) convertView.findViewById(R.id.id_tv_right);
/**
* 赋值
*/
DataBean bean = mList.get(position);
mLeftTv.setText(bean.getName());
mRightTv.setText(bean.getSchool());
return convertView;
}
当 convertView 不存在时,即第一次使用它,我们就创建一个item布局的View对象并赋给convertView,以后使用convertView时,只需 复用之前创建的convertView,不需要再次创建item的布局对象了,这样便提高了性能。
ViewHolder的子View复用
我们都知道在getView()方法中的操作是这样的:
先从xml中创建view对象(inflate操作,我们采用了重用convertView方法优化),然后在这个view去findViewById,找到每一个item的子View的控件对象,如:ImageView、TextView等。
这里的 findViewById 操作是一个树查找过程,也是一个耗时的操作,所以这里也需要优化,就是 使用ViewHolder,把每一个item的子View控件对象都放在Holder中 ,当第一次创建convertView对象时,便把这些item的子View控件对象findViewById实例化出来并保存到ViewHolder对象中。
然后用convertView的setTag将viewHolder对象设置到Tag中, 当以后加载ListView的item时便可以直接从Tag中取出复用ViewHolder对象中的,不需要再findViewById找item的子控件对象了。这样便大大提高了性能。
话不多说,上代码
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder mViewHolder = null;
/**
* 如果 convertView 是 空(null) 的话 ,就创建新的 convertView , 否则直接使用convertView
*/
if(convertView == null){
convertView = LayoutInflater.from(context).inflate(R.layout.adapter_item, null,true);
/**
* 初始化 mViewHolder
*/
mViewHolder = new ViewHolder();
mViewHolder.mLeftTv = (TextView) convertView.findViewById(R.id.id_tv_left);
mViewHolder.mRightTv = (TextView) convertView.findViewById(R.id.id_tv_right);
/**
* 将设置好后的 mViewHolder 放入convertView 的 Tag 中。
*/
convertView.setTag(mViewHolder);
}else{
/**
* 从 convertView 的 Tag 中取出 mViewHolder。
*/
mViewHolder = (ViewHolder) convertView.getTag();
}
DataBean bean = mList.get(position);
/**
* (!!!优化的地方)
* 复用 convertView 的 子View控件对象
*/
mViewHolder.mLeftTv.setText(bean.getName());
mViewHolder.mRightTv.setText(bean.getSchool());
return convertView;
}
/**
* 新建的 ViewHolder 内部类
*/
private final class ViewHolder{
TextView mLeftTv;
TextView mRightTv;
}
分页加载
一个应用在用 ListView 展现大量数据时,不会将全部的可用数据都呈现给用户,因为这不管对于服务端还是客户端来说都是不小的压力,因此,很多应用都是采用分批次加载的形式来获取用户所需的数据。
传送门:http://blog.csdn.net/liuhe688/article/details/6852523
缓存数据复用
若 ListView 中要展示的数据需要联网取得,则在首次联网获取数据的时候将数据缓存到本地,然后在为 ListView 设置数据源前加个判断,优先使用本地数据即可。
缓存一些普通字符串数据等可自己来实现,缓存图片则可直接使用 Universal-Image-Loader 等框架来实现,节约效率。
参考文章:
http://blog.csdn.net/u010687392/article/details/45620541
http://www.cnblogs.com/xiaowenji/articles/1900579.html