在开发中ListView是一个用的比较的频繁组件,使用ListView来展示数据,我们一般需要做以下几个步骤:在主布局文件中写ListView组件,设置一些属性,编写一个布局文件用来做为ListView的item,最后写一个适配器来连接ListView和数据。我们对ListView的优化基本上都是在适配器中实现的。
先来说下自定义适配器中复写baseAdapter的几个方法。
//返回数据源中数据的个数,如果该方法的返回值为0,那么适配器就不用生成布局对象了,提高了程序性能
@Override
public int getCount() {
int count = 0;
if(datas!=null)
count = datas.size();
return count;
}
@Override
public Object getItem(int position) {
// TODO 根据位置返回数据项
return datas.get(position);
}
@Override
public long getItemId(int position) {
// TODO 返回数据项的位置
return position;
}
//返回用来显示每个数据项的布局对象
//参数parent接收的是容器视图对象(在本例中就是ListView对象)
//参数position接收的是当前显示的数据项的位置
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return convertView;
}
其中的getView方法是最为重要的。
ListView的优化分为以下几个方面:
- 内存空间上的优化(ConvertView)
- 运行时间的优化(ViewHolder)
ListView的item多布局复用
内存空间上的优化(ConvertView) :
问题:ListView每加载一个item就会创建一个与之对应布局的实例,如果item达到一定的数量时就会出现应用消耗内存太大,使应用出现卡顿的现象。
实例代码:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//第一种方式:没有实现布局对象的复用
// TODO 生成显示数据项的布局对象
//根据布局的xml文件生成布局对象
//得到LayoutInflater对象
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
//LayoutInflater对象可以根据布局的xml文件生成布局对象
//第二个参数必须是null,因为如果不是null,会去调用ListView的addView()方法,而ListView没有这个方法
View view = inflater.inflate(resourceId, null);
//System.out.println("====="+view);
//convertView接收的是可复用的布局对象,当没有可复用的布局对象时,接收的是null
//System.out.println("====="+convertView);
//把要显示的数据放到布局对象中的TextView上,得到TextView对象
//从整体的布局对象中获取TextView对象
TextView textView = (TextView) view.findViewById(R.id.textView);
//给textView的text属性设置为当前显示的数据项
textView.setText(datas.get(position).toString());
return view;
}
这样写代码,有多少个item就会创建多个对象。
解决:getview方法给我们提供了convertView参数,其实它就是一个可用的复用对象,如果加载第一屏的数据,没有可以复用的对象,那么这个convertView就是空的,如果它不是空的,那么我们就可以拿来复用,再也不需要创建那么多的布局实例。
实例代码:
//第二种方式:
//convertView接收的是可复用的布局对象,当没有可复用的布局对象时,接收的是null
//先判断是否有可复用的
if(convertView==null){
//convertView = LayoutInflater.from(context).inflate(resourceId, null);
convertView = LayoutInflater.from(context).inflate(resourceId, parent, false);
}
//得到布局对象中的TextView对象
//每次都要findViewById,影响程序性能
TextView tv = (TextView) convertView.findViewById(R.id.textView);
tv.setText(datas.get(position).toString());
return convertView;
运行空间的优化
问题:对于以上的写法,发现在不断的调用findviewbyid方法,这会是应用在运行时受到一定的影响,如果能把这个方法的调用减少,就能提高应用运行的速度。
解决:所以我们设计一个viewholder类来持有一个item布局中的控件,并将其实例化,作为convertview的tag值,用的时候直接在convertview中取就好了,省去了每次都要findviewbyid。
实例代码:
//第三种方式:
ViewHolder holder = null;
if(convertView ==null){
convertView = LayoutInflater.from(context).inflate(resourceId, parent, false);
holder = new ViewHolder();
//让holder中的textView成员指向布局对象中的TextView对象
holder.textView = (TextView) convertView.findViewById(R.id.textView);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();//当可复用时,直接获取holder
}
//操作holder中的textView就是操作布局对象中的TextView对象
holder.textView.setText(datas.get(position).toString());
return convertView;
}
//通过ViewHolder减少findViewById方法执行的次数
class ViewHolder
{
TextView textView;
}
ListView的item多布局复用:
这个已经在上面实现了。
二,加载图片出现图片错位,闪朔等问题的解决
在listview中有大量的图片时,如果处理不好就会出现图片的错位和闪朔等问题,
问题来源分析:
为了优化listview我们复用了convertview,其实问题就出现在这个地方。当item中有图片是我们会异步去加载图片,并展示在相应的位置,但是由于某些原因,图片的加载的速度就不一样的,比如,当你要加载一个item里面有图片,这时你复用了上面的convertview,正好上面的那个item也有一张图片加载,你刚显示这个item时,上一张图片就加载好了,而你的图片却还在加载中,就会先在布局上显示上一张图片,等你的图片加载好后,就立马显示你的图片。这就出先了图片的快速切换,在我们看来就是闪朔。有时还会出现图片位置的错乱。
解决方案:
所以只要解决了,图片可以显示在它指定的那个imageview上面,就ok了。所以我们给imageview设置一个tag可以是图片的url,等图片加载完后就根据tag来找到相应的imageview并显示图片。如果这个imageview已经被复用了,在listview中就找不到了,就不加载图片。这样就解决了问题。
实例代码:
异步任务:
public class DownImageAsyncTask extends AsyncTask<String, Void, Bitmap> {
private String path;
public DownImageAsyncTask(DownBack mDownBack) {
this.mDownBack = mDownBack;
}
@Override
protected Bitmap doInBackground(String... params) {
String url = null;
if (params != null) {
url = params[0];
path = url;
try {
byte[] bytes = HttpUtils.getByteFromPath(url);
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
return bitmap;
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null) {
if (mDownBack != null) {
mDownBack.response(path, bitmap);
}
}
}
public interface DownBack {
void response(String url, Bitmap bitmap);
}
private DownBack mDownBack;
}
这里定义了个接口给适配器实现,在加载完图片后由异步任务来调用。
getview中的代码:
String imageUrl = Urls.BASE_IMAGE_URL + news.getCover();
iv_image.setTag(imageUrl);
iv_image.setImageResource(R.mipmap.ic_launcher);
if (imageUrl.length() <= 0) {
iv_image.setVisibility(View.GONE);
} else {
iv_image.setVisibility(View.VISIBLE);
SoftReference<Bitmap> soft = mBitmapCache.get(imageUrl);
if (soft!=null){
Bitmap bitmap=soft.get();
if (bitmap!=null){
iv_image.setImageBitmap(bitmap);
}else{
downImage(imageUrl);
}
}else {
downImage(imageUrl);
}
}
在这个适配器中为了避免每次都发请求去下载图,我们就用了一个map来保存已经下载好的图片,map的key是图片的url,value是bitmap的一个软应用类型的实例。这样每次可以先判断map中有没有图片,如果有就直接显示,没有就去发请求下载图片。