ListView终极优化方法,绝对流畅
listview可以说是Android开发中最常见的UI控件了,listview能够以列表的方式显示大量同类的数据,
这样问题就产生了,既然是大量数据,就会使用到很多布局,给布局绑定数据,listview将占用大量资源还可能会产生卡顿现象。
private String[] mArrData;
private TextView mTV;
@Override
protected void onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.main);
mTV = (TextView) findViewById(R.id.tvShow);
mArrData = new String[ 1000 ];
for ( int i = 0 ; i < 1000 ; i ++ ) {
mArrData[i] = " Google IO Adapter " + i ;
}
mAdapter = new TestAdapter( this , mArrData);
((ListView) findViewById(android.R.id.list)).setAdapter(mAdapter);
}
listview现在最常用也拥有很好的性能的优化方式是在Adapter中使用静态的ViewHolder,具体代码如下:
Activity
private
TestAdapter mAdapter;
private String[] mArrData;
private TextView mTV;
@Override
protected void onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.main);
mTV = (TextView) findViewById(R.id.tvShow);
mArrData = new String[ 1000 ];
for ( int i = 0 ; i < 1000 ; i ++ ) {
mArrData[i] = " Google IO Adapter " + i ;
}
mAdapter = new TestAdapter( this , mArrData);
((ListView) findViewById(android.R.id.list)).setAdapter(mAdapter);
}
Adapter
private
int
count
=
0
;
private
long
sum
=
0L
;
@Override
public View getView( int position, View convertView, ViewGroup parent) {
// 开始计时
long startTime = System.nanoTime();
ViewHolder holder;
if (convertView == null ) {
convertView = mInflater.inflate(R.layout.list_item_icon_text,
null );
holder = new ViewHolder();
holder.icon1 = (ImageView) convertView.findViewById(R.id.icon1);
holder.text1 = (TextView) convertView.findViewById(R.id.text1);
holder.icon2 = (ImageView) convertView.findViewById(R.id.icon2);
holder.text2 = (TextView) convertView.findViewById(R.id.text2);
convertView.setTag(holder);
}
else {
holder = (ViewHolder)convertView.getTag();
}
holder.icon1.setImageResource(R.drawable.icon);
holder.text1.setText(mData[position]);
holder.icon2 .setImageResource(R.drawable.icon);
holder.text2.setText(mData[position]);
// 停止计时
long endTime = System.nanoTime();
// 计算耗时
long val = (endTime - startTime) / 1000L ;
Log.e( " Test " , " Position: " + position + " : " + val);
if (count < 100 ) {
if (val < 1000L ) {
sum += val;
count ++ ;
}
} else
mTV.setText(String.valueOf(sum / 100L )); // 显示统计结果
return convertView;
}
}
static class ViewHolder {
TextView text1;
ImageView icon1;
TextView text2;
ImageView icon2;
}
@Override
public View getView( int position, View convertView, ViewGroup parent) {
// 开始计时
long startTime = System.nanoTime();
ViewHolder holder;
if (convertView == null ) {
convertView = mInflater.inflate(R.layout.list_item_icon_text,
null );
holder = new ViewHolder();
holder.icon1 = (ImageView) convertView.findViewById(R.id.icon1);
holder.text1 = (TextView) convertView.findViewById(R.id.text1);
holder.icon2 = (ImageView) convertView.findViewById(R.id.icon2);
holder.text2 = (TextView) convertView.findViewById(R.id.text2);
convertView.setTag(holder);
}
else {
holder = (ViewHolder)convertView.getTag();
}
holder.icon1.setImageResource(R.drawable.icon);
holder.text1.setText(mData[position]);
holder.icon2 .setImageResource(R.drawable.icon);
holder.text2.setText(mData[position]);
// 停止计时
long endTime = System.nanoTime();
// 计算耗时
long val = (endTime - startTime) / 1000L ;
Log.e( " Test " , " Position: " + position + " : " + val);
if (count < 100 ) {
if (val < 1000L ) {
sum += val;
count ++ ;
}
} else
mTV.setText(String.valueOf(sum / 100L )); // 显示统计结果
return convertView;
}
}
static class ViewHolder {
TextView text1;
ImageView icon1;
TextView text2;
ImageView icon2;
}
在Adapter的代码中,在getView方法里首先判断convertView是否为空,若为空则加载相应布局,若不为空则直接使用该布局,这能够很有效的使用Android为listview提供的缓存机制:只加载一屏的布局,之后滑动出来的item使用的是之前已经加载的布局的缓存;
而使用静态的ViewHoulder的目的则是节省了findViewById的时间。如果不使用ViewHolder,每次getView的时候都需要得到一次子布局,而这也是很耗时并且耗资源的;如果使用了ViewHolder作为子布局的缓存,使用View的setTag方法将缓存与每个item绑定,则也可以省去了findViewById的事件;而将ViewHolder设置为static的目的是指在初始化Adapter时初始化一次这个内部类,否则将会在每次创建Adapter时都要初始化一次,而这是没有必要的。
上述方法能够解决大部分listview消耗资源以及卡顿的问题,但对于不同的需求的listview来说还会存在其他让listview卡顿的原因,比如listview的item每次加载时都需要获得图片并设置到imageview中,item加载时需要进行大量的计算,item里的TextView需要设置指定字体;这些耗时的操作都会让listview滑动起来很卡,带来不好的体验;
这几天我一直在研究Android4.0+系统联系人的源码,因为我发现,系统联系人采用的也是listview布局,每一个item都有图片文字,而且使用了fastscroller模式,对联系人以首字母进行了分来,同时Adapter还实现了SectionIndexer接口,能够实现这种.
但是却一点都不卡,而我自己做的一个类似的界面,却十分卡顿,很影响用户体验;于是我找来了Android体统联系人的源码,自己建立起来了一个可以运行的程序,然后去研究它是如何实现这么流畅的listview的;
经过研究我发现系统联系人的listview使用的是自定义的listview:
PinnedHeaderListView
它的定义是这样的:
/**
* A ListView that maintains a header pinned at the top of the list. The
* pinned header can be pushed up and dissolved as needed.
*/
它给listview加了一个header即显示在联系人界面最上方贴着屏幕顶部的那部分,同时它继承自
AutoScrollListView
而这个AutoScrollListView继承自listview,对listview进行了一些优化,让listview在互动到指定的item时更流畅;
看到这里,于是我使用了这个
AutoScrollListView
,但效果不是很明显,listview还是很卡;在系统联系人使用的listview中没有找到解决方法,我开始研究它的Adapter;研究Adapter发现,系统的Adapter进行了很多层的封装,完全淡化了geiView方法;使用了很多AsyncTask来加载不同的数据,然后使用了CursorLoader来将加载好的数据添加到Adapter里,同时还将图片的加载与数据的加载进行了分离,代码逻辑十分复杂;但通过这个我得出了一个结论:不要将任何的耗时操作放在listview的getView方法里。
在系统联系人的这些Adapter里我发现getView方法中没有任何的耗时操作,在设置图片时图片已经得到,对列表按照字母进行的分类也已经分类好了,存放在一个内部类里;在得出这个结论之前我尝试过很多系统联系人中的代码,但都没有得到明显的效果,经过大量的测试我得出了这个结论,并且在测试中得到了验证。
在我的项目中,listview的每一个item都有一个图片,和很多TextView,而且所有的TextView都要设置非系统的字体;Adapter使用的是ViewHolder优化,在getView中的代码已经很少了,但是还是卡;我的listview中的数据是一个对象的List,在对象里只存放了item需要展示的图片的资源ID,或者是图片的路径,需要通过一些操作才能获得图片,而这些操作其实是很耗时的;于是我将原来的对象进行了更改,将图片对象直接存放在item对应的对象中,然后再Adapter初始化的时候将这些对象初始化,虽然listview展示所需的时间稍微长了一点,但是结果是listview滑动流畅了很多;接着我又将从assets中获得字体TypeFace的操作放在了Adapter初始化的方法中,并且将字体通过静态的变量都存起来,然后再getView中只需为TextView设置一下taptface即可,不需要在从asset中获取字体所花费的时间;通过上面两步操作之后,我的listview一点都不卡了,十分流畅。
综上,listview的优化其实就是去找getView中的耗时操作,然后提取出来,要么使用异步的方式为item的布局设置数据,要是实在需要同步,就只能在Adapter初始化时将数据准备好,然后再getView中只需绑定一下就行。